Spring Framework リファレンスの“8.6 Proxying mechanisms”に、Spring AOP はターゲットオブジェクトがインターフェースを実装している時は JDK の Dynamic Proxy、インターフェースを実装していない時は CGLIB Proxy を使うと記されています。
今回は、CGLIB Proxy を有効にして Pointcut の対象を広げます。
準備 - cglib
『Spring AOP で流れを追う!』で触れたとおり、cglib(Code Generation Library) サイトから cglib-2.2.2.jar をダウンロードし、/WEB-INF/lib にコピーします。
asm
CGLIB Proxy を利用するためにはこれも必要です。cglib サイトのトップページ、あるいはOW2 Consortium サイトの ASM ページに行き、リンクを辿って asm-3.3.1-bin.zip を持ってきたら、解凍先の lib フォルダーに入っている asm-3.3.1.jar を /WEB-INF/lib にコピーします。
※ 最新の asm-4.0 で試したところ cglib-2.2.2 がうまく動きませんでした。
CGLIB Proxy の有効化
リファレンス“8.6 Proxying mechanisms”に従って[servlet-name]-servlet.xml で以下の設定を行います。
<aop:aspectj-autoproxy proxy-target-class="true"/>
Pointcut の追加
まずは Join Point にしたいメソッド周りの特徴を見極めます。以下は『Bean Validation』で手を加えた AccountController クラスの一部です。
AccountController.java(抜粋)
package wrider.controller; : @Controller @SessionAttributes({"idCard", "userProfile"}) public class AccountController { : @RequestMapping(value="/account/login.html", method=RequestMethod.POST) public ModelAndView login(@Valid IdCard idCard, BindingResult br) { : } : @RequestMapping(value="/account/register.html", method=RequestMethod.POST) public ModelAndView register(@Valid UserProfile userProfile, BindingResult br) { : } : }
クラス定義を見ると AccountController は wrider パッケージにあり、インターフェースを implements していないことがわかります。POST リクエストで呼び出される login()メソッドと register()メソッドは public で、どちらも2つ目の引数として BindingResult 型を受け取っています。この辺の特徴を Pointcut に定義したのが以下のコードです。
WebPointcuts.java(抜粋)
@Aspect
@Component
public class WebPointcuts {
@Pointcut("within(com.scopeandtarget.wrider..*)")
public void inWriderPackage() {}
:
@Pointcut("inWriderPackage() && execution(public * *(*,org.springframework..BindingResult))")
public void postAction() {}
}
これは『Spring AOP で流れを追う!』で作った WebPointcuts クラスに手を加え、新たに postAction というメソッドを追加しています。@Pointcut アノテーションの中身を見ると、「wrider パッケージにある全ての型に定義されたメソッド」を表す inWriderPackage と、「public で2つ目の引数に BindingResult 型を持つメソッド」という条件を "&&" でつないでいます。
Advice の追加
続いて、Pointcut に新しく追加した postAction で呼び出される Advice を追加します。
WebAdvices.java(抜粋)
@Before("com.scopeandtarget.wrider.aop.pointcut.WebPointcuts.postAction() && args(*,param)") public void logPrePostRequest(final JoinPoint jp, final Object param) { BindingResult bindingResult = (BindingResult)param; String format = "{}.{} will be invoked!"; outputLogMessage( format, jp.getTarget().getClass().getSimpleName(), jp.getSignature().getName()); StringBuffer sb = new StringBuffer(); sb.append("BindingResult has .."); sb.append(CoreConstants.LINE_SEPARATOR); for (Map.Entryentry : bindingResult.getModel().entrySet()) { sb.append(" key: " + entry.getKey()); sb.append(CoreConstants.LINE_SEPARATOR); sb.append(" value: " + entry.getValue().toString()); sb.append(CoreConstants.LINE_SEPARATOR); sb.append(" -----------------------------------"); sb.append(CoreConstants.LINE_SEPARATOR); } logger.info(sb.toString()); }
新たに logPrePostRequest メソッドが加わりました。@Before アノテーションを付けているので Before Advice です。インターセプトしたメソッドの 2 番目の引数を final Object 型で受け取り、メソッドの中で BindingResult 型にキャストしています。クラス名とメソッド名は、他の Advice と同じように、引数として受け取った JoinPoint を介して取得後、logger.info() でメッセージ内のプレースホルダーに埋め込んで出力しています。
一方 BindingResult は、取り出した key と value を StringBuffer に append しながらループし、最後に logger.info() で出力するようにしています。尚、改行に ch.qos.logback.core.CoreConstants を使用しているため、/WEB-INF/lib の logback-core-[version].jar をビルドパスに登録しています。
実行!
以上で作業は完了です。実際に動かし、吐き出されたログの一部が以下です。まず、CheckPasswordValidator、続いて CheckEmailValidator の isValid で入力値が検証され、AccountController の login メソッドが呼び出されている様子がわかります。検証結果は全て正常なので、最後にある BindingResult の内容は“0 errors”のみです。
尚、検証エラーがあった際の BindingResult の内容は『Validator と MessageSource』に一例を示しています。
ishtar.log
21:47:04.593 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:93] CheckPasswordValidator.isValid will be invoked! [q1w2e3r4] 21:47:04.593 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:93] CheckPasswordValidator.isValid is on going! 21:47:04.593 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:93] CheckPasswordValidator.isValid completed! result is true 21:47:04.640 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:93] CheckEmailValidator.isValid will be invoked! [wrider@abc.com] 21:47:04.640 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:93] CheckEmailValidator.isValid is on going! 21:47:04.640 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:93] CheckEmailValidator.isValid completed! result is true 21:47:04.640 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:93] AccountController.login will be invoked! 21:47:04.640 [ishtar] INFO [http-bio-8080-exec-3] wrider.aop.advice.WebAdvices [WebAdvices.java:89] BindingResult has .. key: idCard value: wrider.model.IdCard@1875a82 ----------------------------------- key: org.springframework.validation.BindingResult.idCard value: org.springframework.validation.BeanPropertyBindingResult: 0 errors -----------------------------------
ログと聞くと一見地味な印象ですが、特にセキュリティやマーケティングでは必要不可欠な要素です。CGLIB Proxy を使うことで Pointcut に設定できる Join Point ―― Spring AOP の場合はメソッド実行のタイミングということですが――の領域を広げることができ、それに伴いログデータを採取できる機会も増えます。BigData の重要性が増しているといわれていますが、例えばシステム上でのユーザーの行動についての、より詳細で豊富なデータを集めたいというようなとき、Spring AOP を試してみるのも悪くないと思いました。
0 件のコメント:
コメントを投稿