2011年11月10日

SpringJUnit4ClassRunner

どうも、塩内藤です。

Spring配下にあるBeanのテスト

JUnit使ってユニットテストする際にApplicationContextをアノテーションで設定できるということを最近知った。。。
どういう事かというとSpringが単体テスト用の部品も持っていて
SpringJUnit4ClassRunnerっていうのを使うと自分でApplicationContextを作成するコードを記述しなくてよくなる。

たとえば下みたいなクラスがあるとして


package test.validator;

import org.springframework.stereotype.Component;

@Component
public class FooValidator {
public boolean validate() {
return true;
}
}


今までは下みたいな感じで書いてた

package test;

import junit.framework.TestCase;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import test.validator.FooValidator;

public class FooValidatorTest extends TestCase {

private FooValidator validator = null;

@Override
protected void setUp() throws Exception {
// ClassPathXmlApplicationContextを使用してコンテキストファイルを読み込む
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:test/test-context.xml");

// コンテキストからテスト対象のBeanを取得する
validator = context.getBean(FooValidator.class);
}

@Test
public void testCase1() {
assertTrue(validator.validate());
}

}


しかし、知ったあとは、、
アノテーション指定でコンテキスト設定してるので
AutowiredでApplicationContextのDIも可能
当然ながらFooValidatorへのAutowiredはApplicationContextの自動設定部分を削除しても動作します。

package test;

import junit.framework.TestCase;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import test.validator.FooValidator;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "test-context.xml"})
public class FooValidatorTest extends TestCase {

@Autowired
private ApplicationContext context = null;

@Autowired
private FooValidator validator = null;

@Test
public void testCase1() {
assertTrue(validator.validate());
}

}



○注意点
・JUnit4より前のバージョンではRunWithアノテーションを持っていないため、JUnit4.xのみで使用可能
・Spring2.5.xでは、SpringJUnit4ClassRunnerはJUnit4.4以前のバージョンにしか対応していないため、JUnit4.4を使用した方がいい


ちなみに今回はSpring3.0のJUnit4.10で実行しました。
posted by しお at 14:20| Comment(0) | TrackBack(0) | spring

2010年11月19日

Aspectjを使用したインターセプターでの処理対象メソッド取得

どうも、塩内藤です。

タイトルの処理がしないといけなくなったんですが、以前インターセプトについて記事を書いたのを思い出して、見てみました。。。

@RequestMappingが付与されたメソッドのインターセプトサンプルは書いてあったけど、今回やりたい事は書いてなかった。。。


ということで、サンプル

実際はタイトルの処理に加えて、ターゲットのメソッドに付与されているアノテーションを取得するという処理


@Around("execution(* sample.hoge.impl.*Impl.*(..))")
public Object doHogeImplIntercept(ProceedingJoinPoint pjp) throws Throwable {
Object result = null;

MethodSignature signature = (MethodSignature) pjp.getSignature();
HogeAnnotation hogeAnnotation = signature.getMethod().getAnnotation(HogeAnnotation.class);

hogeAnnotation.value();

result = pjp.proceed();

return result;
}


これで、sample.hoge.implパッケージ配下にあるクラスのうち、接尾語がImplのもので、その中にある全てのメソッドの処理をインターセプト

あとはpjp.getSignature()の戻りをMethodSignatureして、処理対象メソッドからアノテーションを取得します。

posted by しお at 19:46| Comment(0) | TrackBack(0) | spring

2010年08月26日

ThreadLocalで

どうも、塩内藤です。

Webアプリ開発をしていて業務ロジック内とかでRequestとか参照したーーーい!!とかよくあると思いますよ?

少なくとも自分は結構あります。。。

そんな時に、この方法を先生に教えてもらいました。


今更な技術らしいのですが、自分は知りませんでした。。。。

めんどいのでクラスをそのままw

spring3.0のspringMVCで開発してます。

まぁあれです、リクエスト毎のHttpServletRequestとHttpServletResponseをコンテキストに設定後、コンテキストそのものをThreadLocalに登録するみたいな事です。

使い方はHandlerInterceptorAdapterとか継承したクラスのpreHandleメソッドでTestWebContext.init()を呼び出してコンテキストの初期化

あとは、好きなとこでTestWebContext.getContext()からあれやこれやします。

今回はセッション、リクエストへの属性保存と取得。
あとはBeanFactoryも入れてみました。

便利ですねぇ楽ですねぇ



package test.context;

import org.springframework.web.servlet.support.RequestContext;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* コンテキスト
* <p>
* ローカルスレッドにTestWebContextを保持し、<br/>
* リクスト毎のローカル変数としてHttpServletRequestを提供します.
* </p>
*
*
*/
public final class TestWebContext {

/** ThreadLocal. */
private static ThreadLocal<TestWebContext> threadLocal = new ThreadLocal<TestWebContext>();

/** HttpServletRequest. */
private HttpServletRequest request = null;

/** HttpServletResponse. */
private HttpServletResponse response = null;

/** RequestContext. */
private RequestContext requestContext = null;

/**
* コンストラクタ.
*/
private TestWebContext() {
}

/**
* HttpServletRequest getter.
*
* @return HttpServletRequest
*/
public HttpServletRequest getRequest() {
return request;
}

/**
* HttpServletRequest setter.
*
* @param request
* HttpServletRequest
*/
public void setRequest(HttpServletRequest request) {
this.request = request;
}

/**
* HttpServletResponse getter.
*
* @return HttpServletResponse
*/
public HttpServletResponse getResponse() {
return response;
}

/**
* HttpServletResponse setter.
*
* @param response
* HttpServletResponse
*/
public void setResponse(HttpServletResponse response) {
this.response = response;
}

/**
* RequestContext getter.
*
* @return RequestContext
*/
public RequestContext getRequestContext() {
return requestContext;
}

/**
* RequestContext setter.
*
* @param requestContext RequestContext
*/
public void setRequestContext(RequestContext requestContext) {
this.requestContext = requestContext;
}

/**
* リクエスト:値の取得.
*
* @param <T>
* T
* @param key
* String
* @return <T>
*/
@SuppressWarnings("unchecked")
public <T> T fromRequest(String key) {
return (T) this.request.getAttribute(key);
}

/**
* リクエスト:値の保存.
*
* @param key
* String
* @param obj
* Object
*/
public void toRequest(String key, Object obj) {
this.request.setAttribute(key, obj);
}

/**
* If session does exist,return true.
*
* @return boolean
*/
public boolean isSessionExist() {

return this.request.getSession(false) != null;

}

/**
* セッション:値の取得.
*
* @param <T>
* T
* @param key
* String
* @return <T>
*/
@SuppressWarnings("unchecked")
public <T> T fromSession(String key) {
return (T) this.request.getSession().getAttribute(key);
}

/**
* セッション:値の保存.
*
* @param key
* String
* @param obj
* Object
*/
public void toSession(String key, Object obj) {
this.request.getSession().setAttribute(key, obj);
}

/**
* セッション:値のクリア.
*
* @param key
* String
*/
public void clearSession(String key) {
this.request.getSession().removeAttribute(key);
}

/**
* Beanの取得
* <p>
* springにより管理されているBeanをid指定で取得します.
* </p>
*
* @param <T> T
* @param beanId
* コンテナに登録されているbeanId
* @return <T> 取得したBean
*/
@SuppressWarnings("unchecked")
public <T> T getBean(String beanId) {

return (T) this.requestContext.getWebApplicationContext().getBean(beanId);

}

/**
* Beanの取得.
* <p>
* springにより管理されているBeanをクラス指定で取得します.
* </p>
*
* @param <T>
* Class
* @param clazz
* Class<T> コンテナに登録されているbeanのクラス
* @return <T> 取得したBean
*/
public <T> T getBean(Class<T> clazz) {

return this.requestContext.getWebApplicationContext().getBean(clazz);

}

/**
* コンテキストを取得します.
*
* @return TestWebContext
*/
public static TestWebContext getContext() {

return threadLocal.get();

}

/**
* コンテキスト初期化処理
* <p>
* コンテキストをスレッドローカル変数として保持します.
* </p>
*
* @return TestWebContext
*/
public static TestWebContext init() {

TestWebContext testContext = new TestWebContext();

threadLocal.set(testContext);

return testContext;

}

/**
* コンテキストの破棄.
*/
public static void destroy() {

threadLocal.set(null);

}

}


posted by しお at 13:53| Comment(0) | TrackBack(0) | spring

2010年08月13日

ApplicationEvent

どうもどうも、塩内藤です。

忘れたくなかったので2回目のアップw

ApplicationContextの初期化時に、ある処理を実行したかったのでちょっと調べてみました。

Spring では標準的な Observer パターンによるイベントを伝播する機能があるとの事

ちうことで、実装してみました。


import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.context.support.RequestHandledEvent;

/**
* ApplicationContext Observer
*
* @author sio
*
*/
public class ContextObserver implements ApplicationListener<ApplicationEvent> {

/** {@inheritDoc} */
@Override
public void onApplicationEvent(ApplicationEvent event) {

if (event instanceof ContextRefreshedEvent) {

onContextRefreshedEvent((ContextRefreshedEvent) event);

} else if (event instanceof ContextClosedEvent) {

onContextClosedEvent((ContextClosedEvent) event);

} else if (event instanceof RequestHandledEvent) {

onRequestHandledEvent((RequestHandledEvent) event);

}

}

/**
* ApplicationContext が初期化またはリフレッシュされたときに発行されます。
*
* @param event ContextRefreshedEvent
*/
private void onContextRefreshedEvent(ContextRefreshedEvent event) {


}

/**
* ApplicationContext がクローズされたときに発行されます。
*
* @param event ContextClosedEvent
*/
private void onContextClosedEvent(ContextClosedEvent event) {


}

/**
* DispatcherServlet を使用している Web アプリケーションにHTTP リクエストがあった場合に発行されます。
*
* @param event RequestHandledEvent
*/
private void onRequestHandledEvent(RequestHandledEvent event) {


}

}



とりあえず、今回は初期化処理なのでContextRefreshedEventをハンドリングして処理を記述しようかなぁと思ってます。
posted by しお at 14:40| Comment(0) | TrackBack(0) | spring

2010年08月11日

Aware

どうも、塩内藤です。

引き続きspring系で

bean定義のxmlで定義しているメッセージソースをMessageUtilみたいなの作って業務ロジック内でも使いたかった。

で、ちょっと考えてAutowiredとか使えばいいのかなぁと思ってたんですが、Awareなるものがあるそうな

ちゅうことで、実装。

1.MessageUtilにMessageSourceAwareをimplements
2.@Componentを指定してコンポーネントスキャンの対象する
3.受け取ったMessageSourceをstaticで保持

これで、いいみたいです。

下のbean定義のxmlで定義しているメッセージソースをinjectionしてくれます。

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/messages/messages" />
<property name="cacheSeconds" value="0" />
</bean>



package org.sample;

import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.stereotype.Component;

@Component
public class MessageUtil implements MessageSourceAware {

private static MessageSource messageSource = null;

/** {@inheritDoc} */
@Override
public void setMessageSource(MessageSource messageSource) {

MessageUtil.messageSource = messageSource;

}


public static String getMessage(String messageCode, Object... args) {

return messageSource.getMessage(messageCode, args, null);

}

}


ついでにBeanFactory用のやつも作成。

こんな感じになりました。

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

@Component
public class BeanFactoryUtils implements ApplicationContextAware {

/** ApplicationContext */
private static ApplicationContext applicationContext = null;

/** {@inheritDoc} */
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

BeanFactoryUtils.applicationContext = applicationContext;

}

@SuppressWarnings("unchecked")
public static <T> T getBean(String beanId) {

return (T) applicationContext.getBean(beanId);

}

public static <T> T getBean(Class<T> clazz) {

return applicationContext.getBean(clazz);

}

}


よくよく調べるとAware系のインターフェイスが存在してて、その手間を省くためにAutowiredが出てきたぽい?

Autowiredだと複数の実装クラスが存在してると@Qualifier使ってbeanIdガチ指定しないといけない気がするけど、どうなんだろう、、、
posted by しお at 13:03| Comment(0) | TrackBack(0) | spring

2010年08月10日

interceptor

どうも、塩内藤です。

こないだからの続きでRequestMappingのinterceptorを作ってみました。

ソース
  
package org.lesson.interceptor;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class RequestMappingInterceptor {

@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object doRequestMappingIntercept(ProceedingJoinPoint pjp) throws Throwable {

return pjp.proceed();

}
}


これで、RequestMappingアノテーションが付いたメソッド全部をインターセプト

参考にしたのは、ドラチックコードアシスターc9katayama先生!

http://d.hatena.ne.jp/c9katayama/20080802/1217670477

アザース


posted by しお at 15:13| Comment(0) | TrackBack(0) | spring

2010年08月06日

RequestMapping時のメソッドシグネチャ

どうも、塩内藤です。

spring3.0でのspringMVC

@Controllerを使用するときに、ちょっとした疑問

まぁ使用するにあたってbeanの定義ファイルに<mvc:annotation-driven />という1行が必要みたいなんですが、ここまでは全然おけ


その後にControllerのクラス作成して下みたいにメソッドを定義していきます。

@RequestMapping(method = RequestMethod.POST)
public ModelAndView onPost()

で、まぁこの時のメソッドシグネチャがイマイチワカンネ

HttpServletRequestを引数に指定すれば入ってくるし、無くても問題なし

というか、requestかsessionスコープに入ってるやつ指定したらなんでも引数として受け取れるんですかねぇ??

ソース見ろって話なんですが、調べるのめんどいので見てないです。。。

だた、引数でBindingResult受け取ったときにvalidate対象のモデル、BindingResultの順番に定義しないとシグネチャエラー的な事言われた気がする。。。

正直よくわからない、、、


エライ人だれかオシエテー
posted by しお at 13:23| Comment(0) | TrackBack(0) | spring

2010年07月29日

AOP

どうも、塩内藤です。

そういえば、Interceptorとか作った事なかったということで、やってみました。

なんせ、初めて作るので必要なjarとか書き方とか全然ワカンネ

下の二つのサイトを見てなんとか実装できました。

http://static.springsource.org/spring/docs/2.5.x/reference/aop.html

http://johnsjavapda.blogspot.com/2009/06/i-want-my-aop-spring-3-aop-that-is.html


今回はspring3.0使ってます。


posted by しお at 16:28| Comment(0) | TrackBack(0) | spring