Spring Framework 3 を使う

戻る
目次 動作環境

このページでは以下の環境での動作を説明しています。

  • JDK 1.6.0_45
  • Spring Framework 3.2.6
  • AspectJ 1.7.4
  • Commons BeanUtils 1.9.2
  • Commons DBCP 1.4
  • Commons Lang3 3.3.2
  • Commons Pool 1.6
  • Hibernate Validator 4.3.1
  • JBoss Logging 3.1.0
  • Log4J 1.2.16
  • Validation API 1.0.0
「Spring Framework」の主な機能

Spring Framework(以下Spring)にて用意されている、主な機能を紹介します。

  • DI(Dependency Injection)を用いたインスタンス生成

    設定ファイルによる、複雑なインスタンスを生成する方法を提供します。従来のFactory概念が覆されます。

  • データアクセス フレームワーク

    JDBC, iBATIS, Hibernate, JDO, JPAと連携する機能を提供します。

  • AOPによるインタセプタ

    AOP(Aspect Oriented Programming)を用いることによる、インタセプタ機能を提供します。

    AOP Alliance の提供するモジュールを利用しているため、作成したインタセプタは他でも利用できます。

  • AOPによるトランザクション管理

    JDBC, JTAに代表されるトランザクション管理を提供しています。

    入れ子になったトランザクションに対応し、例外時に自動ロールバックといった機能も提供します。

  • Model-View-Controller フレームワーク

    Spring MVC は Struts に変わるRESTFul WEBアプリ作成機能を提供します。

Springを用いたインスタンス生成
最も簡単なサンプル

Springを利用したアプリケーションでは、new演算子を用いずインスタンス生成をSpringに委譲します。

Springでは設定ファイルを用いインスタンス生成を行います。以下は設定ファイル(spring.xml)のサンプルになります。

初歩的なSpring設定ファイル <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd "> <bean id="hogehoge" class="foo.bar.Abc" />; </beans>

設定ファイルの解説を簡単にします。ここではXMLの記述法については説明しません。

beansタグの開始タグには属性が存在します。こちらは後述します。以降のソースでは属性を省略し記述しません。

beansタグの中の要素にはbeanタグが存在します。このタグにはid属性とclass属性が存在します。id属性はSpringで管理するインスタンスのユニークな値を設定します。Springに"hogehoge"のインスタンスをほしいと要求すると、このidタグの該当箇所からインスタンスを生成します。class属性はidにて指定された場合の返却すべきインスタンスをFQCNにて記述します。

つまり今回例ではSpringに対し「"hogehoge"のインスタンスがほしい」と要求すると「foo.bar.Abc」クラスのインスタンスが要求元に返されます。

Springにおいて上記設定ファイルに特定の名称は定められておりません。今回はspring.xmlという名称とし、クラスパスの通っている箇所(ルート)に保存します。

ではこちらをJavaソースから呼び出してみます。

Abc.java package foo.bar; public class Abc { public String toString() { return "!!!Abc!!!"; } }
Spring設定ファイルを利用する初歩的なサンプル(Kicker.java) package foo.bar; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Kicker { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Abc abc = (Abc) ac.getBean("hogehoge"); System.out.println(abc); } public static void same() { Abc abc = new Abc(); System.out.println(abc); } }

mainメソッドを参照下さい。先頭行でSpring設定ファイルを読み込んでいます。今回は設定ファイルをクラスパスのルートに配置しましたのでクラスパスを利用し設定ファイルを読み込むClassPathXmlApplicationContextクラスを利用しました。ClassPathXmlApplicationContextクラスの他にFileSystemXmlApplicationContextクラスも存在します。どちらのクラスも「implements ApplicationContext」になっています。他にも実装クラスは存在しますので必要に応じ実装クラスを選択して下さい。

次にSpring設定ファイルを読み込んだApplicationContextからインスタンスをgetBeanメソッドにて取得します。先ほどの例だとidに「hogehoge」と設定しましたのでgetBean("hogehoge")とします。戻り値はObject型になります。

これと同様のことをSpringを用いずに行うsameメソッドも参考として記述しておきました。どちらも同じ動作を行います。

他のgetBean

従来の実装では、上記のsameメソッドのように、new演算子を用いるため、キャスト変換は不要です。ですが上記の「getBean」は戻り値がObject型なため毎回キャストするのはとても時間を要します。

このような場合のために「getBean」には他のパラメータのものが用意されています。

他のgetBean ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Abc abc = (Abc) ac.getBean("hogehoge"); Abc abc2 = ac.getBean("hogehoge", Abc.class); Abc abc3 = ac.getBean(Abc.class);

abc2はシグネチャが「T getBean(String, Class<T>)」になっていますので、キャストしたい型をSpringに伝えることでキャスト変換が不要となります。

abc3はどうでしょう。今まで利用していたidの"hogehoge"をSpringに伝えておりません。この場合、パラメータで指定したクラス(今回でいうとAbc.class)に該当するインスタンスを自動で探し出します。検索のイメージとしてJavaで表現するなら「instanceof Abc」になります。

上記のabc3の利用法の場合、以下の状況ではどうなるでしょうか。

Abc2.java package foo.bar; public class Abc2 extends Abc {}
初歩的な設定ファイルを変更 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd "> <bean id="hogehoge"  class="foo.bar.Abc" /> <bean id="hogehoge2" class="foo.bar.Abc2" /> </beans>

以下のabc3の利用法でSpringに問い合わせます。

他のgetBean(abc3のみ) try { Abc abc3 = ac.getBean(Abc.class); Assert.fail(); } catch (NoUniqueBeanDefinitionException e) { e.printStackTrace(); }

instanceof Abc」がid="hogehoge"とid="hogehoge2"と複数該当してしまいます。このような場合、Springは例外(NoUniqueBeanDefinitionException)をthrowします。

つまり同一インスタンスを別名(別のidのこと)で登録すると、利用時に例外となってしまうため、こちらの利用法は避けるほうがよいでしょう。

生成したインスタンスの特長

Springでのインスタンス生成では、生成したインスタンスを再利用する(一般的なシングルトン)か、毎回生成するか設定ファイル(scope属性)にて指定することができます。

初歩的な設定ファイルを変更 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd "> <bean id="hogehoge3" class="foo.bar.Abc" /> <bean id="hogehoge4" class="foo.bar.Abc" scope="prototype" /> </beans>

scope属性に「prototype」を指定するとgetBeanの度にインスタンスを生成。scope属性に「singleton」を指定するかscope属性を省略すれば最初に生成したインスタンスを呼ばれる度に返却します(一般的なシングルトン)。

Javaで表現するなら以下のようになります。

シングルトンの状況確認 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Abc abc31 = ac.getBean("hogehoge3", Abc.class); Abc abc32 = ac.getBean("hogehoge3", Abc.class); Abc abc41 = ac.getBean("hogehoge4", Abc.class); Abc abc42 = ac.getBean("hogehoge4", Abc.class); Assert.assertTrue(abc31 == abc32); Assert.assertTrue(abc31 != abc41); Assert.assertTrue(abc41 != abc42);
singletonとprototypeの違い

どのようなインスタンスがscope=singletonになり、どのようなインスタンスがscope=prototypeになるのでしょうか。

シングルトンのインスタンスは再利用が前提なので、Logicクラス(Serviceクラスと同じ意)、DAO(Data Access Object)クラスはシングルトンにすべきでしょう。

反対に再利用ができないクラス、DTO(Data Transfer Object)、JavaBeansはリクエストの度に値が変化するので、シングルトンにはなりえないでしょう。

LogicクラスやDAOクラスに、リクエストの度に変化する(もしくは変化する可能性のある)フィールドを含めていませんか?DTOクラスはデータを転送するためのクラスなので、そのような値はDTOに含めるべきと筆者は思います。

属性の順番

属性に順番はあるのでしょうか。属性に順番はありません。ではどのような順に記述するのがよいのでしょうか。

一般論としてJavaソースやXMLは左から右へ、上から下へ順に読まれます。左上に重要な属性を記述するのがよいでしょう。

では重要な属性とは何でしょう。JavaソースからSpringにインスタンス生成を依頼する際はid属性を頼りに紐付けを参照するためid属性を最初に記述するとよいでしょう。

2番目はどうでしょうか。class属性も重要かと思いますが、筆者はscope属性を2番目に記述するのがよいと思います。scope属性はアプリケーションの動作に大きな影響を与えます。id,class,scopeの順で記述されていたとするとscope属性を見落としてしまうかもしれません。class属性より先にscope属性が記述されていれば見落とされる確率は大いに減るでしょう。

このように一工夫するだけでバグが減少する場合もあるとおもいます。たかが順番ですが、順を工夫し記述して下さい。

DI(Dependency Injection)

Springではインスタンスを生成の際、あらかじめセッター(setter injection)を呼び出すことができます。同様にパラメータ付きコンストラクタ(constractor injection)を呼び出すことができます。

Injectionの例
DI用クラス(Human.java) package foo.bar; import java.io.Serializable; @SuppressWarnings("serial") public class Human implements Serializable { private Integer age; private String name; public Human() { super(); } public Human(Integer age, String name) { this(); this.age = age; this.name = name; } public Integer getAge() { return age; } public String getName() { return name; } public void setAge(Integer age) { this.age = age; } public void setName(String name) { this.name = name; } }
DI用設定ファイル <beans> <bean id="human1" class="foo.bar.Human" /> <bean id="human2" class="foo.bar.Human"> <property name="age" value="12" /> <property name="name"> <value>Taro</value> </property> </bean> <bean id="human3" class="foo.bar.Human"> <constructor-arg> <value>12</value> </constructor-arg> <constructor-arg> <value>Taro</value> </constructor-arg> </bean> </beans>
DIを実施 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Human human1 = ac.getBean("human1", Human.class); Assert.assertNull(human1.getAge()); Assert.assertNull(human1.getName()); Human human2 = ac.getBean("human2", Human.class); Assert.assertEquals(12, human2.getAge().intValue()); Assert.assertEquals("Taro", human2.getName()); Human human3 = ac.getBean("human3", Human.class); Assert.assertEquals(12, human3.getAge().intValue()); Assert.assertEquals("Taro", human3.getName());
Injection時に型を指定する

Injectionの際、パラメータが異なる同名メソッドが複数存在する場合等、型を指定しなければならない場合、どのパラメータのメソッドを呼び出すか指定するため、type属性にてパラメータの型を指定することができます。

型を指定したDI <bean id="human4" class="foo.bar.Human"> <property name="age" value="12" /> <property name="name"> <value type="java.lang.String">Taro</value> </property> </bean> <bean id="human5" class="foo.bar.Human"> <constructor-arg type="java.lang.Integer"> <value>12</value> </constructor-arg> <constructor-arg value="Taro" type="java.lang.String" /> </bean>

上記サンプルを参照するとわかるのですが、propertyタグにtype属性は存在しません。propertyタグでtype属性を指定したい場合、子要素のvalueタグを用いてtype属性を指定します。

StringやIntegerでない自作クラスもDI可能

Injectionの際、自作クラスをDIすることも可能です。propertyタグの値にbeanタグを含めて下さい。詳細は以下を参考にしてください。

自作クラスをDIされるクラス(Organization.java) package foo.bar; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @SuppressWarnings("serial") public class Organization implements Serializable { private Human leader; private Map<String, Human> member; private String name; public Human getLeader() { return leader; } public Map<String, Human> getMember() { return new LinkedHashMap<String, Human>(this.member); } public String getName() { return name; } public void setLeader(Human leader) { this.leader = leader; } public void setMemberArray(Human[] member) { this.member = new LinkedHashMap<String, Human>(); for (Human human : member) { this.member.put(human.getName(), human); } } public void setMemberList(List<Human> member) { setMemberArray(member.toArray(new Human[0])); } public void setMemberMap(Map<String, Human> member) { this.member = new LinkedHashMap<String, Human>(member); } public void setName(String name) { this.name = name; } }
自作クラスをDIされるクラスの設定ファイル <bean id="org1" class="foo.bar.Organization"> <property name="name" value="Organization1" /> <property name="leader"> <bean class="foo.bar.Human"> <property name="age" value="12" /> <property name="name" value="Taro" /> </bean> </property> </bean>
自作クラスをDIされるクラスを利用する ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Organization org1 = ac.getBean("org1", Organization.class); Assert.assertEquals("Organization1", org1.getName()); Assert.assertNotNull(org1.getLeader()); Assert.assertEquals(12, org1.getLeader().getAge().intValue()); Assert.assertEquals("Taro", org1.getLeader().getName());
一度設定したbeanを再利用

上記でInjectionしたHumanのインスタンスは、id="human2"やid="human3"と同一です。今回のhuman2は比較的容易なインスタンスなのでよいですが、複雑なインスタンス生成を複数回記述するのはバグの元です。Springではこのようなとき、一度定義したbeanをref属性(もしくはrefタグ)で再利用することができます。

再利用した設定ファイル <bean id="human2" class="foo.bar.Human"> <property name="age" value="12" /> <property name="name"> <value>Taro</value> </property> </bean> <bean id="org2" class="foo.bar.Organization"> <property name="name" value="Organization2" /> <property name="leader" ref="human2" /> </bean> <bean id="org3" class="foo.bar.Organization"> <property name="name" value="Organization3" /> <property name="leader"> <ref bean="human2" /> </property> </bean>
配列やList,Map形式もDI可能

配列やList型の場合、propertyタグの値にarrayタグやlistタグを記述します。そのarrayタグやlistタグの値にbeanタグやrefタグで内容を記述します。

Map型の場合は配列やList型に比べるとやや複雑です。arrayタグやlistタグのようにmapタグを含めることは同じです。Map型は配列やList型と異なりKey-Value形式ですのでentryタグを利用します。

entryタグにはkey, key-ref, value, value-ref, value-typeという属性が存在します。先頭がkeyのものはKeyに関する項目、先頭がvalueのものはvalueに関する項目になります。末尾がrefはrefタグ同様の動作をし、末尾がtypeは型を指定できます。key-type属性は存在しないので型指定を行う際はkey-refで型指定を行うのかと思います。

またここで使用するmapタグによるインジェクションされるMapのインスタンスの実装クラスはLinkedHashMapになります。

配列やList,Map形式をDIの設定ファイル <bean id="male1" class="foo.bar.Human"> <property name="age" value="13" /> <property name="name" value="Male1" /> </bean> <bean id="male2" class="foo.bar.Human"> <property name="age" value="14" /> <property name="name" value="Male2" /> </bean> <bean id="female1" class="foo.bar.Human"> <property name="age" value="15" /> <property name="name" value="Female1" /> </bean> <bean id="org4" class="foo.bar.Organization"> <property name="memberArray"> <array> <ref bean="male1" /> <ref bean="male2" /> <ref bean="female1" /> <bean class="foo.bar.Human"> <property name="age" value="16" /> <property name="name" value="Female2" /> </bean> </array> </property> </bean> <bean id="org5" class="foo.bar.Organization"> <property name="memberList"> <list> <ref bean="male1" /> <ref bean="male2" /> <ref bean="female1" /> <bean class="foo.bar.Human"> <property name="age" value="16" /> <property name="name" value="Female2" /> </bean> </list> </property> </bean> <bean id="org6" class="foo.bar.Organization"> <property name="memberMap"> <map> <entry key="Male1" value-ref="male1" /> <entry key="Male2" value-ref="male2" /> <entry key="Female1"> <ref bean="female1" /> </entry> <entry key="Female2"> <bean class="foo.bar.Human"> <property name="age" value="16" /> <property name="name" value="Female2" /> </bean> </entry> </map> </property> </bean>
parentやabstractを用いて設定を継承させる

bean定義毎に同じ設定を行うことは時間がかかりますし、設定忘れの原因になりかねません。Springではbean定義をJavaの親クラス、子クラスのように継承させることができます。

Extends0.java package foo.bar; abstract public class Extends0 { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return getName(); } }
Extends1.java package foo.bar; public class Extends1 extends Extends0 { private String suffix = "."; public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } public String toString() { return super.toString() + getSuffix(); } }
Extends2.java package foo.bar; public class Extends2 extends Extends1 { public String toString() { return "I'm " + super.toString(); } }

上記のExtends0,Extends1,Extends2のクラスを参照して下さい。Extends2のインスタンスを頻繁に生成するとします。その際、毎回のようにSuffixに"!"を設定するとします。その場合以下の設定のようになるでしょう。

毎回同じ設定をする例 <bean id="ng1" class="foo.bar.Extends1"> <property name="name" value="AAA" /> <property name="suffix" value="!" /> </bean> <bean id="ng2" class="foo.bar.Extends2"> <property name="name" value="BBB" /> <property name="suffix" value="!" /> </bean>

このような場合、parent属性を利用することで設定を引き継ぐことができます。

parent属性の使用例 <bean id="ex1" class="foo.bar.Extends1"> <property name="name" value="AAA" /> <property name="suffix" value="!" /> </bean> <bean id="ex2" parent="ex1" class="foo.bar.Extends2"> <property name="name" value="BBB" /> </bean>

上記のようなparent属性を利用する際、parent属性で指定されたbeanタグのclassがabstractクラス(今回でいうとExtends0)の場合、abstract属性を利用することで設定ができるようになります。

abstract属性の使用例 <bean id="ex0" abstract="true" class="foo.bar.Extends0"> <property name="name" value="unknown" /> </bean> <bean id="ex3" parent="ex0" class="foo.bar.Extends1"> <property name="name" value="CCC" /> <property name="suffix" value="." /> </bean>

またparent属性で指定したbeanと同じクラス(同じclass属性)の場合、class属性を省略することが可能です。

class属性の省略例 <bean id="ex4" parent="ex3"> <property name="name" value="DDD" /> </bean>

下記は検証結果になります。

parent属性, abstract属性の利用例、class属性の省略例の検証結果 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Extends1 ex1 = ac.getBean("ex1", Extends1.class); Assert.assertEquals("AAA", ex1.getName()); Assert.assertEquals("!", ex1.getSuffix()); Assert.assertEquals("AAA!", ex1.toString()); Extends2 ex2 = ac.getBean("ex2", Extends2.class); Assert.assertEquals("I'm BBB!", ex2.toString()); Extends1 ex3 = ac.getBean("ex3", Extends1.class); Assert.assertEquals("CCC.", ex3.toString()); Extends1 ex4 = ac.getBean("ex4", Extends1.class); Assert.assertEquals("DDD.", ex4.toString());
設定ファイルに記述できるその他のインスタンス
ListやMapを直接Bean化する

性別(1=男性,2=女性)といったようなコードを扱う場合、Map型変数に値を格納しておくべきである。このような場合、設定ファイルはどのようになるでしょうか。

Mapをbeanタグで表現する <bean id="map1" class="java.util.TreeMap"> <constructor-arg> <map> <entry key="0" value="unknown" /> <entry key="1" value="male" /> <entry key="2" value="female" /> </map> </constructor-arg> </bean>

beansタグの直下にmapタグを記述できればよいのですが、スキーマを参照する限り許されておりません。これを考慮すると上記のような「beans -> bean -> constructor-arg -> map」という記述になるのではないでしょうか。

この記述で表現できるのですが、上記設定を正確に解釈すると「TreeMapをインスタンス化し、コンストラクタにLinkedHashMapを設定する。その内容はentryタグの...となる」になります。これはTreeMapのインスタンス化のみでよいところ、冗長なLinkedHashMapをインスタンス化しています。

このようにMapを直接インスタンス化するには「名前空間:util」を用い以下のように記述します。

util:mapを利用した設定ファイル <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd "> <util:map id="map2"> <entry key="0" value="unknown" /> <entry key="1" value="male" /> <entry key="2" value="female" /> </util:map> </beans>

名前空間:util」を利用する場合、beansタグの属性を上記のように変更します。「名前空間:util」を利用できるようになるとutil:mapタグが利用できるようになります。util:mapタグが利用できるようになれば、上記のmapタグと記述方法は同一です。

以下を利用し動作確認をしてみます。

util:mapの動作確認 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); @SuppressWarnings("rawtypes") Map map1 = ac.getBean("map1", Map.class); Assert.assertEquals(3, map1.size()); Assert.assertEquals("unknown", map1.get("0")); Assert.assertEquals("male", map1.get("1")); Assert.assertEquals("female", map1.get("2")); Assert.assertEquals(map1, ac.getBean("map2"));

List型変数をインスタンス化する場合「util:list」を利用することで実現できます。

util:listを利用した設定ファイル <util:list id="list1"> <ref bean="male1" /> <ref bean="male2" /> <ref bean="female1" /> <bean class="foo.bar.Human"> <property name="age" value="16" /> <property name="name" value="Female2" /> </bean> </util:list>
定数を直接Bean化する

Javaにて既に用意されている定数をインスタンス化することができます。

Javaの定数 package jp.co.mclnet; public interface Abc { public int READONLY = 1; }
util:constantを利用した設定ファイル <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd "> <util:constant id="hoge" static-field="jp.co.mclnet.Abc.READONLY" /> </beans>
メッセージプロパティファイルを利用する

Javaにてメッセージファイルを利用するにはResourceBundleクラスが存在しますが、SpringではMessageSourceインタフェースの実装クラスを利用します。

プロパティファイルの記述法はResourceBundleクラスに代表されるプロパティファイルと同様なため特に解説は行いません。

メッセージファイル(i18n.properties) login.fail={0} or {1} is incorrect.
MessageSourceの設定例 <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames" value="classpath:/i18n" /> </bean>

利用時は「ReloadableResourceBundleMessageSource」を指定します。クラス名から想像できますがこちらのクラスは一定時間で再読込する機能を持っています。ここはプロパティファイルの設定方法についての解説なのでReloadableResourceBundleMessageSourceの詳細は説明しません。

propertyタグを利用しプロパティファイルの名称を設定します。name属性には"basenames"をvalue属性には"classpath:/i18n"を指定します。このvalue属性の意味は「クラスパスの通っているパスの先頭のi18n.propertiesを利用する」という意になります。また複数のプロパティファイルがある場合、「value="classpath:/aaa,classpath:/bbb"」のようにカンマ区切りで複数ファイルを指定することもできます。

では実際に利用してみます。

MessageSourceの利用例 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); String base = "login.fail"; Object[] placeHolders = new Object[] { "ID", "PASSWORD" }; Locale locale = Locale.getDefault(); MessageSource ms = ac.getBean(MessageSource.class); String msg1 = ms.getMessage(base, placeHolders, locale); Assert.assertEquals("ID or PASSWORD is incorrect.", msg1); String msg2 = ac.getMessage(base, placeHolders, locale); Assert.assertEquals(msg1, msg2);

インタフェース名(MessageSource)でms変数にSpringからインスタンスを取得しています。その後「ms.getMessage(String, Object[], Locale)」にて値を取得しています。

上記のようにMessageSource#getMessageしてもよいのですが、「ApplicationContext extends MessageSource」なので「ac.getMessage(String, Object[], Locale)」でも同じ結果が得られます。ただしこの場合、MessageSourceは「id="messageSource"」にしなければなりません(messageSourceは予約語)。

Calendarのようなファクトリメソッドを利用

「サービス開始日」といった情報を、あらかじめDIしたい場合など、Calendarクラスを利用したい場合があります。

しかしCalendarクラスの場合、staticなgetInstanceを利用するのが定石です。このような場合、以下のfactory-method属性を利用します。

ファクトリメソッドを利用する設定例 <bean id="calendar" class="java.util.Calendar" factory-method="getInstance" /> <bean id="serviceIn" class="java.util.Calendar" factory-method="getInstance"> <constructor-arg ref="timeZone" /> <property name="timeInMillis" value="954547200026" /> <!-- 2000/04/01 00:00:00 --> </bean> <bean id="timeZone" class="java.util.TimeZone" factory-method="getTimeZone"> <constructor-arg value="UTC" /> </bean>
ファクトリメソッドを利用する実装例 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Calendar cal1 = ac.getBean("calendar", Calendar.class); Assert.assertEquals(TimeZone.getDefault(), cal1.getTimeZone()); Calendar cal2 = ac.getBean("serviceIn", Calendar.class); Assert.assertEquals(TimeZone.getTimeZone("UTC"), cal2.getTimeZone()); Assert.assertEquals(2000, cal2.get(Calendar.YEAR)); Assert.assertEquals(4, cal2.get(Calendar.MONTH) + 1); Assert.assertEquals(1, cal2.get(Calendar.DATE));
MessageDigestをDIする

factory-method属性がパラメータなしであれば上記の方法でよいのですが、MessageDigestクラスのようにgetInstanceにパラメータを含めたいような場合は、以下のように行います。

設定ファイルの例 <property name="messageDigest"> <bean class="java.security.MessageDigest" factory-method="getInstance"> <constructor-arg value="MD5" /> </bean> </property>
Connection Pool

データベースに接続する際、Tomcatアプリケーション(Servletアプリケーション)では、以下の様な設定で、TomcatにConnectionをPoolさせ、利用することが多いと思います。

既存の Connection Pool の例(server.xml) <Context path="/app" docBase="${catalina.home}/webapps/app"> <ResourceParams name="jdbc/app"> <parameter> <name>driverClassName</name> <value>com.mysql.jdbc.Driver</value> </parameter> <parameter> <name>url</name> <value>jdbc:mysql://localhost:3306/appdb</value> </parameter> </ResourceParams> </Context>

この「Connection Pool」を、Logicクラスから利用することが困難です。SpringアプリケーションではSpringに「Connection Pool」をさせることができます。Springにプールさせることによって、Logicクラスからも容易にConnectionを利用することができます。以下に「Commons Connection Pool」を利用した設定例を記述します。

Connection Pool の設定例 <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/appdb" /> <property name="username" value="root" /> <property name="password" value="" /> <property name="maxActive" value="10" /> <property name="maxWait" value="5000" /> <property name="defaultAutoCommit" value="false" /> <property name="validationQuery" value="SELECT 1" /> </bean>
Connection Pool の利用例 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); DataSource ds = ac.getBean("dataSource", DataSource.class); Connection conn = ds.getConnection(); ..... ..... .....
HSQLDBを使う

100% Pure Java で実装されているHSQLDB。プロトタイプ作成時や、高速のメモリDBとして使用することもあるかと思います。こちらを利用する際は以下を参考に設定して下さい。

HSQLDB使用時の設定例 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd "> <jdbc:initialize-database data-source="dataSource"> <jdbc:script location="classpath:/hsqldb.sql" /> </jdbc:initialize-database> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:mem:spring" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean> </beans>
Bean Validation

SpringでのValidationは「JSR303 Bean Validation」の定義(jar)を利用する前提となっています(ここで「Bean Validation」については解説しません)。

Validationを行うには「javax.validation.Validator」のインスタンスが必要です。そのインスタンスをSpringに管理させます。MessageSourceが「id="messageSource"」であったように、Validationでは「id="validator"」にしなければなりません(validatorは予約語)。

設定は以下のようにします。このときValidationにて精査エラーになった場合のエラーメッセージのファイルをプロパティ名validationMessageSourceにて指定することができます。

Validation の設定例 <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="validationMessageSource" ref="messageSource" /> </bean>
Validation の実施例 XXX xxx = new XXXImpl(); ..... ..... ..... ApplicationContext ac = new ClassPathXmlApplicationContext("simple.xml"); Validator v = ac.getBean("validator"); Set<ConstraintViolation<XXX>> violations = v.validate(xxx, XXX.class);
他の設定ファイルをインポート

設定を1つのファイルに書くと設定ファイルが肥大化してしまいます。複数の設定ファイルに設定を分割しインポートすることができます。

以下はspring.xmlファイルからdao.xml設定ファイルとother.xmlファイルをインポートする例になります。

設定ファイルをインポートする例 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <import resource="classpath:/dao.xml" /> <import resource="classpath:/other.xml" /> </beans>
dao.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/appdb" /> <property name="username" value="root" /> <property name="password" value="" /> <property name="maxActive" value="10" /> <property name="maxWait" value="5000" /> <property name="defaultAutoCommit" value="false" /> <property name="validationQuery" value="SELECT 1" /> </bean> </beans>
other.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <bean id="calendar" class="java.util.Calendar" factory-method="getInstance" /> </beans>
AOP(Aspect Oriented Programming)

SpringでのAOPは「AOP Alliance」の定義(jar)を利用する前提となっています(ここで「AOP」「AOP Alliance」については解説しません)。

SpringにてAOPするにあたり以下の注意点があります。

  • publicのメソッドのみAOP可能。
  • リフレクションで呼び出されたメソッド等にはAOP不可。
  • Springを経由した(設定ファイルに記述したbean)場合のみAOP可能。その場合「java.lang.reflect.Proxy」を利用する前提。
  • Springを経由してもポイントカットに該当するもののみAOP可能。
  • ポイントカットに該当するメソッドは指定したクラスのみ対応。親クラスにしか実装されていないメソッド等はAOP不可。
  • aaaメソッドをポイントカットで指定していたとしてもthis.aaaはAOPしない。
  • 複数のポイントカットに該当した場合、すべてにおいてAOPされる。
  • *1 上記項目は全て筆者の実測であり、誤っている可能性もありますのでご自身でご確認下さい。

まずはAOPされる側とする側のクラスです。

AOPされるクラスのインタフェース package foo.bar; public interface Logic { public Object execute(Object instance); }
AOPされるクラス package foo.bar; public class LogicImpl implements Logic { public Object execute(Object instance) { return instance.toString(); } }
AOPする処理 package foo.bar; import org.aspectj.lang.ProceedingJoinPoint; public class InterceptorImpl { public Object invoke(ProceedingJoinPoint pjp) throws Throwable { Object proceed = pjp.proceed(); System.out.println(proceed); return proceed; } }

LogicImplのexecuteメソッドに対しAOPすることとします。AOPする場合、InterceptorImplのinvokeメソッドでAOPします。

以下の設定ファイルでは「名前空間:AOP」を利用します。AOPするクラス、されるクラスをbeanタグにて定義します。

AOPを行うにはaop:configタグを利用します。

aop:configタグの子要素にaop:pointcutタグを用意しました。aop:pointcutタグにはid属性とexpression属性があります。id属性は他のタグから参照するための値です。expression属性にはAOPをする条件を記述します。この属性の値について詳細には解説しませんが「foo.barパッケージの任意のクラスでexecuteメソッド(パラメータは問わない)場合にAOPする」という意味になります。これでAOPする準備ができました。

aop:aspectタグにはAOPする条件に該当した場合の行為を設定します。id属性はこのaspectのid、ref属性は呼び出すインターセプタのidを設定します。

aop:aroundタグにはAOPした場合に呼び出すメソッド名を設定します。pointcut-ref属性はポイントカットのid、method属性は呼び出すメソッド名を設定します。上記例にある通りpointcut-ref属性の代わりにpointcut属性を指定することもできます。

AOPの設定例 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd "> <bean id="foo.bar.Logic" class="foo.bar.LogicImpl" /> <bean id="interceptorClass" class="foo.bar.InterceptorImpl" /> <aop:config proxy-target-class="true"> <aop:pointcut id="pc" expression="execution(* foo.bar.*.execute(..))" /> <aop:aspect id="aspect" ref="interceptorClass"> <aop:around pointcut-ref="pc" method="invoke" /> <aop:around pointcut="execution(* foo.bar..execute(..))" method="invoke" /> </aop:aspect> </aop:config> </beans>

上記を用意し実行します。SpringからLogicImplのインスタンスを取得します。Logic#executeメソッドを呼び出すとInterceptorImpl#invokeが呼び出されるはずです。

今回例ではaop:aroundを2つ(pointcut-ref属性のものとpointcut属性のもの)定義しましたので、InterceptorImpl#invokeが2回呼び出されます。

AOPの実施例 ApplicationContext ac = new ClassPathXmlApplicationContext("simple.xml"); Logic l = ac.getBean("foo.bar.Logic", Logic.class); l.execute(l);
proxy-target-class

今回例ではaop:configタグのproxy-target-class属性を"true"としました(default=false)。この設定がfalseだとどのように動作するのでしょうか。

上記のlに対し以下のコードを実行します。するとtrueの場合は「instanceof LogicImpl」がtrue、逆はfalseとなります。

AOPの実施例 Assert.assertTrue(l instanceof Logic); Assert.assertTrue(l instanceof LogicImpl); // proxy-target-class="true" Assert.assertFalse(l instanceof LogicImpl);// proxy-target-class="false"

Spring AOPではproxy-target-class=falseの場合「java.lang.reflect.Proxy」を利用します。このProxyクラスはインタフェースを満たすがクラスは満たさないクラスになるため「l instanceof LogicImpl == false」になります。proxy-target-class=trueなら「l instanceof LogicImpl == true」になります。

ですから常にproxy-target-class=trueでよいのでは?と思いますが、Eclipseのデバッグが期待通り動作しない場合があるなど、不十分な動作をすることがあります。

falseがdefault値ということは、Springではインターフェースを強要する実装が推奨されているのかとおもいます。

ポイントカットの条件(expression属性)

ポイントカットの指定にはいくつかの方法があります。ここでは簡単に説明します。詳細はSpringのページにて確認下さい。

「メソッドの呼び出しに対してAOPする」ための「execution」を説明します。executionの書式は以下になります。

executionの書式 execution(modifiers? ret-type declaring-type? name(param) throws?)
executionの書式
No. 英名 具体値 解説
1 modifiers * public アクセス修飾子
2 ret-type String 戻り値
3 declaring-type ??? 筆者は不明
4 name foo.bar..exec* パッケージ名を含めたメソッド名。
パッケージ名に..が含まれる場合、を含むサブパッケージの意。
つまりここでは「foo.barとそのサブパッケージ」
末尾はメソッド名。
ここではexec*となっているのでメソッド名がexecで始まるもの。
「foo.bar.*.exec」なら「foo.bar.1階層のパッケージでメソッド名がexecとなる。」
5 param .. パラメータ
StringやInteger等と具体的に記述。
また「..」は全てのパラメータに該当するので、事実上パラメータは問わない。
6 throws ??? 筆者は不明。おそらく例外

つまり今回は「execution(* foo.bar..execute(..))」ですので「foo.barパッケージとそのサブパッケージでメソッド名がexecute」の場合AOPが行われます。

execution以外にwithin,target,args等が存在しますので、詳細に調べてみてはいかがでしょうか。

AOPの発動タイミング

今回例ではaop:aroundタグを利用しましたが、他にはどのようなものがあるのでしょうか。

AOPの発動タイミングとタグ名一覧
No. 英名 解説
1 aop:before ジョインポイントの前に実行
2 aop:after ジョインポイントの後に実行
3 aop:after-returning ジョインポイントが完全に正常終了後に実行
4 aop:around ジョインポイントの前後で実行
5 aop:after-throwing ジョインポイントで例外発生時に実行

* ジョインポイントとはAOPが発生した条件のこと

AOP JDBC Transaction
AOPでトランザクション管理を行う

AOPを使って何がうれしいの?ログを出力する以外に何かあるの?と質問されることがあります。AOPというより横断的関心事ということの解説になるのですが、ログ出力以外に、サーブレットのFilterを用いて「ログイン認証/権限管理」を行うことも横断的関心事です。

話は戻りまして、せっかくAOPというメソッド実行時に割り込ませる技術が利用できるのですから、トランザクション管理(コネクションの取得、トランザクション開始、コミット、ロールバック)に利用しよう、というのがこの章の内容になります。

AOPにてトランザクション管理を行うと、開発者がコネクションの取得、開始、コミット、ロールバックという作業から開放され、開発工数が減少する傾向にあります。

またフレームワークでトランザクション管理を行うということは、常にトランザクション管理が行われるようになるので、開発者がトランザクション管理を忘れたとしても、自動的にフレームワーク配下のトランザクション管理が行われることになります。

トランザクション管理としてはJTA,DataSource等、多数対応しています。今回はDataSourceでのトランザクション管理を行ってみます。

AOPでトランザクション管理の設定例 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> ..... 省略 ..... </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <aop:config proxy-target-class="false"> <aop:advisor advice-ref="txAdvice" pointcut="execution(* foo.bar.logic.*Logic*.execute(..))" /> <aop:advisor advice-ref="txAdvice" pointcut="execution(* foo.bar.service.*Service*.execute(..))" /> </aop:config> </beans>

AOPでトランザクション管理を行うには「transactionManager」のインスタンスと、特別なtx:adviceタグを利用します。

「transactionManager」のインスタンスとして「DataSourceTransactionManager」を使います。その際、dataSourceプロパティに既存のDataSourceのインスタンスを渡します。

次にトランザクションの管理法をtx:adviceタグで設定します。こちらは後述します。

最後にどのメソッドが呼ばれた時にトランザクション管理を行うかaopタグを利用し指定します。

上記設定ではfoo.bar.logicパッケージ、もしくはfoo.bar.serviceパッケージ内のクラスで、クラス名にLogic、もしくはServiceを含むクラスの、executeメソッドにてAOPを行うこととなります。

では具体的なクラスを見てみます。

AOPでトランザクション管理できる具体的なクラス package foo.bar.logic; import java.sql.Connection; import java.sql.PreparedStatement; import javax.sql.DataSource; import org.springframework.jdbc.datasource.DataSourceUtils; import foo.bar.Logic; public class JdbcExampleLogic implements Logic { private DataSource dataSource; public Object execute(Object instance) { DataSource ds = null; Connection conn = null; try { ds = getDataSource(); conn = DataSourceUtils.getConnection(ds); ..... 省略 ..... return null; } finally { if (conn != null) { DataSourceUtils.releaseConnection(conn, ds); } } } public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }
トランザクション管理されたBean設定例 <bean id="foo.bar.logic.JdbcExampleLogic" scope="prototype" class="foo.bar.logic.JdbcExampleLogic"> <property name="dataSource" ref="dataSource" /> </bean>

この状態でfoo.bar.logic.JdbcExampleLogic#executeメソッドを呼び出すと自動的にトランザクションが開始します。

この際、Connectionを取得するには「DataSourceUtils.getConnection(DataSource)」、closeするには「DataSourceUtils.releaseConnection(Connection, DataSource)」を利用します。

トランザクション開始、自動コミット、自動ロールバックの設定

tx:adviceタグの子要素にtx:attributesタグ、その子要素にtx:methodタグが存在します。

tx:methodタグの属性にname属性とpropagation属性、read-only属性、rollback-for属性、no-rollback-for属性等が存在します。

name属性にはメソッド名を記述します。今回例では*となっているので全てのメソッドが対象となります。

propagation属性にはトランザクションの開始法を指定します。

トランザクションの開始法
No. 解説
1 REQUIRED トランザクションが開始されていなければ開始する
2 SUPPORTS トランザクションが開始されていればそれを利用。開始されていなければ開始せず利用しない。
3 MANDATORY トランザクションが開始されていなければ例外を発生。
4 REQUIRES_NEW 常に新しいトランザクションを開始する。
5 NOT_SUPPORTED トランザクションを利用しない。
6 NEVER トランザクションが開始していれば例外を発生。
7 NESTED ネストしたトランザクションを開始する。

propagation属性の解説をみてもわかりますが、トランザクションを入れ子にすることもできます。入れ子を考慮し具体的な値を選択するべきでしょう。

read-only属性はトランザクションが読み取り専用かどうか設定できます。DB等からデータを取得専用のreadメソッドやfindメソッド等の場合、read-only属性をtrueにしてもよいかもしれません。

rollback-for属性、no-rollback-for属性は例外発生時の動作を決定します。指定していない場合、例外が発生するとロールバックし、例外がなく正常終了した場合、コミットします。

詳細に設定しないのであれば「<tx:method name="*" propagation="REQUIRED" />」という設定でよいでしょう。

トランザクションが入れ子の際、どのタイミングで自動ロールバックか

上記の設定の際、トランザクションが入れ子になっているとき、内側のメソッド内で例外が発生するとどのような動作をするでしょうか。

筆者としてはpropagation属性がREQUIREDなのですから、その一番外側のメソッドを例外で抜けるか、正常終了で抜けるかで判断してほしいと思っています。

ですが内側のメソッドを抜ける際にロールバックしてしまうようです。

自動トランザクションの状況をログ出力する

今回のDataSourceTransactionManagerクラスでトランザクション管理を行うのですが、コミットした、ロールバックしたということをログ出力し状況を検証したいと思います。

その際は以下の設定をlog4jの設定に行うことで、トランザクションの状態がログ出力されます。

トランザクション状態をログ出力する log4j.logger.org.springframework.jdbc.datasource.DataSourceTransactionManager=debug
Spring MVC
Spring MVC の特徴

Spring MVC は DIを各所に用いたフレームワークになります。

従来のWEBアプリではweb.xmlに代表する各種設定ファイルを記述するのが重労働で、デグレードの原因にもなります。しかしアノテーションによるマッピング機能等で、最低限の設定ファイルの設定のみで実装することもできます。

またアノテーションによるマッピングを用いることで、マッピングされたメソッドを呼び出す場合、メソッド名も固定されておらず、戻り値、パラメータも自由に設計できます。アノテーションでマッピングすることで特定のクラスを継承する必要もなく、JavaBeansのようなPOJOのDTOクラスも利用することができます。

いま流行りのRESTfulアプリケーションを作成し易い機能も所有しています。

リクエストマッピング

従来のWEBアプリでは「http://servername/appname/api/foo/bar」をマッピングするには以下のようにweb.xmlを記述します。

Servletによるマッピング
従来のweb.xml(Servlet) <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd "> <servlet> <servlet-name>servlet</servlet-name> <servlet-class>foo.bar.HogeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>servlet</servlet-name> <url-pattern>/api/foo/bar</url-pattern> </servlet-mapping> </web-app>
Strutsによるマッピング
従来のweb.xml(Struts) <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd "> <servlet> <servlet-name>struts</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>struts</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping> </web-app>
SpringMVCによるマッピング
SpringMVCでのweb.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd "> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/spring.xml</param-value> </context-param> <servlet> <servlet-name>spring_mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>spring_mvc</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping> </web-app>

SpringMVCとStrutsのweb.xmlはほとんど同じようになっています。

Spring設定ファイルの書き方

アノテーションによる自動マッピング機能を使います。

context:component-scanタグのbase-package属性でアノテーションによる自動DIを行うパッケージを指定します。下記例だと「foo.bar.controller」パッケージ内のクラスが自動DIの対象となります。

mvc:annotation-drivenタグを記述することで「アノテーションによる自動マッピングを行う」という宣言になります。

SpringではVIEWを管理・解決するVIEWResolverという概念があります。beanのid属性が「viewResolver」のものが標準的(default)に利用されます。こちらでは標準的なVIEWResolverはJSPとし、ファイル名接頭語は「/WEB-INF/view/」ファイル名接尾語は「.jsp」ということになります。つまり標準VIEWResolverに"hogehoge"という文字列を渡すと「/WEB-INF/view/hogehoge.jsp」が表示されます。

SpringMVCの標準的な設定ファイル(spring.xml) <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd "> <context:component-scan base-package="foo.bar.controller" /> <mvc:annotation-driven /> <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/view/" /> <property name="suffix" value=".jsp" /> <property name="order" value="2" /> </bean> </beans>
SpringMVC用Javaクラス

最後にSpringMVCで利用するクラスを作成します。これは「extends HttpServlet」や「extends Action」といったSpringによって最初に呼び出される自作のJavaクラスです。Springではコントローラーと呼びます。

@Controllerアノテーションはコントローラークラスを表すアノテーションです。このアノテーションがないと自動検索にも該当しませんので記述忘れのないように。

@RequestMappingアノテーションはクラスとメソッドと双方に記述があります。クラスに記述してあるとURLの接頭語、メソッドに記述してあるとURLの接尾語になります。つまりURL「foo/bar」が呼び出されるとhogehogeMethodが呼び出されます。

戻り値は文字列で"hogehoge"が返却されます。上記のVIEWResolverの設定を利用すると、URL「foo/bar」が呼び出されると「/WEB-INF/view/hogehoge.jsp」が表示されることになります。

FooController.java package foo.bar.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("foo") public class FooController { @RequestMapping("bar") public String hogehogeMethod() { return "hogehoge"; } }
自動スキャンせず手動でマッピング

自動マッピングを行わないためにmvc:annotation-drivenタグを削除します。

mvc:annotation-drivenタグに変わり、id="handlerMapping"でマッピングクラスを指定します。マッピングはvalueタグの値に改行区切りで設定します。

下記例だと「foo/」が呼び出されると「foo.bar.controller.FooController」クラスを呼び出し、「bar/」が呼び出されると「foo.bar.controller.BarController」クラスを呼び出します。

手動マッピングの際のSpring設定ファイル(spring.xml) <context:component-scan base-package="foo.bar.controller" /> <!-- <mvc:annotation-driven /> --> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <value> /foo/**=fooController /bar/**=barController </value> </property> </bean> <bean id="fooController" class="foo.bar.controller.FooController" /> <bean id="barController" class="foo.bar.controller.BarController" />
他のマッピング条件

Springでは、URL以外に、httpメソッドで条件指定したり、特定のパラメータの値がXXXだったらといったように、マッピング条件を絞り込むことができます。RESTFulアプリには欠かせない機能です。

以下は「URLがsearch」「httpメソッドがget」「outputパラメータの値がcsv」といった条件を指定したことになります。

@RequestMappingの他のマッピング条件例 @RequestMapping(value = "search", method = { RequestMethod.GET }, params = "output=csv")
リダイレクト

メソッドで戻り値を返す際、標準的にはVIEWResolverがJSP名を指し示すことになりますので、メソッド呼び出し後にJSPにforwardすることになります。

呼び出し方法によってはforwardでなくredirectしたい場合があります。このような場合、論理名の前に「redirect:」を付加することでリダイレクトすることができます。

下記例では通常「/WEB-INF/view/hogehoge.jsp」にforwardされますが「redirect:」が付加されているため「/WEB-INF/view/hogehoge.jsp」にredirectすることになります。

FooController.java(リダイレクト用に変更) @RequestMapping("bar") public String hogehogeMethod() { return "redirect:hogehoge"; }
マッピングされたメソッドに指定できるパラメータ

上記例では「public String hogehogeMethod()」メソッドが呼び出されましたが、パラメータが何もありません。これではHttpServletRequestのインスタンスを利用したくてもできません。

このようにHttpServletRequestのインスタンスを利用したい場合等、自由にパラメータを利用することができます。以下の表から選択し組み合わせて下さい。

No. クラス名 概要など
1 javax.servlet.http.HttpServletRequest
2 javax.servlet.http.HttpServletResponse
3 javax.servlet.http.HttpSession
4 java.io.InputStream
5 java.util.Locale リクエストのLocale
6 java.io.OutputStream
7 java.security.Principal
8 java.io.Reader
9 java.io.Writer
10 org.springframework.ui.Model

HttpServletRequest.setAttributeやHttpSession.setAttributeに変わるもの。SpringではsetAttributeする際、こちらのクラスを利用する。

Model Example public String hogehogeMethod(Model model, HttpServletRequest request) { String result = "success"; request.setAttribute("result", result); // NG model.addAttribute("result", result); // OK return "hogehoge"; }
11 org.springframework.web.multipart.MultipartFile

ファイルアップロード時にファイルの内容を受け取るためのクラス。

マッピングされたメソッドの戻り値

戻り値は大きく分けて以下の種類があります。以下のうちのいずれかを戻り値として下さい。

No. 戻り値の型 概要など
1 String VIEWResolverへの論理名を文字列で返します。上記のVIEWResolverではJSP名の一部になっています。
2 ModelAndView ModelとView名を持つクラスで、Springで汎用的な戻り値クラスです。
3 void HttpServletResponse等で直接クライアントに処理を返した場合は戻り値をvoidにします。
4 HttpEntity<?>
ResponseEntity<?>
レスポンスヘッダとレスポンスボディをもつクラスで、レスポンスの情報を全て指定できるため、レスポンスを詳細に指定したい場合に利用します。
リクエストパラメータの受け取り方(初期化,受け取らないパラメータ)

リクエストパラメータを受け取るには2つの方法があります。1項目ずつ変数に受け取る方法と、JavaBeansにしてまとめて受け取る方法があります。ここではまとめて受け取る方法を説明します。ここでは「http://servername/appname/api/foo/bar?id=aaaa&password=bbbb」のidとpasswordを受け取ってみます。

まずは受け取るためのJavaBeansを作成します。作成時にはパラメータで受け取りたい変数名のセッターを作成します。

パラメータ受け取り用JavaBeans(IdPass.java) package foo.bar; import java.io.Serializable; @SuppressWarnings("serial") public class IdPass implements Serializable { private String id; private String password; public String getId() { return id; } public String getPassword() { return password; } public void setId(String id) { this.id = id; } public void setPassword(String password) { this.password = password; } }

ここで作成したJavaBeansを利用しパラメータを受け取ります。受け取る際は@ModelAttributeアノテーションを利用します。先ほどのFooControllクラスを以下のように変更します。

まずはパラメータに受け取り用クラスの変数を用意します。具体的には「IdPass idPass」というパラメータを追加します。

この変数の前に@ModelAttributeアノテーションを追加します。するとSpringがIdPassをインスタンス化し、パラメータにidとpasswordが存在するなら自動で設定。その後、hogehogeMethodメソッドを呼び出しidPass変数に前記のインスタンスを渡されます。

FooController.java(変更後) package foo.bar.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("foo") public class FooController { @RequestMapping("bar") public String hogehogeMethod(@ModelAttribute IdPass idPass) { return "hogehoge"; } }

@ModelAttributeアノテーションにはvalue属性を追加することができます。属性値が"abc"の場合「@ModelAttribute("abc")」もしくは「@ModelAttribute(value = "abc")」のように記述します。value属性を設定すると「HttpServletRequest#setAttribute("abc", xxxx)」を行ってくれます。

FooController.java(setAttribute例) public String hogehogeMethod(HttpServletRequest request, @ModelAttribute("abc") IdPass idPass) { Object instance = request.getAttribute("abc"); Assert.assertEquals(ipPass, instance); return "hogehoge"; }
リクエストパラメータの初期化

ログインしているユーザ自身のパスワードを変更する際は、新しいパスワードのみを受け取り、自身のIDはHttpServletRequest#getRemoteUser()やセッション等から取得したい場合があります。

このような場合、@ModelAttributeアノテーションを用いて変数を受け取る以前に、メソッドに@ModelAttributeアノテーションを用いることで、事前にメソッドを呼び出し、値を設定することができます。

メソッド名は自由です。パラメータは「マッピングされたメソッドに指定できるパラメータ」と同様です。戻り値は@ModelAttributeアノテーションを用いて受け取る変数を返さなければなりません。

JavaBeans package foo.bar; import java.io.Serializable; @SuppressWarnings("serial") public class Human implements Serializable { private Boolean admin; private Integer age; private String gender; private String id; private String name; private String password; public Boolean getAdmin() { return admin; } public Integer getAge() { return age; } public String getGender() { return gender; } public String getId() { return id; } public String getName() { return name; } public String getPassword() { return password; } public void setAdmin(Boolean admin) { this.admin = admin; } public void setAge(Integer age) { this.age = age; } public void setGender(String gender) { this.gender = gender; } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } }
Controller package foo.bar; @Controller @RequestMapping("password") public class GroupController extends ProjectController { @ModelAttribute("human") public Object newCommand(HttpServletRequest request) { Human h = new Human(); h.setId(request.getRemoteUser()); return h; } @RequestMapping(value = "change", method = { RequestMethod.POST }) public String changePassword(HttpServletRequest request, @ModelAttribute("human") Human command) { Assert.assertEquals(request.getRemoteUser(), command.getId()); return "human/changed"; } }

インスタンス生成とマッピングされたメソッドで受け取る変数型は継承関係があればどのようなインスタンスも受け取れます。よって以下の様なことも可能です。

JavaBeans package foo.bar; import java.io.Serializable; @SuppressWarnings("serial") public class Human2 extends Human { }
Controller package foo.bar; @Controller @RequestMapping("password") public class GroupController extends ProjectController { @ModelAttribute("human") public Object newCommand(HttpServletRequest request) { return new Human2(); } @RequestMapping(value = "change", method = { RequestMethod.POST }) public String changePassword(HttpServletRequest request, @ModelAttribute("human") Human command) { Assert.assertEquals(Human2.class, command.getClass()); return "human/changed"; } }
受け取らないリクエストパラメータ

上記のようにユーザ自身のパスワードを変更の初期値を設定することができました。

ですが初期値設定後にリクエストからインスタンスに値のバインドが行われるため、初期値が上書きされてしまう可能性があります。

このような場合にリクエストからバインドする変数を指定(WebDataBinder#setAllowedFields)、バインドしない変数を指定(WebDataBinder#setDisallowedFields)する方法があります。

以下の実装に対し「http://servername/appname/password/change?id=hoge」を行ってもidの値(hoge)は受け取りません。

Controller package foo.bar; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; @Controller @RequestMapping("password") public class GroupController extends ProjectController { @InitBinder public void initBinder(WebDataBinder binder) { binder.setDisallowedFields("id"); } @RequestMapping(value = "change", method = { RequestMethod.POST }) public String changePassword(@ModelAttribute("human") Human command) { Assert.assertNull(command.getId()); return "human/changed"; } }
@InitBinder等の呼出順序

パラメータを受け取る際に以下のことを学びました。動作する順に記述してあります。

パラメータ受取時の動作順
Order 名称 解説など
1 Method @ModelAttribute 受け取るべき変数をインスタンス化する。
2 @InitBinder 変数にバインドする、しない等の方針を決定する。
3 Parameter @ModelAttribute インスタンス化されバインド後の値を受け取り処理を行う。
受け取ったリクエストパラメータをセッションで管理

@ModelAttributeアノテーションにvalue属性を追加することで「HttpServletRequest#setAttribute("abc", xxxx)」を行ってくれますが、「HttpSession#setAttribute("abc", xxxx)」を行う方法はあるのでしょうか。「HttpSession#setAttribute("abc", xxxx)」を行うにはクラスに対しアノテーションを付与します。

FooController.java(セッション用に変更) package foo.bar.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("foo") @ModelAttribute("abc") public class FooController { @RequestMapping("bar") public String hogehogeMethod(HttpSession session, @ModelAttribute("abc") IdPass idPass) { Object instance = session.getAttribute("abc"); Assert.assertEquals(ipPass, instance); return "hogehoge"; } }
受け取ったリクエストパラメータを精査する

Spring MVC では「JSR303 Bean Validation」を利用します。「JSR303 Bean Validation」については、ここで解説はしません。

精査を行うためには@ModelAttributeアノテーションの前に@Validアノテーションを付加します。精査結果は「org.springframework.validation.Errors」クラスで受け取ります。@Validアノテーションの変数とErrorsクラスの変数のパラメータは、@Validアノテーションの変数の次にErrorsクラスの変数が並ぶよう、変数が連続していなければなりません。

FooController.java(精査用に変更) package foo.bar.controller; import org.springframework.stereotype.Controller; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("foo") public class FooController { @RequestMapping("bar") public String hogehogeMethod(@Valid @ModelAttribute("abc") IdPass idPass, Errors errros) { if (errors.hasErrors()) { return "redirect:error"; } return "hogehoge"; } }

また「Errors」クラスには以下のAPIがあります。以下が全てではありませんので、必要に応じて利用して下さい。

Errors API
No. return signature 解説など
1 boolean hasErrors

精査の結果、エラーの有無を取得。

2 List<ObjectError> getAllErrors()

全てのエラー情報を取得する。

3 void reject(String errorCode)
reject(String errorCode, Object[] errorArgs, String defaultMessage)
reject(String errorCode, String defaultMessage)

BeanValidationとは別に、エラーを追加する(INSERT Duplicate等)。

message.properties required.error={0} is required.
Java Example errors.reject("required.error", new Object[]{ "ID" }, "ID is required.")
4 void rejectValue(String field, String errorCode)
rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage)
rejectValue(String field, String errorCode, String defaultMessage)

rejectに同じだが、こちらは特定のフィールド(第1パラメータ)にエラーを関連付けすることができる。

Java Example errors.rejectValue("id", "required.error", new Object[]{ "ID" }, "ID is required.")

また「ObjectError」クラスには以下のAPIがあります。以下が全てではありませんので、必要に応じて利用して下さい。

ObjectError API
No. return signature 解説など
1 String getCode()
Java Example errors.reject("required.error", new Object[]{ "ID" }, "ID is required.") ObjectError e = errors.get(0); Assert.assertEquals("required.error", e.getCode());
2 String getDefaultMessage()
Java Example errors.reject("required.error", new Object[]{ "ID" }, "ID is required.") ObjectError e = errors.get(0); Assert.assertEquals("ID is required.", e.getDefaultMessage());
3 String getObjectName()
Java Example public String hogehogeMethod( @Valid @ModelAttribute("abc") IdPass idPass , Errors errros) { ObjectError e = errors.get(0); Assert.assertEquals("abc", e.getObjectName()); return "hogehoge"; }
4 Object[] getArguments()
Java Example errors.reject("required.error", new Object[]{ "ID" }, "ID is required.") ObjectError e = errors.get(0); Object[] objs = e.getArguments(); Assert.assertEquals(1, objs.length); Assert.assertEquals("ID", objs[0]);
Springで利用するJSPタグ

Springでは主に以下のタグライブラリを利用します。

No. prefix uri 解説など
1 c http://java.sun.com/jsp/jstl/core
2 fmt http://java.sun.com/jsp/jstl/fmt
3 form http://www.springframework.org/tags/form 主にFormタグに関連するSpringタグ。
4 spring http://www.springframework.org/tags 上記に該当しないSpringタグ。

宣言は以下になっており、Spring固有のタグライブラリはformタグとspringタグになります。

タグライブラリを利用するための宣言 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

では具体的にJSPにて表示してみます。「http://servername/appname/humanInfo/readByKey?id=hoge」を以下を用いて表示します。

JavaBeans package foo.bar; import java.io.Serializable; @SuppressWarnings("serial") public class Human implements Serializable { private Boolean admin; private Integer age; private String gender; private String id; private String name; private String password; public Boolean getAdmin() { return admin; } public Integer getAge() { return age; } public String getGender() { return gender; } public String getId() { return id; } public String getName() { return name; } public String getPassword() { return password; } public void setAdmin(Boolean admin) { this.admin = admin; } public void setAge(Integer age) { this.age = age; } public void setGender(String gender) { this.gender = gender; } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } }
Controller package foo.bar; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("humanInfo") public class FooController { @RequestMapping("readByKey") public String readByKey(Model model, @ModelAttribute("human") Human human) { // 実際にはDB等から取得する human.setName("ab"); human.setAge(34); human.setPassword("ef"); human.setAdmin(true); human.setHobby(new String[] { "tv", "drive" }); human.setGender("1"); Map<String, String> genders = new LinkedHashMap<String, String>(); genders.put("0", "unknown"); genders.put("1", "male"); genders.put("2", "female"); model.addAttribute("genderMap", genders); Map<String, String> hobbies = new LinkedHashMap<String, String>(); hobbies.put("baseball", "PlayBaseball"); hobbies.put("drive", "DriveCar"); hobbies.put("tv", "WatchTV"); model.addAttribute("hobbyMap", hobbies); return "readByKey"; } }
readByKey.jsp <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <form:errors /> <form:form modelAttribute="human" action="update" method="post"> <table border> <tr> <td>ID</td> <td><c:out value="${human.id}" /></td> </tr> <tr> <td>Name</td> <td><form:input path="name" /></td> </tr> <tr> <td>Age</td> <td><form:input path="age" /></td> </tr> <tr> <td>Password</td> <td><form:password path="password" /></td> </tr> <tr> <td>Admin</td> <td><form:checkbox path="admin" label="Adminstrator" /></td> </tr> <tr> <td rowspan="2">Gender</td> <td><form:radiobuttons path="gender" items="${genderMap}" /></td> </tr> <tr> <td><form:select path="gender" items="${genderMap}" /></td> </tr> <tr> <td rowspan="2">Hobby</td> <td><form:checkboxes path="hobby" items="${hobbyMap}" /></td> </tr> <tr> <td><form:select path="hobby" items="${hobbyMap}" multiple="true"/></td> </tr> </table> </form:form> <form:radiobuttons path="human.gender" items="${genderMap}" /> <%-- form:formタグの外側 --%>
Springで利用するJSPタグ
No. タグ 使用法等
1 c:out

変数の値をテキストとして出力する。

2 fmt:message

プロパティファイルの内容を出力する。

spring:messageタグは存在するのですが、fmt:messageタグを使うようにして下さい。

3 form:input

「input type=text」タグを生成する。

4 form:hidden

「input type=hidden」タグを生成する。

5 form:password

「input type=password」タグを生成する。

6 form:checkbox

プロパティ変数がBoolean型の「input type=checkbox」タグを生成する。

7 form:checkboxes

プロパティ変数が配列型の「input type=checkbox」タグを生成する。

またdelimiter属性を設定すると各checkboxの間にデリミタを挿入することができる。

8 form:radiobuttons

「input type=radio」タグを生成する。

form:checkboxesタグ同様にdelimiter属性を設定できる。

9 form:select

selectタグ、optionタグを生成する。

10 form:form

formタグを出力する。

modelAttribute属性を指定するとformタグの内側にあるform:XXXタグはgetAttribute名を省略できる。

11 form:errors

@Controllerにて行った@Validの結果を表示する。

JSPタグでエラーを表示

<form:errors />と単純に記述してしまうと全てのエラーが同一箇所に表示されてしまいます。項目ごとにエラーを表示するには以下のように記述することでエラーを個別表示することができます。

また自作エラーの場合、rejectでなくrejectValueの第1パラメータfieldに設定した値と、path属性の値が一致しているものも表示されます。

エラーを個別表示する <%-- プロパティ「name」のエラーのみを表示 --%> <form:errors path="name" />

エラーに関係なくform:XXXX全般ですがcssStyle属性が存在します。htmlにstyle属性に出力します。

cssStyle属性(jsp) <form:errors path="name" /><br/> <form:errors path="name" cssStyle="color:red" />
style属性(html) <input id="name" name="name" type="text" /><br/> <span id="name.errors" style="color:red">XXXXXXXX</span>
国際化対応(ロケールをセッションで管理)

プロパティファイルの言語を切り替える方法がいくつかありますが、ここでは指定された言語をセッションで管理する方法を紹介します。

セッションで管理するには「SessionLocaleResolver」クラスを利用します。「SessionLocaleResolver」クラスのプロパティとして「defaultLocale」プロパティを設定します。指定しない場合はVMのデフォルトロケールになります。

このセッションで管理しているロケールを変更するために「LocaleChangeInterceptor」クラスを利用します。「LocaleChangeInterceptor」クラスのプロパティとして「paramName」プロパティを設定します。ここではvalueに「locale」と指定しました。パラメータ名がlocaleという意味になります。

ロケールを変更する際にはURLに?locale=enのようにパラメータを指定することでロケールを変更できます。

国際化対応(spring.xml) <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd "> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"> <property name="defaultLocale" value="ja" /> </bean> <mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> <property name="paramName" value="locale" /> </bean> </mvc:interceptors> </beans>
ファイルアップロード

ファイルアップロードを行うためには「MultipartFile」クラス、@RequestParamアノテーションを利用します。

uploadするためのhtml <form action="upload" method="POST" enctype="multipart/form-data"> <input type="file" name="uploadFile" /> <input type="submit" value="upload" /> </form>
Controller package foo.bar; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; @Controller @RequestMapping("humanInfo") public class FooController { @RequestMapping(value = "upload", method = { RequestMethod.POST }) public String upload(@RequestParam("uploadFile") MultipartFile multi) { System.out.println(multi.getOriginalFilename()); System.out.println(multi.getSize()); System.out.println(multi.getContentType()); File tmp = File.createTempFile("humanInfo", ".txt");; try { multi.transferTo(tmp); } catch (IOException e) { throw new IllegalStateException(e); } return "uploaded"; } }

受け取るメソッドのパラメータに「MultipartFile」クラスのパラメータを含めます。この際、このパラメータの前にアノテーションで@RequestParamを含めます。@RequestParamのvalue属性にはアップロードする際のname属性の値を記述します。

「MultipartFile」クラスにtransferToメソッドがあります。このメソッドを実行するとアップロードされたファイルを保存することができます。

また「MultipartFile」クラスには以下のAPIがあります。以下が全てではありませんので、必要に応じて利用して下さい。

MultipartFile API
No. return signature 解説など
1 String getContentType()

アップロードされたファイルのContentTypeを取得します。

2 String getOriginalFilename()

アップロードされたファイルのブラウザにて指定されたファイル名を取得します。

3 long getSize()

アップロードされたファイルのファイルサイズ(byte)を取得します。

4 Object[] transferTo(File)

第1パラメータのインスタンスにアップロードされたファイルの内容を転送します。

またSpringMVCにて「multipart」ファイルを扱うには以下の設定を追加しなければなりません。

File Upload 設定ファイル(spring.xml) <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" /> </beans>
ファイルダウンロード

ファイルをダウンロードさせるには「ResponseEntity」クラスを利用します。

「ResponseEntity」クラスのコンストラクタは第1パラメータにファイルの内容、第2パラメータにヘッダ情報、第3パラメータにHttpStatusコードを指定します。

Controller package foo.bar; import java.nio.charset.Charset; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("humanInfo") public class FooController { @RequestMapping(value = "find", method = { RequestMethod.GET }, params = "download") public ResponseEntity<String> download() { String contents = "a1" + "\t" + "b1" + "\tc1" + "\n" + "a2" + "\t" + "b2" + "\t" + "c2" + "\n" + "a3" + "\t" + "b3" + "\t" + "c3"; HttpHeaders h = new HttpHeaders(); h.setContentType(new MediaType("text", "tsv", Charset.forName("UTF-8"))); String disposition = "attachment; filename=\"" + filename + "\""; h.set("Content-Disposition", disposition); return new ResponseEntity<String>(contents, h, HttpStatus.OK); } }