Spring Framework 3 を使う
戻る- 動作環境
- 「Spring Framework」の主な機能
- Springを用いたインスタンス生成
- DI(Dependency Injection)
- 設定ファイルに記述できるその他のインスタンス
- AOP(Aspect Oriented Programming)
- AOP JDBC Transaction
- Spring MVC
- TIPS
このページでは以下の環境での動作を説明しています。
- 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)にて用意されている、主な機能を紹介します。
- 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を利用したアプリケーションでは、new演算子を用いずインスタンス生成をSpringに委譲します。
Springでは設定ファイルを用いインスタンス生成を行います。以下は設定ファイル(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-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ソースから呼び出してみます。
package foo.bar;
public class Abc {
public String toString() {
return "!!!Abc!!!";
}
}
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メソッドも参考として記述しておきました。どちらも同じ動作を行います。
従来の実装では、上記のsameメソッドのように、new演算子を用いるため、キャスト変換は不要です。ですが上記の「getBean」は戻り値がObject型なため毎回キャストするのはとても時間を要します。
このような場合のために「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の利用法の場合、以下の状況ではどうなるでしょうか。
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に問い合わせます。
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);
どのようなインスタンスが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属性が記述されていれば見落とされる確率は大いに減るでしょう。
このように一工夫するだけでバグが減少する場合もあるとおもいます。たかが順番ですが、順を工夫し記述して下さい。
Springではインスタンスを生成の際、あらかじめセッター(setter injection)を呼び出すことができます。同様にパラメータ付きコンストラクタ(constractor injection)を呼び出すことができます。
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; }
}
<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>
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の際、パラメータが異なる同名メソッドが複数存在する場合等、型を指定しなければならない場合、どのパラメータのメソッドを呼び出すか指定するため、type属性にてパラメータの型を指定することができます。
<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属性を指定します。
Injectionの際、自作クラスをDIすることも可能です。propertyタグの値にbeanタグを含めて下さい。詳細は以下を参考にしてください。
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;
}
}
<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>
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());
上記で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型の場合、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になります。
<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>
bean定義毎に同じ設定を行うことは時間がかかりますし、設定忘れの原因になりかねません。Springではbean定義を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();
}
}
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();
}
}
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属性を利用することで設定を引き継ぐことができます。
<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属性を利用することで設定ができるようになります。
<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属性を省略することが可能です。
<bean id="ex4" parent="ex3">
<property name="name" value="DDD" />
</bean>
下記は検証結果になります。
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());
性別(1=男性,2=女性)といったようなコードを扱う場合、Map型変数に値を格納しておくべきである。このような場合、設定ファイルはどのようになるでしょうか。
<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」を用い以下のように記述します。
<?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タグと記述方法は同一です。
以下を利用し動作確認をしてみます。
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 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>
Javaにて既に用意されている定数をインスタンス化することができます。
package jp.co.mclnet;
public interface Abc {
public int READONLY = 1;
}
<?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クラスに代表されるプロパティファイルと同様なため特に解説は行いません。
login.fail={0} or {1} is incorrect.
<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"」のようにカンマ区切りで複数ファイルを指定することもできます。
では実際に利用してみます。
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は予約語)。
「サービス開始日」といった情報を、あらかじめ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));
factory-method属性がパラメータなしであれば上記の方法でよいのですが、MessageDigestクラスのようにgetInstanceにパラメータを含めたいような場合は、以下のように行います。
<property name="messageDigest">
<bean class="java.security.MessageDigest" factory-method="getInstance">
<constructor-arg value="MD5" />
</bean>
</property>
データベースに接続する際、Tomcatアプリケーション(Servletアプリケーション)では、以下の様な設定で、TomcatにConnectionをPoolさせ、利用することが多いと思います。
<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」を利用した設定例を記述します。
<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>
ApplicationContext ac =
new ClassPathXmlApplicationContext("spring.xml");
DataSource ds = ac.getBean("dataSource", DataSource.class);
Connection conn = ds.getConnection();
.....
.....
.....
100% Pure Java で実装されているHSQLDB。プロトタイプ作成時や、高速のメモリDBとして使用することもあるかと思います。こちらを利用する際は以下を参考に設定して下さい。
<?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>
SpringでのValidationは「JSR303 Bean Validation」の定義(jar)を利用する前提となっています(ここで「Bean Validation」については解説しません)。
Validationを行うには「javax.validation.Validator」のインスタンスが必要です。そのインスタンスをSpringに管理させます。MessageSourceが「id="messageSource"」であったように、Validationでは「id="validator"」にしなければなりません(validatorは予約語)。
設定は以下のようにします。このときValidationにて精査エラーになった場合のエラーメッセージのファイルをプロパティ名validationMessageSourceにて指定することができます。
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource" />
</bean>
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>
<?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>
<?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>
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される側とする側のクラスです。
package foo.bar;
public interface Logic {
public Object execute(Object instance);
}
package foo.bar;
public class LogicImpl implements Logic {
public Object execute(Object instance) {
return instance.toString();
}
}
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属性を指定することもできます。
<?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回呼び出されます。
ApplicationContext ac =
new ClassPathXmlApplicationContext("simple.xml");
Logic l = ac.getBean("foo.bar.Logic", Logic.class);
l.execute(l);
今回例ではaop:configタグのproxy-target-class属性を"true"としました(default=false)。この設定がfalseだとどのように動作するのでしょうか。
上記のlに対し以下のコードを実行します。するとtrueの場合は「instanceof LogicImpl」がtrue、逆はfalseとなります。
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ではインターフェースを強要する実装が推奨されているのかとおもいます。
ポイントカットの指定にはいくつかの方法があります。ここでは簡単に説明します。詳細はSpringのページにて確認下さい。
「メソッドの呼び出しに対してAOPする」ための「execution」を説明します。executionの書式は以下になります。
execution(modifiers? ret-type declaring-type? name(param) throws?)
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:aroundタグを利用しましたが、他にはどのようなものがあるのでしょうか。
No. | 英名 | 解説 |
---|---|---|
1 | aop:before | ジョインポイントの前に実行 |
2 | aop:after | ジョインポイントの後に実行 |
3 | aop:after-returning | ジョインポイントが完全に正常終了後に実行 |
4 | aop:around | ジョインポイントの前後で実行 |
5 | aop:after-throwing | ジョインポイントで例外発生時に実行 |
* ジョインポイントとはAOPが発生した条件のこと
AOPを使って何がうれしいの?ログを出力する以外に何かあるの?と質問されることがあります。AOPというより横断的関心事ということの解説になるのですが、ログ出力以外に、サーブレットのFilterを用いて「ログイン認証/権限管理」を行うことも横断的関心事です。
話は戻りまして、せっかくAOPというメソッド実行時に割り込ませる技術が利用できるのですから、トランザクション管理(コネクションの取得、トランザクション開始、コミット、ロールバック)に利用しよう、というのがこの章の内容になります。
AOPにてトランザクション管理を行うと、開発者がコネクションの取得、開始、コミット、ロールバックという作業から開放され、開発工数が減少する傾向にあります。
またフレームワークでトランザクション管理を行うということは、常にトランザクション管理が行われるようになるので、開発者がトランザクション管理を忘れたとしても、自動的にフレームワーク配下のトランザクション管理が行われることになります。
トランザクション管理としてはJTA,DataSource等、多数対応しています。今回はDataSourceでのトランザクション管理を行ってみます。
<?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を行うこととなります。
では具体的なクラスを見てみます。
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 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 は DIを各所に用いたフレームワークになります。
従来のWEBアプリではweb.xmlに代表する各種設定ファイルを記述するのが重労働で、デグレードの原因にもなります。しかしアノテーションによるマッピング機能等で、最低限の設定ファイルの設定のみで実装することもできます。
またアノテーションによるマッピングを用いることで、マッピングされたメソッドを呼び出す場合、メソッド名も固定されておらず、戻り値、パラメータも自由に設計できます。アノテーションでマッピングすることで特定のクラスを継承する必要もなく、JavaBeansのようなPOJOのDTOクラスも利用することができます。
いま流行りのRESTfulアプリケーションを作成し易い機能も所有しています。
従来のWEBアプリでは「http://servername/appname/api/foo/bar」をマッピングするには以下のように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>
<?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>
<?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はほとんど同じようになっています。
アノテーションによる自動マッピング機能を使います。
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」が表示されます。
<?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で利用するクラスを作成します。これは「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」が表示されることになります。
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」クラスを呼び出します。
<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(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することになります。
@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を作成します。作成時にはパラメータで受け取りたい変数名のセッターを作成します。
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変数に前記のインスタンスを渡されます。
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)」を行ってくれます。
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アノテーションを用いて受け取る変数を返さなければなりません。
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; }
}
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";
}
}
インスタンス生成とマッピングされたメソッドで受け取る変数型は継承関係があればどのようなインスタンスも受け取れます。よって以下の様なことも可能です。
package foo.bar;
import java.io.Serializable;
@SuppressWarnings("serial")
public class Human2 extends Human {
}
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)は受け取りません。
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";
}
}
パラメータを受け取る際に以下のことを学びました。動作する順に記述してあります。
Order | 名称 | 解説など |
---|---|---|
1 | Method @ModelAttribute | 受け取るべき変数をインスタンス化する。 |
2 | @InitBinder | 変数にバインドする、しない等の方針を決定する。 |
3 | Parameter @ModelAttribute | インスタンス化されバインド後の値を受け取り処理を行う。 |
@ModelAttributeアノテーションにvalue属性を追加することで「HttpServletRequest#setAttribute("abc", xxxx)」を行ってくれますが、「HttpSession#setAttribute("abc", xxxx)」を行う方法はあるのでしょうか。「HttpSession#setAttribute("abc", xxxx)」を行うにはクラスに対しアノテーションを付与します。
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クラスの変数が並ぶよう、変数が連続していなければなりません。
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があります。以下が全てではありませんので、必要に応じて利用して下さい。
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があります。以下が全てではありませんので、必要に応じて利用して下さい。
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では主に以下のタグライブラリを利用します。
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」を以下を用いて表示します。
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; }
}
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";
}
}
<%@ 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タグの外側 --%>
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の結果を表示する。 |
<form:errors />と単純に記述してしまうと全てのエラーが同一箇所に表示されてしまいます。項目ごとにエラーを表示するには以下のように記述することでエラーを個別表示することができます。
また自作エラーの場合、rejectでなくrejectValueの第1パラメータfieldに設定した値と、path属性の値が一致しているものも表示されます。
<%-- プロパティ「name」のエラーのみを表示 --%>
<form:errors path="name" />
エラーに関係なくform:XXXX全般ですがcssStyle属性が存在します。htmlにstyle属性に出力します。
<form:errors path="name" /><br/>
<form:errors path="name" cssStyle="color:red" />
<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のようにパラメータを指定することでロケールを変更できます。
<?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アノテーションを利用します。
<form action="upload" method="POST" enctype="multipart/form-data">
<input type="file" name="uploadFile" />
<input type="submit" value="upload" />
</form>
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があります。以下が全てではありませんので、必要に応じて利用して下さい。
No. | return | signature | 解説など |
---|---|---|---|
1 | String | getContentType() | アップロードされたファイルのContentTypeを取得します。 |
2 | String | getOriginalFilename() | アップロードされたファイルのブラウザにて指定されたファイル名を取得します。 |
3 | long | getSize() | アップロードされたファイルのファイルサイズ(byte)を取得します。 |
4 | Object[] | transferTo(File) | 第1パラメータのインスタンスにアップロードされたファイルの内容を転送します。 |
またSpringMVCにて「multipart」ファイルを扱うには以下の設定を追加しなければなりません。
<?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コードを指定します。
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);
}
}