ひよっこ。

I want to…

Posts Tagged ‘Spring’

SpringBatchで実行可能なjarファイルを作ったらSAXParseExceptionではまるなど

Posted by hikaruworld : 2011 6月 25

SpringBatchを使ってバッチプログラムを書いて、実行可能なjarファイルを作り、
おもむろにjava -jarで起動したら表題のごとくSAXParseExceptionではまりました。

発生した例外のスタックトレースは以下の通り。

2011-06-25 16:21:08,829 ERROR [org.springframework.batch.core.launch.support.CommandLineJobRunner] - 
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 5 in XML document from class path resource [launch-context.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'.
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:396)
        ....
        ....
        (以下略)

色々ググってみたり、リファレンスを読んでいたのですが、
Springコミュニティでも話題になっている割には解決策が見つからずorz…
すると、あるブログで同様の件が発生していて、
その解決策を書いている人がいました。

根本的な原因は以下に記されていることに起因するようです。
参照されるjarファイルのMETA-INF内が参照されないというように読み取れました。

In fact, the root cause is that the META-INF directory of a can be blocked when referenced inside an EJB jar.

SAXParseExceptionが出ている直接の原因は、定義ファイルlaunch-context.xmlで指定されている
ルートタグbeanで定義されている名前空間、http://www.springframework.org/schema/beansが見つからないことです。

<beans xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    ....

これは、xsi:schemaLocationが定義されていればSpringはクラスパス上のMETA-INF内部に定義されている
spring.handlersに書かれている名前空間から解決してくれるようですが、
上記の理由により依存ライブラリのMETA-INFが参照できず、XMLの解析に失敗してしまいます。

# ここはSpring2.0系ですが、InfoQのこの辺りの記事が参考になると思います。

具体的な回避策

先ほどのサイトに書いてあった通りです。
実行対象のjarファイル内のMETA-INF内に、XMLファイルが参照する名前空間を『全て』定義した状態の
マージしたspring.schemasとMETA-INF/spring.handlersのファイルを同梱する必要があります。
必要なxmlの名前空間定義は、各spring-xxx.jar内に含まれていますのでそれをマージします。

大体階層的にはこんな感じになります。

batch-sample.jar
├── META-INF
│   ├── INDEX.LIST
│   ├── MANIFEST.MF
│   ├── maven
│   │   └── org.springframework.samples.batch
│   │       └── spring-batch-simple
│   │           ├── pom.properties
│   │           └── pom.xml
│   ├── spring
│   │   └── module-context.xml
│   ├── spring.schemas
│   └── spring.handlers
├── batch.properties
├── empty.txt
├── launch-context.xml  

なお、自分の環境だとネットワークがつながっていなかったのですが、つながっている場合はもうちょっとわかりやすいエラーになるようです。
会わせてエラーログを張っておきます。

自分がでたエラーログ

2011-06-25 16:21:08,829 ERROR [org.springframework.batch.core.launch.support.CommandLineJobRunner] - <Job Terminated in error: Line 5 in XML document from class path resource [launch-context.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'.>
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 5 in XML document from class path resource [launch-context.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'.
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:396)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:212)
        at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:126)
        at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:92)
        at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130)
        at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:465)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:395)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
        at org.springframework.batch.core.launch.support.CommandLineJobRunner.start(CommandLineJobRunner.java:272)
        at org.springframework.batch.core.launch.support.CommandLineJobRunner.main(CommandLineJobRunner.java:541)
Caused by: org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'.
        at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195)
        at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:131)
        at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:384)
        at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1916)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:705)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:400)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDriver.scanRootElementHook(XMLNSDocumentScannerImpl.java:626)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3103)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:922)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:235)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:284)
        at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:75)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:388)
        ... 15 more

ネットワークにつながる環境の場合

2011-06-25 16:29:11,590 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@12b7eea: startup date [Sat Jun 25 16:29:11 JST 2011]; root of context hierarchy>
2011-06-25 16:29:11,689 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [launch-context.xml]>
2011-06-25 16:29:13,462 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/module-context.xml]>
2011-06-25 16:29:14,149 ERROR [org.springframework.batch.core.launch.support.CommandLineJobRunner] - <Job Terminated in error: Configuration problem: Failed to import bean definitions from URL location [classpath:/META-INF/spring/module-context.xml]
Offending resource: class path resource [launch-context.xml]; nested exception is org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/batch]
Offending resource: class path resource [META-INF/spring/module-context.xml]
>
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to import bean definitions from URL location [classpath:/META-INF/spring/module-context.xml]
Offending resource: class path resource [launch-context.xml]; nested exception is org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/batch]
Offending resource: class path resource [META-INF/spring/module-context.xml]

        at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68)
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:76)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource(DefaultBeanDefinitionDocumentReader.java:193)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(DefaultBeanDefinitionDocumentReader.java:148)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:133)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:93)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:212)
        at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:126)
        at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:92)
        at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130)
        at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:465)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:395)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
        at org.springframework.batch.core.launch.support.CommandLineJobRunner.start(CommandLineJobRunner.java:272)
        at org.springframework.batch.core.launch.support.CommandLineJobRunner.main(CommandLineJobRunner.java:541)
Caused by: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/batch]
Offending resource: class path resource [META-INF/spring/module-context.xml]

        at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68)
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:80)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:284)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1332)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1325)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:136)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:93)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource(DefaultBeanDefinitionDocumentReader.java:187)
        ... 20 more

以上です。

広告

Posted in program | タグ: , , , , | Leave a Comment »

AMF48が便利だったのでSpringFramework3経由でもっと楽してみた

Posted by hikaruworld : 2011 4月 25

先日のfxug@北陸in富山で、わっきーさんがAMF通信用の軽量ライブラリとして、
AMF48を作ったよという紹介をしてくれていました。
某BlazeDSとかに比べてずいぶんお手軽そうだったので、Spring経由でもっと楽しようと思って遊んでいました。
(ちゃんとJava5対応してて、色々BlazeDS側で対応されてない部分も対応してくれてるらしい)

なお、AMF48(えーえむえふふぉーてぃーえいと)の使い方は公式サイトを参照ください。

なぜSpring経由で?

SpringMVCを利用すると、簡単にデータのバインディングを行う事が出来ます。
つまりHTTPリクエストをBeanにバインドしたり、
Beanの内容をHTTPリクエストに変換してViewに送出する事が出来ます。

Springの便利な部分は、これらのバインディグを様々なものに変換する事が簡単に出来る事です。
Spring自身によって提供されている形式としては、JSONや、XML、RSS、それだけはなくPDFやExcelにも対応していたはずです。
これらの実装はHttpMessageConverterを拡張されてそのコンバータが提供されていますので、
これを拡張する形でAMFに対応したものを構築できるはずです。

AMF48は便利なのですが、自力でdecode/encodeする必要があってちょっぴりめんどいので、
その部分はSpringさんに任せてしまえという思惑。

HttpMessageConverterを拡張する

JSONのコンバータとして提供されている、MappingJacksonHttpMessageConverterが
同じような事をやっているので参考になります。

実装するのは、HttpMessageConverterを実装している抽象クラス、AbstractHttpMessageConverterです。
とりあえずソースをば。詳細はコメントを見てください。
なお、細かい例外処理などは省いていますのであしからず。

public class AMFHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

	public AMFHttpMessageConverter() {
		// 対応するメディアタイプを指定します。AMFの場合はapplicaiton/x-amf
		super(new MediaType("application", "x-amf", DEFAULT_CHARSET));
	}

	@Override
	public boolean canRead(Class<?> clazz, MediaType mediaType) {
		// 読み込み(AMF -> オブジェクト形式)が読み込み可能な場合trueを返します
		// とりあえずMediaTypeで判断。
		return checkMediaTypeAMF(mediaType);
	}

	@Override
	public boolean canWrite(Class<?> clazz, MediaType mediaType) {
		// 書き込み(オブジェクト -> AMF)が書き込み可能な場合trueを返します。
		// とりあえずMediaTypeで判断。
		return checkMediaTypeAMF(mediaType);
	}

	@Override
	protected boolean supports(Class<?> clazz) {
		// 利用禁止。canRead/canWriteを参照するため。
		throw new UnsupportedOperationException();
	}

	@Override
	protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
		// AMF48を利用してdecode
		return AmfUtil.decode(inputMessage.getBody(), clazz);
	}

	@Override
	protected void writeInternal(Object o, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {
		// AMF48を利用してencode
		outputMessage.getBody().write(AmfUtil.encode(o));
	}

	private boolean checkMediaTypeAMF(MediaType mediaType) {
		return "application".equals(mediaType.getType())
				&& "x-amf".equals(mediaType.getSubtype());
	}
}

ちなみにこいつらは、org.springframework.web.bind.annotation.support.HandlerMethodInvoker
から呼び出されています。興味あればどうぞ。

AnnotationMethodHandlerAdapterの設定

作成したAMFHttpMessageConverterをDI上にロードします。
これらのConverterはAnnotationMethodHandlerAdapterで読み込まれるので、
このクラスのプロパティにインジェクションします。設定ファイルはこんな感じです。

<beans:bean id="annotationMethodHandlerAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <beans:property name="messageConverters">
	<beans:array>
		<beans:bean class="com.wordpress.prepro.AMFHttpMessageConverter"></beans:bean>
	</beans:array>
  </beans:property>
</beans:bean>

なお、上記のソースではAMFHttpMessageConverterのみを設定していますが、
通常はコンストラクタで設定されている

this.messageConverters = new HttpMessageConverter[]{
		new ByteArrayHttpMessageConverter(),
		stringHttpMessageConverter,
		new SourceHttpMessageConverter(),
		new XmlAwareFormHttpMessageConverter()
};

あたりも設定しておかないときっと困るでしょう〜(通常のバインディング系が動かないはず)。
また、この設定は

<annotation-driven />

よりも先に設定する必要があります(最初これではまりましたorz…)。

Controllerへの設定

そして、本題。
実際に使う場合の話。Controllerに設定を埋め込みます。

クライアントからAMF通信を受け取る場合

@RequestBodyを使います。
@RequestBodyを使うと設定したオブジェクトに対して、HTTPリクエスト本体を渡します。
この渡されたHTTP情報を元に、対応するHttpMessageConverterが処理されます。
ソースはこんな感じになります。

// HttpリクエストがhelloCommandに変換されて設定されます
@RequestMapping(value="/amf", method=RequestMethod.POST, headers="Accept=application/x-amf")
public Result home(@RequestBody HelloCommand helloCommand) throws IOException {
	//....
}

サーバからAMF形式で送る場合

@ResponseBodyを使います。
@ResponseBodyを設定すると、オブジェクトをHttpレスポンスに変換する際にHttpMessagConverterによって処理されます。

@RequestMapping(value="/amf", method=RequestMethod.POST)
@ResponseBody
public Result home(@RequestBody HelloCommand helloCommand) throws IOException {
	Result result = new Result();
	result.message = "hoge";
	return result;
	// この後resultがHTTPレスポンスに変換されます。
}

おまけ

TODO 後でソースコードはlaunchpadにUpします。

以上です。
AMF48いいですね。使いやすい感じです。

Posted in program | タグ: , , , | Leave a Comment »

Spring3の@MVCの@InitBinderの挙動に関して

Posted by hikaruworld : 2011 2月 9

Springの@Controllerにはクライアントから渡された値を任意の型に変換する、
@InitBinderというアノテーションがあります。
Spring1.Xから知っている人にはおなじみのInitBinderそのものです。

簡単にいうと、クライアント側で

2/12

という値を設定してデータが送られた場合、Date型に変換したり、
サーバからクライアントに値を送る際にDate型をDateFormatで
任意の表示形式に変換したりできます。

今回やりたかったのは、このInitBinderの値を特定のフィールド、ないしはパラメータにのみ適用する方法です。
@InitBinderには引数に値を設定出来、おそらくこの値を設定すれば良いのは分かるのですが、
この英語の説明がパッと分からなかったので、ソースを読んでいました。

実際に実行処理が行われているのは、HandlerMethodInvoker.initBinderの部分で以下のようになっています。

String[] targetNames = AnnotationUtils.findAnnotation(initBinderMethod, InitBinder.class).value();
if (targetNames.length == 0 || Arrays.asList(targetNames).contains(attrName)) {
	//ここで実処理
	...

気になるのはattrNameとtargetNamesの部分。
targetNamesはInitBinderのアノテーションで指定可能なvalue値の部分で、
何も設定していない場合は、そのまま全部のデータに対して処理が行われるようです(targetNames.length == 0)。

じゃあattrNameに何が渡されるかというのが今回の興味。

POJOを@RequestMappingの情報に渡した場合は、そのオブジェクトの値が渡されます。
つまり、以下のような設定をしていた場合、

@RequestMapping(value = "/", method = RequestMethod.GET)
public String hogehoge(Piyo piyo) {
  //...
}

attrNameにはpiyoが渡されます。
この変換ロジックはSpringのドキュメントのどこかに書いてあった気がしますが、
忘れたので割愛。

次に、@PathVariableを設定した場合はその変数名が渡されます。
こういうケースですね。この例ではabcが渡されます。

@RequestMapping(value="/{abc}", method=RequestMethod.GET)
public String home(@PathVariable("abc") int def) {
  //...
}

次に@RequestParamを利用したケースです。

@RequestMapping(value="/ab", method=RequestMethod.GET)
public String home2(@RequestParam int id) {
  logger.info("Welcome home!");
  return "home";
}

はい、言わずともがな、attrNameにはidが渡されます。

眠くなってきたのでこの辺で。

ちなみに、同名の名前があった場合はどちらにもInitBinderが適用されるようです。
これは規約ベースの書き方で素敵ですね。

Posted in program | タグ: , , | Leave a Comment »

DefaultHandlerExceptionResolverのExceptionHandlingに関する備忘録

Posted by hikaruworld : 2011 2月 9

SpringMVCでExpcetionのHandlingはorg.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverが使われるのだけども、
これは、HttpStatusCodeによって以下の様に分類されます。

必要な時にいちいちドキュメント探すのが面倒なのでメモ。
ここに書いてあります。

Exception HTTP Status 補足
ConversionNotSupportedException 500 (Internal Server Error)
HttpMediaTypeNotAcceptableException 406 (Not Acceptable)
HttpMediaTypeNotSupportedException 415 (Unsupported Media Type)
HttpMessageNotReadableException 400 (Bad Request)
HttpMessageNotWritableException 500 (Internal Server Error)
HttpRequestMethodNotSupportedException 405 (Method Not Allowed)
MissingServletRequestParameterException 400 (Bad Request)
NoSuchRequestHandlingMethodException 404 (Not Found)
TypeMismatchException 400 (Bad Request)

ちなみに、SpringSecurityによる認証エラーは以下が返されます

AccessDeniedException 403 (Access is denied)

Posted in program | タグ: , | Leave a Comment »

Spring3(というかJSR-303)でBeanValidationのテスト

Posted by hikaruworld : 2011 1月 15

Spring3(というかJSR-303)でBeanValidationのテスト

Spring3にはJSR-303のBeanValidationの機能が実装されています。
これのテストの仕方について調べていました。

対象のコード

例えば以下のようなBeanがあるとします。

public class User {
  private int userId;
  private String userName;
  
  // getter/setter
}

これに、以下のような制約を加えたいとします。

userId

  • 1以上の数字
  • 9桁以内

userName

  • 20文字以内
  • 必須項目

BeanValidation実装後の修正コード

必要なアノテーションを設定すると以下の通りになります。

public class User {
  @Min(value=1)
  @Max(value=999999999)
  private int userId;
  
  @Length(max=20)
  @NotNull
  private String userName;
  
  // getter/setter
}

テストケース

テストケースは以下のようになります。
一部のテストケースだけ抜粋しています。

// Springではなくjavax側のValidatorをimport
import javax.validation.Validator;

public class UserTest {
  /** 検証対象のクラス */
  public User user;
  
  /** Validatorの実行クラス */
  private static Validator validator;
  /** 実行結果格納用 */
  private Set<ConstraintViolation<User>> violations;
  
  /**
   * validatorのインスタンスをFactoryから取得
   */
  @BeforeClass
  public static void beforeClass() {
    validator = Validation.buildDefaultValidatorFactory().getValidator();
  }

  /** 検証対象のクラスを初期化 */
  @Before
  public void before() {
    this.user = new User();
  }

  //---------------------------------------------------------------------------------------------
  //
  // Validationのテスト
  //
  //---------------------------------------------------------------------------------------------
  
  @Test
  public void userIdの最小値の検証_0を許さない() throws Exception {
    this.user.setUserId(0);
    
    this.violations = validator.validate(this.user);
    
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("value", 1L);
    assertThat(this.violations, hasItems(
    		IsViolation.<User>hasViolation("userId", "{javax.validation.constraints.Min.message}", map ))
    );
  }
}

キモは、以下の部分で、validateを実行した結果、
エラーがあった場合にviolationsに格納されます。

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(this.user);

IsViolation.hasViolationは、
テスト結果がviolationsに格納されてテストしづらいので、
Matcher用のクラスを実装した任意のクラスです。

ちなみにIsViolationクラスは以下の通りです。

package com.wordpress.prepro.controller.utils;

import java.util.HashMap;
import java.util.Map;

import javax.validation.ConstraintViolation;
import javax.validation.Path;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;

/**
 * アノテーションフィールドを用いたバリデーションで利用されるMatcherクラス.
 * 未テスト
 * 
 * <h1>利用方法</h1>
 * 以下のようなクラスがあった場合
 * <pre>
 * pubic class User {
 *      @Length(max = "3")
 *      private String title;
 * }
 * </pre>
 * 以下のようにユニットテスト側で実行時に利用される。
 * <pre>
 * User user = new User();
 * user.setTitle("hoge");
 * 
 * Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
 * Set&lt;ConstraintViolation&lt;User&gt;&gt; violations = validator.validate(user);
 * 
 * assertThat(violations, hasItem(IsAnnotationValidate.&lt;User&gt;hasViolation(
 *  "title", "{org.hibernate.validator.constraints.Length.message}")
 * )));
 * </pre>
 *
 * @author morikawa
 * @version 0.0.1 
 *
 * @param <T> アノテーションが付与されているBean
 */
public class IsViolation<T> extends TypeSafeMatcher<ConstraintViolation<T>> {
  
  /** 検証対象フィールド */
  private String propertyField;
  /**
  * エラーメッセージ
  * ローカライズされていないkeyを検証する
  */
  private String messageTemplate;
  /** アノテーションのフィールド検証用 */
  private Map<String, Object> annotationValue;
  
  /**
  * コンストラクタ
  * @param propertyField 検証対象フィールド
  * @param messageTemplate エラーメッセージ
  * @param annotationValue アノテーションに設定された値。ValueのObjectはkeyに依存する
  */
  public IsViolation(String propertyField, String messageTemplate, Map<String, Object> annotationValue) {
    this.propertyField = propertyField;
    this.messageTemplate = messageTemplate;
    this.annotationValue = annotationValue;
  }
  
  /**
  * {@inheritDoc}
  * @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description)
  */
  public void describeTo(Description description) {
    description.appendText("a annotation validate ")
        .appendText("PropertyPath:")
        .appendValue(this.propertyField)
        .appendText(" ")
        .appendText("MessageTempate:")
        .appendValue(this.messageTemplate)
        .appendText(" ")
        .appendText("annotationValue:")
        .appendValue(this.annotationValue);
  }
  
  /**
   * {@inheritDoc}
   * @see org.hamcrest.TypeSafeMatcher#matchesSafely(java.lang.Object)
   */
  @Override
  protected boolean matchesSafely(ConstraintViolation<T> item) {
    if (item == null) {
      return false;
    }
    // PropertyPath
    if (item.getPropertyPath() == null) {
      return false;
    }
    boolean msgField = false;
    for (Path.Node node : item.getPropertyPath()) {
      if (node.getName().equals(this.propertyField)) {
        msgField = true;
        break;
      }
    }
    
    // messageTemplate
    boolean msgTemp = this.messageTemplate.equals(item.getMessageTemplate());
    
    // annotation
    Map<String, Object> atrMap = item.getConstraintDescriptor().getAttributes();
  
    boolean atr = true;;
    for (Map.Entry<String, Object> entry : this.annotationValue.entrySet()) {
      if (atrMap.containsKey(entry.getKey())) {
        if (Matchers.is(entry.getValue()).matches(atrMap.get(entry.getKey()))) {
          continue;
        }
        atr = false;
        break;
      }
      atr = false;
      break;
    }
    return msgField && msgTemp && atr;
  }

  /**
   * 
   * @param <T> 検証対象のPOJO
   * @param propertyField 検証対象フィールド
   * @param messageTemplate エラーメッセージ
   * @param annotationValue アノテーションに設定された値。ValueのObjectはkeyに依存する
   * @return {@link IsViolation}のインスタンス
   * @see IsViolation
   */
  public static <T> Matcher<ConstraintViolation<T>> hasViolation(String propertyField, String messageTemplate, Map<String, Object> annotationValue) {
    return new IsViolation<T>(propertyField, messageTemplate, annotationValue);
  }

  /**
   * Annotationプロパティ検証の省略版
   * @see IsViolation#hasValidate(String, String, String, Map)
   */
  public static <T> Matcher<ConstraintViolation<T>> hasViolation(String propertyField, String messageTemplate) {
    return new IsViolation<T>(propertyField, messageTemplate, new HashMap<String, Object>());
  }	
}

以上です。

Posted in program | タグ: , , , , | Leave a Comment »

SpringSecurityのMethodSecurityとFilterSecurityではまる

Posted by hikaruworld : 2011 1月 10

結論は、Interceptorを行うFIlterの実装が異なっていた事でした。

現在SpringSecurityを使って設定する場合には、
階層構造定義を以前設定したように行っています。

ROLE_ADMIN > ROLE_LEADER
ROLE_LEADER > ROLE_USER

ただ、この設定でメソッドレベルアノテーションに@Securedを設定した場合に、
上手く行きませんでした。

@Secured(value = "ROLE_LEADER")
public String hoge() {
	return "hoge";
}

ロギングを有効にして(org.springframework.security -> debug)確認してみると、
org.springframework.security.access.vote.RoleHierarchyVoterと、
org.springframework.security.access.vote.RoleVoterがなぜか呼ばれています。

RoleHierarchyVoterは自信で階層構造を設定しているので、
利用されていて当然なんですが、RoleVoterが謎。

ちょっとデバッグしてみると、
FilterSecurityInterceptorとMethodSecurityInterceptorに注入されています。

つまり、Filterレベル(主として認証)の実装は指定した物を参照しているが、
Methodレベル(主として認可)の実装がデフォルト値が利用されているということでしょうか。

というわけで、こんな感じに設定したら解決しました。

<!-- accessDecisionManagerは任意に設定したアクセス管理のインスタンス -->
<global-method-security secured-annotations="enabled" access-decision-manager-ref="accessDecisionManager"/>

うーん。Springの空気読みっぷりを読まなかった自分が悪いなぁと、痛感。

以上です。

Posted in program | タグ: , , | Leave a Comment »

SpringSecurity3で権限の階層構造を定義する

Posted by hikaruworld : 2010 12月 24

参考ページ

バージョンはSpringSecurity3.0.5です。

SpringSecurityでは、認証と認可の設定を比較的簡易に設定することが可能ですが、
実際に運用する場合、権限を包括するような設定を行いたくなります。
つまり、以下のように、上位の権限が下位の権限を包括するようなケースです。
管理者 > ユーザ > ゲスト

この仕組みはRoleHierarchyVoterを利用することで設定することが可能です。

認証の仕組みに関して

Springにおける認可の仕組みを簡単に書いておきます。
※MethodSecurtiyのAccessDecisionManagerの認証処理のみです。

特定のユーザで認証処理が通った場合、そのユーザの持つ権限で
該当画面にアクセスできるか認可の処理が実行されます。
その遷移は以下の絵のようになります。

つまり、

  1. 特定ユーザでアクセスする
  2. 1.のもつユーザ権限が該当画面に適切か投票処理(Voter)が行われる。
    Voterが複数ある場合はそれぞれ認可処理が検証される
  3. 2で実行された投票処理の結果をAccessDecisionManagerで判定する。
    AccessDecisionManagerはデフォルトでは、3パターン(いずれか一つがOK,全てOK,Voter単位での多数決)準備されており、任意に設定可能
  4. 3の結果問題なければ画面が表示され、問題あれば403が返される。
  5. 実際の構成

    サンプルプロジェクトはここにUPしています。

    以下、セキュリティ設定のみ抜粋しています。

    <bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
    	<property name="hierarchy">
    		<value>
    			ROLE_ADMIN > ROLE_USER
    			ROLE_USER > ROLE_GUEST
    		</value>
    	</property>
    </bean>
    <bean id="roleHierarchyVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
    	<constructor-arg ref="roleHierarchy" />
    </bean>
    
    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
    	<property name="decisionVoters">
    		<list>
    			<ref bean="roleHierarchyVoter"/>
    			<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
    		</list>
    	</property>
    </bean>
    

    今回はROLE_ADMIN > ROLE_USER > ROLE_GUESTと階層構造で権限を持たせています。
    まずは、accessDecisionManagerにUnanimousBased(いずれか一つのVoterがOKであればよい)を設定しています。
    そのdecisionVotersとして、AuthenticatedVoterとRoleHierarchyVoterを定義しています。

    RoleHierarchyVoterはコンストラクタインジェクションを用いてRolehierarchyImplを定義しています。

    //org.springframework.security.access.vote.RoleHierarchyVoter
        private RoleHierarchy roleHierarchy = null;
    
        public RoleHierarchyVoter(RoleHierarchy roleHierarchy) {
            Assert.notNull(roleHierarchy, "RoleHierarchy must not be null");
            this.roleHierarchy = roleHierarchy;
        }
        /**
         * Calls the <tt>RoleHierarchy</tt> to obtain the complete set of user authorities.
         */
        @Override
        Collection<GrantedAuthority> extractAuthorities(Authentication authentication) {
            return roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());
        }
    }
    

    RoleHierarchyVoterが実際に認証処理い利用されるのがRoleHierarchyのため、
    この値をRoleHierarchyImplを定義して、プロパティとしてhierarchyを定義します。
    hierarchyは > で各権限を列挙します。

    おまけ

    なお、IS_AUTHENTICATED_FULLYや、
    IS_AUTHENTICATED_ANONYMOUSLYを
    認証処理で利用している場合は、AuthenticatedVoterも
    Voterに入れてあげる必要があります。

    入れてあげないと、

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0’: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Unsupported configuration attributes: [IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_ANONYMOUSLY]

    といった感じで怒られます。
    これは、ここにもあるようにこれらの定義がAuthenticatedVoterで定義されているためです。

    // org.springframework.security.access.vote.AuthenticatedVoter
    
    //~ Static fields/initializers =====================================================================================
    
    public static final String IS_AUTHENTICATED_FULLY
                      = "IS_AUTHENTICATED_FULLY";
    public static final String IS_AUTHENTICATED_REMEMBERED
                      = "IS_AUTHENTICATED_REMEMBERED";
    public static final String IS_AUTHENTICATED_ANONYMOUSLY
                      = "IS_AUTHENTICATED_ANONYMOUSLY";
    

    以上です。

Posted in program | タグ: , , , | Leave a Comment »

SpringSecurityの設定(設定+認証編)

Posted by hikaruworld : 2010 12月 17

SpringSecurityを使うと認証と認可の処理を横断的に挟み込むことが出来ます。
以前は、自力でAOPとFilterで実装してたんですが、便利なものが出たもんですね。

というわけでSecurity Namespace Configurationに従って、初期設定をしてみます。
日本語記事だとマイコミさんの概説 Springプロダクト(3) – Spring Securityでユーザ認証/アクセス制御が参考になりました

あ、ちなみにソースはlaunchpadにUPしてあります

依存ライブラリの取得

まずは、必要なjarを取得して、クラスパスを通してあげます。自分はmavenでサクッとやりました。
今回はWebアプリでFilterを用いてHTTPな認証を行うので以下の3つです。
LDAPとかOpenIDとかやりたければそちらも取ってきましょう。

  • Core – spring-security-core.jar
  • Web – spring-security-web.jar
  • Config – spring-security-config.jar

web.xmlの設定

先ほども記述したように、実際の認証処理は、Filterによって行われるため、
web.xmlに設定を記述する必要があります。

<!-- Filter -->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

spriongSecurity用の設定ファイルを作成

別に設定ファイルはまとめてもかまいませんが、spring1.X時代のXML地獄という思いでがあるので、
設定ファイルは目的(というか名前空間)別に分けるようにしていますのでsecurity用の設定ファイルを作成します。
仮にapplicationContext-security.xmlとすると、

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="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.0.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <http>
        <!-- Login用の画面は認証有無に関係なくアクセス出来るように -->
        <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <!-- Imageなどの静的リソースも同様に認証対象外へ -->
        <intercept-url pattern="/images/*" filters="none" />
        <!-- 全ての画面に対してROLE_USERのみアクセス可能に -->
        <intercept-url pattern="/**" access="ROLE_USER" />
        <!-- ログイン時の画面制御 -->
        <form-login login-page="/login.jsp" default-target-url="/views/home.jsp" />
        <!-- ログアウト時の画面制御 -->
        <logout logout-success-url="/login.jsp"/>
    </http>

    <authentication-manager>
        <authentication-provider>
            <!-- password-encoder hash="md5"/ --> 
            <user-service>
                <user name="bob" password="bob" authorities="ROLE_SUPERVISOR, ROLE_USER" />
                <user name="sam" password="sam" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>
</beans:beans>

に関しては、

Class ‘org.springframework.security.core.userdetails.memory.UserMap’ is marked deprecated

という警告が表示されていますが、まぁ実際に利用する場合はDatabaseと連携させたりすると思いますので、この辺は放置しておきます。

余談ですが、beansにhttp://www.springframework.org/schema/securityを設定してるので、security:http
と書かずに、httpで済むようになります。ちょっとだけ楽ですね。

あとは、xsi:schemaLocationとして、

http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd

を読み込んでおきます。

Spring設定ファイルの定義

自分の環境では、contextConfigLocationとして、/WEB-INF/spring/root-context.xmlを読み込ませた後、
各種定義ファイルをさらにimportしているので、root-context.xmlにセキュリティ用の設定ファイルを読み込ませます。

<import resource="applicationContext-security.xml" />

ログインページの作成

認証用のLoginページを作成します。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Strict//EN">
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<title>Login Page</title>
</head>
<body>
<h1>Login</h1>
<form name="login_form" action="j_spring_security_check" method="post">
<div>
	<p>username <input type="text" name="j_username" /></p>
	<p>password <input type="password" name="j_password" /><br /></p>
</div>
<input type="submit" value="ログイン" /></form>
</body>
</html>

あとは起動するだけです。
すると、ログインページの/login.jspに遷移して認証画面が表示されます。

以上です。

Posted in program | タグ: , , | Leave a Comment »

Spring3でaopの設定をしたらCGLIB2エラーになった

Posted by hikaruworld : 2010 12月 8

Spring3でSessionレベルのデータを管理したくて、以下のようなaopの設定をしたら、

<aop:scoped-proxy/>

こんなエラーが出ちゃいました。


Caused by: org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces.

エラーログにあるように、CGLIB2がないためなんですが、
mvn上でspring-aopの依存関係になってるんじゃないの?と思ってググってみたら、
依存関係はoptionalでした。あらま。

というわけで、リポジトリにhttp://repo1.maven.org/maven2/cglib/cglib/2.2/
を追加して、依存関係でpom.xmlにCGLIB2を追加

    <!-- AOP dependency -->
    <dependency>
    	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>2.2</version>
    </dependency>

で、mvn install して起動したら無事動きました。

参考にしたページ

以上です。

Posted in program | タグ: , , , | Leave a Comment »

SpringFramework3 Controllerのhandlingのテスト

Posted by hikaruworld : 2010 12月 7

Spring3でMVCの@Controllerのテストを行いたい場合のやり方に関してメモしておきます。

Spring3においてControllerのテストを行う場合、
メソッド単位であれば単純にControllerをnewしてテストを行うことが出来ます。
つまり、以下のようなControllerがあった場合、

@Controller
@RequestMapping(value="/employee")
public class HomeController {

	/** とりあえずのサービス。必要に応じてMockなりStubなりを作る */
	private CommonService commonService;

	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	/** コンストラクタインジェクションでServiceを設定 */
	@Autowired(required=true)
	public HomeController(CommonService commonService) {
		this.commonService = commonService;
	}

	/**
	 * /employeeへのGETアクセスの場合に実行されるメソッド
	 */
	@RequestMapping(method=RequestMethod.GET)
	public String home() {
		this.commonService.login();
		
		return "home";
	}
	/**
	 * /employeeへのPOSTアクセスの場合に実行されるメソッド
	 */
	@RequestMapping(method=RequestMethod.POST)
	public String school() {
		return "school";
	}
}

テストは以下のように行います

@Test
public void testInner() {
	HomeController controller = new HomeController();
	String path = controller.home();
	assertThat(path, is("home"));
}

メソッドレベルの検証は上記のように非常に簡単になっているのですが、
HTTPレベルからのテストを考えると、DispatcherServletを経由したテストを行いたい場合があります。
ようするに、DispatcherServletを実行した場合に対象のControllerが起動し、適切なメソッドなりreturnなりが実行されるかの検証です。

このテストを行う場合は、ApplicationContextを@Injectした上で、
applicationContextが持っているhandler情報から、Controller情報を引くようにする必要があるようです。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
	// contextConfigLocationの読み込み
	"file:src/main/webapp/WEB-INF/spring/root-context.xml",
	// DispatcherServletの読み込み
	"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"
})
public class HomeControllerTest {

	@Inject
	private ApplicationContext applicationContext;
	
	/** 検証したいController */
	private HomeController controller;
	
	/** 各種Mock */
	private MockHttpServletRequest request;
	private MockHttpServletResponse response;
	private HandlerAdapter handlerAdapter;
	
	private CommonService commonService;
	
	@Before
	public void setup() {
		this.request = new MockHttpServletRequest();
		this.response = new MockHttpServletResponse();

//		Map<String, HandlerAdapter> map =
//				applicationContext.getBeansOfType(HandlerAdapter.class);
		this.handlerAdapter =
				applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
		this.commonService = applicationContext.getBean(CommonService.class);

		this.controller = applicationContext.getBean(HomeController.class);
		
//		this.controller = new HomeController(this.commonService);
	}
	/** Handlingの実処理 */
	private ModelAndView handle() throws Exception {
		// Map<String, HandlerMapping> map =
		//		applicationContext.getBeansOfType(HandlerMapping.class);
		// AnnotationによってHandlerMappingされた情報を取得
		HandlerMapping handlerMapping =
				this.applicationContext.getBean(DefaultAnnotationHandlerMapping.class);
		// RequestにマッチするControllerをMapping情報から取得
		// この内部にHomeControllerが含まれる。
		HandlerExecutionChain chain = handlerMapping.getHandler(this.request);

		// AOPの取得? TODO ???
		for (HandlerInterceptor interceptor : chain.getInterceptors()) {
			boolean bool = interceptor.preHandle(this.request, this.response, chain);
			if (!bool) {
				return null;
			}
		}
		// Controllerの実行を行い、結果を取得する
		return handlerAdapter.handle(this.request, this.response, chain.getHandler());
	}
	
	@Test
	public void testA() throws Exception {
		// 遷移すべきURLとHTTPメソッドを取得
		this.request.setRequestURI("/employee");
		this.request.setMethod("GET");

		// Controllerの呼び出し
		ModelAndView modelAndView = this.handle();

		// 検証処理
		assertViewName(modelAndView, "home");
	}
}

applicationContextから取得出来る、HandlerAdapterやHandlerMappingを
どれをとればよいかわからず、ちょっとはまりました。

以下、参考にしたblog。感謝。
* More on integration testing of Spring’s MVC annotation mapppings for controllers

Spring1.Xの知識がほとんどなので、便利になったもんですなというのが率直な感想です。
Controllerレベルのテストもメソッド単位でとても実行しやすくなってますね。

Posted in program | タグ: , , | Leave a Comment »