新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:Spring

[学習記録] Spring

内容:第3章 SpringのAOP

DIxAOPコンテナ
public class HogeServiceImpl implements HogeService {
    @Autowired
    private HogeDao dao;

    public int insert(Hoge hoge) throws Exception {
        /* ログ関係の処理 */

        /* コネクションの取得 */
        ret = dao.insert(conn, hoge);
        /* コミット、ロールバック、コネクション解放の処理 */

        /* ログ関係の処理 */
    }
}
  • ログ出力、トランザクション処理などの共通処理
    • コード中からは取り除いた方が可読性が上がり、テストも容易になる
    • AOPで共通化出来る処理をオブジェクトから分離
AOPとは何か
  • 本質的でない余計な処理を外出しする技術のこと
    • 具体的には、ログ出力などの共通化できる処理(横断的関心事)をAspectという一つの単位にまとめる
    • これにより、あるオブジェクトが本来やるべきことだけ行うようにする技術
  • AOPの用語
    • Aspect;横断的な関心事が持つ振る舞い(Advice)と、適用する条件(Pointcut)をまとめたもの。
    • Joinpoint;Adviceが行う振る舞いを割りこませることが可能な時。コーディング上ではプログラムコード中の場所。
    • Advice:Joinpointで実行されるコード。ログ出力やトランザクション処理などのコードが書かれる
    • Pointcut:処理がJoinpointに達したときに、Adviceを呼ぶかどうかをフィルタリングするもの。
Springが提供するAdvice
  • モジュールが本来やるべきでない処理を、モジュールから分離してAdviceに記述する
    • Adviceにもいくつかの種類があり、必要に応じたAdviceに処理を記述する必要
Adivceのタイプ 説明
Before JoinPointの前に実行する
After 後に実行する
AfterReturning 完全に正常終了した後に実行する
Around 前後で実行する
AfterThrowing 例外が発生した場合に実行する
  • どれを使うべきか?
    • メソッド開始・終了時のログ出力には、Before/AfterもしくはAround
    • 例外処理にはAfterThrowing
    • トランザクションのような代表的な処理はAOPのプロダクトで実装したものを提供してくれる(詳細は5章)
Proxyを利用したAOP
  • AOPの実現方法はいくつかあるが、Springで利用しているProxyを利用したAOPについて解説
    • インタフェースを実装したProxyを利用して、クラスQが呼び出したメソッドを横取りしてAdviceを実行
  1. クラスQにはRインタフェース型のインスタンス変数が用意され、@Autowiredが付く
  2. DIxAOPコンテナは、Rインタフェースを実装したProxyクラスを生成し、Qのインスタンス変数にインジェクション
  3. ProxyクラスからはもちろんRImplのメソッドを呼び出すが、(Aroundなら)その前後でAdviceを呼び出す
  • ProxyベースのAOPの欠点
    • クラスのバイナリを書き換えるような他方式とくらべて、パフォーマンスの面で劣る
    • また、メソッド以外のフィールドなどを指定した細かいAOPができない
    • ただし、トランザクション管理やオブジェクトプール、型の再定義などAOPで実現される大部分はカバーできる
AOPの使いどころ
  • やってはいけないこと
    • AOPを利用して業務処理を分離すること
    • ソースコードの可読性が著しく落ちるため
    • getPrice()でフィールドのpriceをただ返しているが、AOPを使って消費税率を掛けてしまう、など
  • あくまで「共通化出来る処理」に使う
    • プロジェクト内でAOPを利用するのは、基盤チームや共通化チーム
    • 業務処理をプログラミングするチームは使ってはいけない
Spring AOPアノテーションを利用したAOP
    • ProductDaoのfindProductメソッドAOPの適用対象
    • findProductメソッドの利用時に、Adviceに記述したHello hogehogeが表示される
@Aspect
@Component
public class MyFirstAspect {
    @Before("execution(* findProduct(String))")
    public void before() {
        System.out.println("Hello Before!!!");
    }

    @Afrer("execution(* findProduct(String))")
    public void after() {
        System.out.println("Hello After!!!");
    }
    
    @AfterReturning(value="execution(* findProduct(String))", returning="product")
    public void afterReturning(Product product) {
        System.out.println("Hello AfterReturning!!!");
    }

    @AfterThrowing(value="execution(* findProduct(String))", throwing="ex")
    public void afterReturning(Throwable ex) {
        System.out.println("Hello AfterThrowing!!!");
    }
}
  • Pointcutの記述方法
    • execution( メソッドの修飾子 メソッドの戻り値の型 パッケージ.クラス.メソッド名(引数の型...) throws 例外)
    • メソッドの修飾子やthrows 例外は省略可
    • メソッドの戻り値型、クラス名やインタフェース名にはワイルドカード*を利用可能
    • *はパッケージの区切り文字.と一致しないため、複数パッケージと一致させるには".."を利用
    • メソッドの引数に..を記述すると、あらゆる引数と一致
    • パッケージ.クラス名は省略可能
  • Aroundアドバイス
    • 他のアドバイスと異なり、AOPの対象となるメソッドの呼び出しをアドバイス中で自ら行う必要
    • ProceedingJoinPiointクラスのproceedメソッドを利用して行う
    • proceedの戻り値は対象メソッドの戻り値なので、しっかりとreturnする(忘れると呼び出し元で受け取れない!)
    • proceedメソッドを忘れると対象メソッドが絶対に呼ばれないので注意(それを利用したテクニックもあるが、わかりにくいのでNG)
@Around("execution(* findProduct(String))")
public Product aroung(ProceedingJoinPoint pjp) throws Throwable {
    // 共通化処理

    Product p = (Product)pjp.proceed();

    // 共通化処理
    return p;
}
  • Aroundアドバイス(承前
    • Aroundだけで他のアドバイスは事足りる、try-catchで囲めばAfterThrowingにもなる
    • あるメソッドが2つのAroundアドバイスの対象となったら?
    • 両方のAdviceの処理は行われるが、メソッドが実行されるのは1回だけ
Spring AOP:Bean定義ファイルを利用したAOP
<aop:config>
  <aop:aspect id="myAspect" ref="myFirstAspect">
    <aop:pointcut id="pc" expression="execution(* findProduct())"/>
    <aop:before pointcut-ref="pc" method="before" />
    <aop:after pointcut-ref="pc" method="after" />
    ...
  </aop:acpect>
</aop:config>