AOPとMVC
for Beginner

 2012/2/20
問題
•  インスタンス変数に宣言したのと同じ型
   のインスタンスをインジェクションして
   もらうためのアノテーションは?
•  インスタンス化され、インジェクション
   されるために、クラス宣言の前に書くア
   ノテーションは?
•  結局、DIコンテナって何のためにあるの
   か?


                         2
前回の続き
•  SpringのDIを使って、部品化はできた
   (気がする)
•  でも・・・
 ‒  ログなどの共通処理部分が部品に残っている
 ‒  例外処理も部品化を壊している
•  それに・・・
 ‒  トランザクション管理は面倒だし、難しい




                           3
Springの説明の前に・・・

AOPの基本


                  4
AOPを使ってもっと部品化する
•  AOPを使えば処理を後からクラスに追加できる
 ‒  例:トレースログを追加する

                           find()を呼ぶ	
                DaoImpl	
      ServiceImpl	
                                                 find()	
                                         Dao

                                          public class DaoImpl extends Dao{	
 >java ・・・	
                                  ・・・	
 16:00:01 *Start*   find() DaoImpl	
          public List find() {	
 16:00:02 *End*     find() DaoImpl	
              List list = select();	
 17:02:12 *Start*   find() DaoImpl	
              return list;	
 17:02:13 *End*     find() DaoImpl	
          }	
                                          }	
           実行結果	

                                                                                5
AOPをもう少し正しく
Adviceを追加できるときが
     Joinpointとなる	
      Joinpointを条件で絞り込む
                                 フィルター	

                                 Aspect

                      Pointcut              Advice

       ソースコード	

                      Pointcut              Advice

          Joinpoint


                                          追加したい処理	


                                                      6
Joinpoint
•  Adviceを追加できるとき(ポイント)
•  AOPの仕様であり、実装者は変更できない
•  Joinpointの仕様例
 ‒  メソッドの開始時、終了時
 ‒  プロパティが利用されたとき

            DaoImpl
       add()
       delete()
       find()
       update()
                      Joinpoint
                      例:メソッドの開始時や終了時	

                                     7
Pointcut
•  Joinpointに達した命令を、Adviceまで到
   達させるか否かフィルタリングするフィ
   ルター

                                 Joinpointを条件「add*」で絞り込む	


                         DaoImpl               Pointcut

                      add()
                      delete()                            Advice
ServiceImpl	
         find()
                Dao   update()
                                   Joinpoint
                                               追加したい処理	

                                                                   8
Befor Advice
•  Joinpointの前で実行される


                             メソッドの実行前に割り込む	



           method()
                      Before Advice
  Client                                          Servant
                                       return
                                      Exception




                                                            9
After Advice
•  Joinpointの後で実行される



            method()

   Client                                     Servant
                   After Advice    return
                                  Exception

                メソッドの実行後に割り込む	




                                                        10
After Returning Advice
•  Joinpointが正常終了した後に実行される


              method()
                                          return
   Client        After Returning Advice            Servant

                                    Exception
            メソッドの実行後に正常終了時
            に割り込む	




                                                             11
Around Advice
•  Joinpointの前後で実行される


           method()                   method()
  Client              Around Advice    return     Servant

                                      Exception


                 メソッドの実行前と実行後、例
                 外発生時にも割り込む	




                                                            12
Throw Advice
•  Joinpointで例外が発生した時に実行される



           method()
  Client                                   Servant
                                return
                Throw Advice   Exception


              例外発生時に割り
              込む	




                                                     13
問題
•  メソッドの開始と終了のログをとりた
   かったら?
•  メソッドが正常終了したログをとりた
   かったら?
•  メソッドが異常終了したときのログをと
   りたかったら?




                        14
AOPの仕組み例
•  Proxyベースの場合、ProxyオブジェクトはSpringが自動生成する




                                 :Bean
                                             :Advice

                         処理の
           処理の依頼	
                         依頼	
                                           Adviceの呼び出し	



 :Client                         :Proxy            :Spring
                 Interface
                                          自動生成	


                                               Bean定義
                 Pointcutの参照	
                  ファイル	

                                                             1-15
AOPの主な利用方法
•  各クラスに記述されている同一の処理を抜き出し、ひと
   まとめにして、既存のクラスに後から追加する
 ‒  ライブラリとの違い
   •  ライブラリは呼び出さないといけない
   •  AOPは勝手に追加される
•  追加すると便利な処理
 ‒  トランザクション管理
   •  トランザクション管理は難しいくプログラマに任せられない
 ‒  ログ管理
   •  メソッドの開始と終了のトレースログが正しく出力されない
     ‒  誰もフォーマットを守らない
     ‒  トレースログを追加し忘れる
 ‒  例外管理
   •  処理の途中でExceptionが握りつぶされてしまう
     ‒  Exceptionを実行時例外にする

                                    16
AOPでやらない方が良いこと
•  個別の処理(特定業務の処理、デバッ
   グ)
 ‒  プログラマが個別にAOPをいれるのは不可
  •  そこで何をやっているのかが分からなくなる


•  業務アプリのプログラマではなく、基盤
   チームとかが使う技術!?




                            17
SpringのAOP
•  定義ファイルの利用
 ‒  Spring1.x系では基本
•  アノテーションの利用
 ‒  Spring2.x系以降、アノテーションの利用が増
    えている(大規模開発や大手SI便だでは定義
    ファイルの利用が多い)




                            18
問題 書いてないけど?
•  例えば
 ‒  アノテーション
   •  メリット:定義ファイルの管理が不要
   •  デメリット:プログラマにアノテーションを意識
 ‒  とか、定義ファイルのメリットとかデメリット




                               19
アノテーションを使った
AOP


              20
アスペクトの例
•  アノテーションの利用
@Aspect
public class AspectMessage {

    @After("execution(* exMethod())")
    public void hoge() {
      // メソッド終了後に動くAdvice
      System.out.println("after called");
    }
}
                                            21
アノテーション一覧
•  アノテーションを利用したAOP
    ‒  Bean定義ファイルの記述が簡潔になる
    ‒  ソースコードに記述することで管理が煩雑

 アノテーション         	
                      説明 	
@Aspect               Adviceとなるクラスを指定するアノテーション     	
@Around               Around Adviceとなるメソッドを指定するアノテーション 	
@Before               Before Adviceとなるメソッドを指定するアノテーション	
@After                After Adviceとなるメソッドを指定するアノテーション	
@AfterReturning                                               	
                      After Returning Adviceとなるメソッドを指定するアノテーション
@AfterThrowing        After Throwing Adviceとなるメソッドを指定するアノテーション	

                                                              22
アドバイス詳細(1)
•  Before, After
   ‒  @After( Primitiveポイントカット )
      •  メソッド名は任意、メソッドのパラメータと戻り値はなしでも可能。
         メソッド内で、アスペクト対象となっているメソッド名やパラメー
         タ,戻り値などの取得をする場合は、パラメータにJoinPoint
      •  メソッド内で、アスペクト対象となっているメソッドを呼び出す必
         要はない


    @Before(“execution(* exMethod())”)
    public void hoge() {
        ・・・
    }	

                                         23
アドバイス詳細(2)
•  Around
  ‒  @Around( Primitiveポイントカット )
    •  メソッド名は任意、メソッドのパラメータには必
       ずProceedingJoinPointが必要、戻り値はアスペ
       クト対象のメソッドにあわせる
    •  メソッド内で、アスペクト対象となっているメ
       ソッドを呼び出す必要がある
      ‒  ProceedingJoinPoint#proceed()メソッド
          »  Object proceed() throws Trowable
    •  メソッド内で、アスペクト対象となっているメ
       ソッド名やメソッドのパラメータの取得は
       ProceedingJoinPointを介しておこなう

                                                24
アドバイス詳細(3)
•  Around
  ‒  戻り値はアスペクト対象にあわせる
public String getMessage() {
    return “hello!”;                アスペクト対象のメソッド
 }	

 @Around(“execution(* getMessage())”)
 public String fuga(ProceedingJoinPoint pjp)
                            throws Throwable {;
    String msg = (String)pjp.proceed();
    return msg;
 }	

                                                   25
アドバイス詳細(4)
public int getFigure() {
    return 100;            アスペクト対象のメソッド
 }	

 @Around(“execution(* getFigure())”)
 public int fuga(ProceedingJoinPoint pjp)
                              throws Throwable {;
    Integer figure= (Integer)pjp.proceed();
    return figure.intValue();
 }	

                                                    26
アドバイス詳細(5)
•  Around
  ‒  ProceedingJoinPointの使い方

  メソッド名の取得

  Signature sig = pjp.getSignature();
  System.out.println("Sig: " + sig.getName());	

          パラメータの取得(最初のパラメータ)

          Object[] os = pjp.getArgs();
          System.out.println("Args: " + os[0]);	

                                                    27
問題∼重複してたらどうなる?
@Around(“execution(* add())”)
public int hoge (ProceedingJoinPoint pjp)
                            throws Throwable {;
  Integer figure= (Integer)pjp.proceed();
  return figure.intValue();              これと同じhogehogeメソッド
}                                            が存在したら?


private int x;
public int add(int i) {
   x = x + i;                     アスペクト対象のメソッド
   return x;
 }	
                                                       28
アドバイス詳細(6)
•  AfterReturning
  ‒  @AfterReturning(value= Primitiveポイント
     カット , returnig = 戻り値の変数名 )
    •  メソッド名は任意、メソッドのパラメータはアス
       ペクト対象となっているメソッドの戻り型とアノ
       テーションのretuning属性で指定した変数名
    •  メソッド内で、アスペクト対象となっているメ
       ソッドを呼び出す必要はない




                                        29
アドバイス詳細(7)
public String exMethod() {
    return “hello!”;             アスペクト対象のメソッド

 }	



 @AfterReturning(value=“execution(* exMethod())”,
                                           returning=“ret”)
 public String hoge(String ret) {
     System.out.println(“Return: “ + ret);
 }	


                                                              30
アドバイス詳細(8)
•  AfterThrowing
  ‒  @AfterReturning(value= Primitiveポイント
     カット , throwing = 例外の変数名 )
    •  メソッド名は任意、メソッドのパラメータはアス
       ペクト対象となっているメソッドの戻り型とアノ
       テーションのthrowing属性で指定した変数名
    •  メソッド内で、アスペクト対象となっているメ
       ソッドを呼び出す必要がない




                                        31
アドバイス詳細(9)

 @AfterThrowing(value=“execution(* exMethod())”,
                              throwing=“ex”)
 public String foo(HogeException ex) {
     System.out.println(“Exception Msg: “ + ex.getMessage());
 }	




                                                            32
アドバイス詳細(10)
•  AfterThrowing
 @AfterThrowing(value=“execution(* exMethod())”, throwing=“ex”)
 public String foo(Throwable ex) {
   System.out.println(“Exception Msg: “ + ex.getMessage());
 }
 @AfterThrowing(value=“execution(* exMethod()”, throwing=“ex”)
 public String var(HogeException ex) {
   System.out.println(“Exception Msg: “ + ex.getMessage());
 }
 @AfterThrowing(value=“execution(* exMethod()”, throwing=“ex”)
 public String hoge(Exception ex) {
   System.out.println(“Exception Msg: “ + ex.getMessage());
 }


                                                                  33
問題∼どのアドバイス?
•  前ページの実装があったとき、どこかの
   プログラムがExceptionを投げてよこしま
   した
•  どのアドバイスが動くでしょう?




                         34
Primitiveポイントカット
  Primitive	
 ポイントカット        	
                      概要 	
execution	
          呼出先の「メソッド」、「コンストラクタ」を指定する。      	
within	
             呼出元の「クラス」を指定する。

                     withinをPointcutに指定すると、指定されたクラスから呼出される、メソッ
                     ド等が選択されることになる。

                     対象は、指定されたクラスで宣言されたメソッドに限定され、指定された
                     クラスの親クラスで宣言されたメソッド内は対象外となる。          	
this	
               呼出元の「クラス」を指定する。

                     thisをPointcutに指定すると、指定されたクラスから呼出される、メソッド
                     等が選択されることになる。

                     withinとは、親クラスで定義されたメソッドの呼出しも対象とする点が異な
                     る。	
target	
             呼出先の「クラス」を指定する。

                     ただし、呼出先のstaticフィールドは対象外となる。     	
args	
               呼出先「メソッド」の引数の型を指定する。       	
※Primitiveポイントカット:あらかじめ用意されているポイントカットのこと	


                                                               35
executionの基本構文
•  execution(メソッドの修飾子△メソッドの戻り値型△
   パッケージ.クラスまたはインタフェース.メソッド名
   (仮引数の型|,仮引数の型…|) △throws 例外)

•  「メソッドの修飾子(publicやprivateは省略可能)」や
   「throws△例外」は省略することが可能
•  メソッドの戻り値型、パッケージやクラス名、インタ
   フェース名にはワイルドカード(*)の利用が可能
•  仮引数に(..)を記述すると任意の個数の引数と一致さ
   せることが可能




                                  36
ポイントカットで利用できる論理演算子
論理                                               説明	
演算子	
|| または    論理和を意味する論理演算子	
or	
          例)
          execution(* *..AopExBean.exMethod()) or execution(* *..AopExBeanParent.exMethod())
           →AopExBeanのメソッドexMethodまたはAopExBeanParentのメソッドexMethodを指定	
&& また     論理積を意味する論理演算子	
は and	
          例)
          execution(* *..AopExBean.exMethod()) && execution(* *..AopExBeanParent.exMethod())
             →AopExBeanのメソッドexMethodまたはAopExBeanParentのメソッドexMethodを指定
! または     否定を意味する論理演算子。	
not	
          例)
          execution(* exMethod()) and not execution(* *..AopExBeanParent.*())
           →AopExBeanParent以外のクラス(インタフェース)のメソッドexMethodを指定	


                                                                                        37
コーディング例(1)
@Aspect
public class AspectMessage {

  @After("execution(* exMethod())")
  public void after() {
    // メソッド終了後に動作するAdvice
    System.out.println("after called");
  }

  @Before("execution(* exMethod())")
  public void before() {
    // メソッド開始時に動作するAdvice
    System.out.println("before called");
  }



                                           38
コーディング例(2)

@Around("execution(* exMethod())")
 public void around(ProceedingJoinPoint pjp) throws Throwable {
   // メソッド呼出の前後に動作するAdvice
   System.out.println("pre proceed");
   pjp.proceed();
   System.out.println("post proceed");
 }




                                                                  39
コーディング例(3)
    @AfterReturning(value="execution(* exMethod())", returning="ret")
    public void afterReturning(String ret) {
      // メソッド呼出が例外の送出なしに終了した際に動作するAdvice
      System.out.println("after returning called");
      System.out.println("return value = " + ret);
    }

    @AfterThrowing(value="execution(* exMethod())", throwing="ex")
    public void afterThrowing(Throwable ex) {
      // メソッド呼出が例外の送出なしに終了した際に動作するAdvice
      System.out.println("after throwing called");
      System.out.println("exception value = " + ex.toString());
    }
}



                                                                        40
定義ファイル
<?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:context=http://www.springframework.org/schema/context
  xmlns:aop=http://www.springframework.org/schema/aop
  xsi:schemaLocation=”
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-2.5.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
…
  <aop:aspectj-autoproxy/>
…
</beans>



                                                                          41
定義ファイルには書かないでしょ!?
AOP


                    42
それでも書きたい定義ファイル(1)

  	
 ・・・
<aop:config>
     <aop:aspect id="myAspect" ref="aspectMessage">
         <aop:pointcut id="fuga"
                expression="execution(* getMessage())"/>
         <aop:before pointcut-ref="fuga" method="foo"/>
         <aop:after pointcut-ref="fuga" method="var"/>
         <aop:around pointcut-ref="fuga" method="hoge"/>
     </aop:aspect>
</aop:config>




                                                           43
それでも書きたい定義ファイル(2)
<aop:config>
   <aop:aspect id="myAspect2" ref="aspectMessage2">
       <aop:pointcut id="fuga2"
              expression="execution(* getMessage())"/>
       <aop:after pointcut-ref="fuga2" method="hoge"/>
   </aop:aspect>
</aop:config>
・・・
<bean id="aspectMessage" class="sample.aop.AspectMsg" />
<bean id="aspectMessage2" class="sample.aop.AspectMsg2" />
・・・




                                                         44
コーディング例
public class AspectMessage {

  public void after() {
    // メソッド終了後に動作するAdvice
    System.out.println("after called");
  }

  public void before() {
    // メソッド開始時に動作するAdvice
    System.out.println("before called");
  }

・・・以下省略




                                           45
問題 消すことできる?

<aop:config>
   <aop:aspect id="myAspect2" ref="aspectMessage2">
       <aop:pointcut id="fuga2"
              expression="execution(* getMessage())"/>
                   beanを定義している2行	
       <aop:after pointcut-ref="fuga2" method="hoge"/>
   </aop:aspect>
</aop:config>
・・・
<bean id="aspectMessage2" class="sample.aop.AspectMsg2" />
<bean id="aspectMessage2" class="sample.aop.AspectMsg" />
・・・



                                                             46
今までの知識を使ってWebアプリケーションを改善する


DIとAOPのまとめ
アーキテクチャ・リファクタリング

                             47
アーキテクチャ・リファクタリング(1/5)

                                  Employee
                       Employee
          FindAction               MySql     RDB
                        Service
                                    Dao
 ブラウザ	



•  表示と永続化のフレームワーク導入済
•  インタフェース未使用(もちろんDI,AOPも)
 ‒  チーム開発がしずらい
 ‒  変更、機能拡張、テストが容易ではない
•  連続性も阻害
 ‒  Conecctionの引き回し、検査時例外の伝搬


                                                   48
アーキテクチャ・リファクタリング(2/5)
インタフェースの導入	
                        Employee                    Employee
                        Service                     Dao
                                   <<Singleton>>               <<Singleton>>
           FindAction               Employee                    Employee       MySQL
                                   ServiceImpl                  DaoMySql
ブラウザ	
                                                                          RDB
            利用	
              利用	
                                             生成	
                      生成	
                                         Factory

   •  メリット
         ‒  インタフェースを区切りとして、チーム開発がやりやすくなった
         ‒  変更、機能拡張、テストが容易になった
   •  デメリット
         ‒  Factoryを実装する必要がある
         ‒  クラスはFactoryに依存する

                                                                                  49
アーキテクチャ・リファクタリング(3/5)

DIの導入	
                         Employee                 Employee
                         Service                  Dao
                                     Employee                Employee
         FindAction                                                     MySQL
                                    ServiceImpl              DaoMySql

ブラウザ	
                                                                   RDB
                                            生成	
                 生成	

             Injection                               Injection
                                    DIコンテナ	


  •  Factoryを実装する必要がない
  •  クラスはDIコンテナに依存しない

                                                                           50
アーキテクチャ・リファクタリング(4/5)
  DIコンテナ導入後のソースコード	

                •    トランザクション管理やログ出力、例外処理が残っている
  Employee            ‒  分岐が多いため、テストの量が多くなる
 ServiceImpl          ‒  共通化できる部分が隠蔽できていない
                      ‒  例外処理とConnectionの引き渡しによる連続性の阻害がある

public class EmployeeServiceImpl"                       } catch(Exception e) {!
 implements EmployeeService{"                             conn.rollback();!
 @Autowired!                                              ・・・	
 private EmployeeDao dao;"                               } finally {!
 ・・・	
                                                    conn.close();!
 public List findAll() throws Exception {!                 ・・・	
  if(Log.flag) { System.out.println(“***Start”); }!       }!
  Connection conn = null;"                              if(Log.flag) { System.out.println(“***End”); }"
  ・・・	
                                                 return employeeList;"
   //EmployeeDao dao !                                }"
  //     = (EmployeeDao)Factory.create(KEY);!
                                                     ・・・	
  List employeeList = null;"
  try {"
     employeeList = dao.findAll(conn);"
     conn.commit();!


                                                                                                 51
アーキテクチャ・リファクタリング(5/5)
     AOP導入後のソースコード	
•    共通ライブラリを廃止してAOPを導入
•    連続性の確保
     ‒  トランザクション管理、ログ出力、例外処理はAOPで実現しているため、ソースコード上から
        はなくなっている
     ‒  Advice実装されており、なくなっている訳ではない
•    ソースコードの記述量が減り、バグの数も低下、開発者の作業も軽減
•    テストの容易性も向上

     public class EmployeeServiceImpl !    書くことがなくなりました・・・!
            implements EmploeeService {!
        @Autowired!
        private EmployeeDao dao;!

          public List findAll() {!
            return dao.findAll();!
         }!
     ・・・	




                                                              52
アーキテクチャ・リファクタリングの嘘
•  AOPで業務例外(例えば、在庫がなかった時に
   どうする)は処理できないから、そんなに奇麗
   に例外は消えない(多分…)
•  だって、AOPを使う基盤チームは業務を知らな
   い(多分…)。だから、業務例外はAOPで提供
   できない
•  それに業務例外がAOPになったら、業務プログ
   ラムが読めない!
•  そもそも、業務例外にExceptionを使うのって
   どうよ(!?)という問題でもある


                           53
かる∼く
Spring MVC


             54
Spring MVCとは
•  Spring Frameworkに含まれるWeb
   MVCフレームワーク
 ‒  初期のSpring Frameworkの段階から含まれ
    ている
 ‒  StrutsやJSFと競合
•  特徴
 ‒  DIコンテナとの親和性
 ‒  きれいな設計
   •  インタフェースを使用して部品化


                              55
Spring MVCのController

DIの導入	
                         Employee                 Employee
                         Service                  Dao
                                     Employee                Employee
         XxxController                                                  MySQL
                                    ServiceImpl              DaoMySql

ブラウザ	
                                                                   RDB
                  生成	
                      生成	
                 生成	

                                                     Injection
              Injection
                                    DIコンテナ	




                                                                           56
難しいと評判(?)のSpring MVC
•  Spring1.xのSpring MVC
  ‒  設定が難しい
  ‒  作り方がよくわからない
  ‒  日本語の情報が少ない
  ‒  そもそも知らない




                          57
簡単になったSpring MVC
•  Spring 3.xのSpring MVC
  ‒  Springの新機能を導入
    •  アノテーションにより設定がシンプルになり、わ
       かりやすくなった
    •  component-scanにより設定ファイルが最低限で
       すむようになった
  ‒  あいかわらず日本語の情報は少ない
    •  英語のマニュアルを読みましょう




                                  58
デモ
•  STS(SpringSource Tool Suite)で作成
 1.  メニューからNew->Project
 2.  SpringSource Tool Suite->Spring
     Template Projectを選択
 3.  Spring MVC Projectを選択




                                       59
動作概要

                            <<controller>>
                                                              Model	
                            HomeController	
            "serverTime"	


                                                                Date
                            home()	
            ③	
           (現在の日時)	

               Dispatcher
ブラウザ	
   ①	
     Servlet
               (ほか色々)	



                                   <<jsp>>
                            /WEB-INF/views/home.jsp	
                ⑥	




                                                                         60
HomeController
                 DIコンテナにより
                 自動で読み込まれる
                 (component-scan)	
                                        HTTPメソッドがGETで
@Controller                             「/」へアクセスした際に
public class HomeController {           実行される	

    @RequestMapping(value = "/", method = GET)
    public String home(Model model) { Viewに渡したいオブジェクトを
      Date date = new Date();         設定する	

        model.addAttribute("serverTime", date);

        return "home";
    }
                            View名をreturnする	
}

                                         ※少し手を加えシンプルにしています	
                                                          61
home.jsp

<html>
<head>
 <title>Home</title>                    Modelに設定したオブジェクトは
</head>                                 自動的にHttpServletRequestに
                                        設定されている	
<body>
<h1>Hello world! </h1>
<p>The time on the server is ${serverTime}.</p>
</body>
</html>

                                   ※少し手を加えシンプルにしています	




                                                              62
@RequestMapping色々

// 一つのメソッドに複数のURLを割り当て
@RequestMapping({"/", "/home"})
public String home() { ・・・

// 一つのURLでHTTPメソッドごとにメソッドを切り分け
@RequestMapping(value="/foo", method=GET)
public String doGetMethod() { ・・・

@RequestMapping(value="/foo", method=POST)
public String doPostMethod() { ・・・



                                             63
引数色々①
// リクエストパラメータを取得(「/person?id=10」などでアクセス)
@RequestMapping(value = "/person", method=GET)
public String showPerson1 (
     @RequestParam("id") int id, Model model) {
   Person person = findById(id);
   model.addAttribute("person", person);
   ・・・

// URLの値を取得(「/person/10」などでアクセス)
@RequestMapping(value = "/person/{id}", method=GET)
public String showPerson2(
     @PathVariable("id") int id, Model model) {
   Person person = findById(id);
   model.addAttribute("person", person);
   ・・・

                                                      64
引数色々②
// 画面からの入力をマッピング(formからデータを送信)
@RequestMapping(value = "/person", method = POST)
public String registerPerson(@ModelAttribute Person person) {
   register(person);
   ・・・



// ほかにも色々
@RequestMapping("/foo")
public String foo(
     Model model, WebRequest req, WebResponse res,
     Cookie cookie, Locale locale,
     HttpServletRequest sreq, HttpServletResponse sres) {
   ・・・

                                                                65
その他の機能
•  Session管理
   ‒  @SessionAttributes(model名)をクラスに設定すると、Modelに追加
      したオブジェクトはHttpSessionに追加される
   ‒  @ModelAttribute(model名)を引数に設定すると、Sessionのオブジェ
      クトが引数に渡される
   ‒  SessionStatus#setComplete()でHttpSession内のオブジェクトが破
      棄される
      •  ControllerごとにSession内のオブジェクトを管理可能
•  入力チェック(Validation)
   ‒  JSR-303(Bean Validation)に対応
      •  @NotNull String id;
      •  @Length(max = 30) String name;
   ‒  Validation対象の引数に@Validを設定する
•  例外処理
   ‒  例外発生時に実行するメソッドに@ExceptionHandler
      (FooException.class)

                                                      66
BON VOYAGE!




              67

Spring3.1概要 AOP & MVC

  • 1.
  • 2.
    問題 •  インスタンス変数に宣言したのと同じ型 のインスタンスをインジェクションして もらうためのアノテーションは? •  インスタンス化され、インジェクション されるために、クラス宣言の前に書くア ノテーションは? •  結局、DIコンテナって何のためにあるの か? 2
  • 3.
    前回の続き •  SpringのDIを使って、部品化はできた (気がする) •  でも・・・ ‒  ログなどの共通処理部分が部品に残っている ‒  例外処理も部品化を壊している •  それに・・・ ‒  トランザクション管理は面倒だし、難しい 3
  • 4.
  • 5.
    AOPを使ってもっと部品化する •  AOPを使えば処理を後からクラスに追加できる ‒ 例:トレースログを追加する find()を呼ぶ DaoImpl ServiceImpl find() Dao public class DaoImpl extends Dao{ >java ・・・ ・・・ 16:00:01 *Start* find() DaoImpl public List find() { 16:00:02 *End* find() DaoImpl List list = select(); 17:02:12 *Start* find() DaoImpl return list; 17:02:13 *End* find() DaoImpl } } 実行結果 5
  • 6.
    AOPをもう少し正しく Adviceを追加できるときが Joinpointとなる Joinpointを条件で絞り込む フィルター Aspect Pointcut Advice ソースコード Pointcut Advice Joinpoint 追加したい処理 6
  • 7.
    Joinpoint •  Adviceを追加できるとき(ポイント) •  AOPの仕様であり、実装者は変更できない • Joinpointの仕様例 ‒  メソッドの開始時、終了時 ‒  プロパティが利用されたとき DaoImpl add() delete() find() update() Joinpoint 例:メソッドの開始時や終了時 7
  • 8.
    Pointcut •  Joinpointに達した命令を、Adviceまで到 達させるか否かフィルタリングするフィ ルター Joinpointを条件「add*」で絞り込む DaoImpl Pointcut add() delete() Advice ServiceImpl find() Dao update() Joinpoint 追加したい処理 8
  • 9.
    Befor Advice •  Joinpointの前で実行される メソッドの実行前に割り込む method() Before Advice Client Servant return Exception 9
  • 10.
    After Advice •  Joinpointの後で実行される method() Client Servant After Advice return Exception メソッドの実行後に割り込む 10
  • 11.
    After Returning Advice • Joinpointが正常終了した後に実行される method() return Client After Returning Advice Servant Exception メソッドの実行後に正常終了時 に割り込む 11
  • 12.
    Around Advice •  Joinpointの前後で実行される method() method() Client Around Advice return Servant Exception メソッドの実行前と実行後、例 外発生時にも割り込む 12
  • 13.
    Throw Advice •  Joinpointで例外が発生した時に実行される method() Client Servant return Throw Advice Exception 例外発生時に割り 込む 13
  • 14.
    問題 •  メソッドの開始と終了のログをとりた かったら? •  メソッドが正常終了したログをとりた かったら? •  メソッドが異常終了したときのログをと りたかったら? 14
  • 15.
    AOPの仕組み例 •  Proxyベースの場合、ProxyオブジェクトはSpringが自動生成する :Bean :Advice 処理の 処理の依頼 依頼 Adviceの呼び出し :Client :Proxy :Spring Interface 自動生成 Bean定義 Pointcutの参照 ファイル 1-15
  • 16.
    AOPの主な利用方法 •  各クラスに記述されている同一の処理を抜き出し、ひと まとめにして、既存のクラスに後から追加する ‒  ライブラリとの違い •  ライブラリは呼び出さないといけない •  AOPは勝手に追加される •  追加すると便利な処理 ‒  トランザクション管理 •  トランザクション管理は難しいくプログラマに任せられない ‒  ログ管理 •  メソッドの開始と終了のトレースログが正しく出力されない ‒  誰もフォーマットを守らない ‒  トレースログを追加し忘れる ‒  例外管理 •  処理の途中でExceptionが握りつぶされてしまう ‒  Exceptionを実行時例外にする 16
  • 17.
    AOPでやらない方が良いこと •  個別の処理(特定業務の処理、デバッ グ) ‒  プログラマが個別にAOPをいれるのは不可 •  そこで何をやっているのかが分からなくなる •  業務アプリのプログラマではなく、基盤 チームとかが使う技術!? 17
  • 18.
    SpringのAOP •  定義ファイルの利用 ‒ Spring1.x系では基本 •  アノテーションの利用 ‒  Spring2.x系以降、アノテーションの利用が増 えている(大規模開発や大手SI便だでは定義 ファイルの利用が多い) 18
  • 19.
    問題 書いてないけど? •  例えば ‒  アノテーション •  メリット:定義ファイルの管理が不要 •  デメリット:プログラマにアノテーションを意識 ‒  とか、定義ファイルのメリットとかデメリット 19
  • 20.
  • 21.
    アスペクトの例 •  アノテーションの利用 @Aspect public classAspectMessage { @After("execution(* exMethod())") public void hoge() { // メソッド終了後に動くAdvice System.out.println("after called"); } } 21
  • 22.
    アノテーション一覧 •  アノテーションを利用したAOP ‒  Bean定義ファイルの記述が簡潔になる ‒  ソースコードに記述することで管理が煩雑 アノテーション 説明 @Aspect Adviceとなるクラスを指定するアノテーション @Around Around Adviceとなるメソッドを指定するアノテーション @Before Before Adviceとなるメソッドを指定するアノテーション @After After Adviceとなるメソッドを指定するアノテーション @AfterReturning After Returning Adviceとなるメソッドを指定するアノテーション @AfterThrowing After Throwing Adviceとなるメソッドを指定するアノテーション 22
  • 23.
    アドバイス詳細(1) •  Before, After ‒  @After( Primitiveポイントカット ) •  メソッド名は任意、メソッドのパラメータと戻り値はなしでも可能。 メソッド内で、アスペクト対象となっているメソッド名やパラメー タ,戻り値などの取得をする場合は、パラメータにJoinPoint •  メソッド内で、アスペクト対象となっているメソッドを呼び出す必 要はない @Before(“execution(* exMethod())”) public void hoge() { ・・・ } 23
  • 24.
    アドバイス詳細(2) •  Around ‒  @Around( Primitiveポイントカット ) •  メソッド名は任意、メソッドのパラメータには必 ずProceedingJoinPointが必要、戻り値はアスペ クト対象のメソッドにあわせる •  メソッド内で、アスペクト対象となっているメ ソッドを呼び出す必要がある ‒  ProceedingJoinPoint#proceed()メソッド »  Object proceed() throws Trowable •  メソッド内で、アスペクト対象となっているメ ソッド名やメソッドのパラメータの取得は ProceedingJoinPointを介しておこなう 24
  • 25.
    アドバイス詳細(3) •  Around ‒  戻り値はアスペクト対象にあわせる public String getMessage() { return “hello!”; アスペクト対象のメソッド } @Around(“execution(* getMessage())”) public String fuga(ProceedingJoinPoint pjp) throws Throwable {; String msg = (String)pjp.proceed(); return msg; } 25
  • 26.
    アドバイス詳細(4) public int getFigure(){ return 100; アスペクト対象のメソッド } @Around(“execution(* getFigure())”) public int fuga(ProceedingJoinPoint pjp) throws Throwable {; Integer figure= (Integer)pjp.proceed(); return figure.intValue(); } 26
  • 27.
    アドバイス詳細(5) •  Around ‒  ProceedingJoinPointの使い方 メソッド名の取得 Signature sig = pjp.getSignature(); System.out.println("Sig: " + sig.getName()); パラメータの取得(最初のパラメータ) Object[] os = pjp.getArgs(); System.out.println("Args: " + os[0]); 27
  • 28.
    問題∼重複してたらどうなる? @Around(“execution(* add())”) public inthoge (ProceedingJoinPoint pjp) throws Throwable {; Integer figure= (Integer)pjp.proceed(); return figure.intValue(); これと同じhogehogeメソッド } が存在したら? private int x; public int add(int i) { x = x + i; アスペクト対象のメソッド return x; } 28
  • 29.
    アドバイス詳細(6) •  AfterReturning ‒  @AfterReturning(value= Primitiveポイント カット , returnig = 戻り値の変数名 ) •  メソッド名は任意、メソッドのパラメータはアス ペクト対象となっているメソッドの戻り型とアノ テーションのretuning属性で指定した変数名 •  メソッド内で、アスペクト対象となっているメ ソッドを呼び出す必要はない 29
  • 30.
    アドバイス詳細(7) public String exMethod(){ return “hello!”; アスペクト対象のメソッド } @AfterReturning(value=“execution(* exMethod())”, returning=“ret”) public String hoge(String ret) { System.out.println(“Return: “ + ret); } 30
  • 31.
    アドバイス詳細(8) •  AfterThrowing ‒  @AfterReturning(value= Primitiveポイント カット , throwing = 例外の変数名 ) •  メソッド名は任意、メソッドのパラメータはアス ペクト対象となっているメソッドの戻り型とアノ テーションのthrowing属性で指定した変数名 •  メソッド内で、アスペクト対象となっているメ ソッドを呼び出す必要がない 31
  • 32.
    アドバイス詳細(9) @AfterThrowing(value=“execution(* exMethod())”,                              throwing=“ex”) public String foo(HogeException ex) { System.out.println(“Exception Msg: “ + ex.getMessage()); } 32
  • 33.
    アドバイス詳細(10) •  AfterThrowing @AfterThrowing(value=“execution(*exMethod())”, throwing=“ex”) public String foo(Throwable ex) { System.out.println(“Exception Msg: “ + ex.getMessage()); } @AfterThrowing(value=“execution(* exMethod()”, throwing=“ex”) public String var(HogeException ex) { System.out.println(“Exception Msg: “ + ex.getMessage()); } @AfterThrowing(value=“execution(* exMethod()”, throwing=“ex”) public String hoge(Exception ex) { System.out.println(“Exception Msg: “ + ex.getMessage()); } 33
  • 34.
    問題∼どのアドバイス? •  前ページの実装があったとき、どこかの プログラムがExceptionを投げてよこしま した •  どのアドバイスが動くでしょう? 34
  • 35.
    Primitiveポイントカット Primitive ポイントカット 概要 execution 呼出先の「メソッド」、「コンストラクタ」を指定する。 within 呼出元の「クラス」を指定する。
 withinをPointcutに指定すると、指定されたクラスから呼出される、メソッ ド等が選択されることになる。
 対象は、指定されたクラスで宣言されたメソッドに限定され、指定された クラスの親クラスで宣言されたメソッド内は対象外となる。 this 呼出元の「クラス」を指定する。
 thisをPointcutに指定すると、指定されたクラスから呼出される、メソッド 等が選択されることになる。
 withinとは、親クラスで定義されたメソッドの呼出しも対象とする点が異な る。 target 呼出先の「クラス」を指定する。
 ただし、呼出先のstaticフィールドは対象外となる。 args 呼出先「メソッド」の引数の型を指定する。 ※Primitiveポイントカット:あらかじめ用意されているポイントカットのこと 35
  • 36.
    executionの基本構文 •  execution(メソッドの修飾子△メソッドの戻り値型△ パッケージ.クラスまたはインタフェース.メソッド名 (仮引数の型|,仮引数の型…|) △throws 例外) •  「メソッドの修飾子(publicやprivateは省略可能)」や 「throws△例外」は省略することが可能 •  メソッドの戻り値型、パッケージやクラス名、インタ フェース名にはワイルドカード(*)の利用が可能 •  仮引数に(..)を記述すると任意の個数の引数と一致さ せることが可能 36
  • 37.
    ポイントカットで利用できる論理演算子 論理 説明 演算子 || または 論理和を意味する論理演算子 or 例) execution(* *..AopExBean.exMethod()) or execution(* *..AopExBeanParent.exMethod())  →AopExBeanのメソッドexMethodまたはAopExBeanParentのメソッドexMethodを指定 && また 論理積を意味する論理演算子 は and 例) execution(* *..AopExBean.exMethod()) && execution(* *..AopExBeanParent.exMethod())  →AopExBeanのメソッドexMethodまたはAopExBeanParentのメソッドexMethodを指定 ! または 否定を意味する論理演算子。 not 例) execution(* exMethod()) and not execution(* *..AopExBeanParent.*())  →AopExBeanParent以外のクラス(インタフェース)のメソッドexMethodを指定 37
  • 38.
    コーディング例(1) @Aspect public class AspectMessage{ @After("execution(* exMethod())") public void after() { // メソッド終了後に動作するAdvice System.out.println("after called"); } @Before("execution(* exMethod())") public void before() { // メソッド開始時に動作するAdvice System.out.println("before called"); } 38
  • 39.
    コーディング例(2) @Around("execution(* exMethod())") publicvoid around(ProceedingJoinPoint pjp) throws Throwable { // メソッド呼出の前後に動作するAdvice System.out.println("pre proceed"); pjp.proceed(); System.out.println("post proceed"); } 39
  • 40.
    コーディング例(3) @AfterReturning(value="execution(* exMethod())", returning="ret") public void afterReturning(String ret) { // メソッド呼出が例外の送出なしに終了した際に動作するAdvice System.out.println("after returning called"); System.out.println("return value = " + ret); } @AfterThrowing(value="execution(* exMethod())", throwing="ex") public void afterThrowing(Throwable ex) { // メソッド呼出が例外の送出なしに終了した際に動作するAdvice System.out.println("after throwing called"); System.out.println("exception value = " + ex.toString()); } } 40
  • 41.
    定義ファイル <?xml version="1.0" encoding="UTF-8"?> <beansxmlns=http://www.springframework.org/schema/beans xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:context=http://www.springframework.org/schema/context xmlns:aop=http://www.springframework.org/schema/aop xsi:schemaLocation=” http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> … <aop:aspectj-autoproxy/> … </beans> 41
  • 42.
  • 43.
    それでも書きたい定義ファイル(1) ・・・ <aop:config> <aop:aspect id="myAspect" ref="aspectMessage"> <aop:pointcut id="fuga" expression="execution(* getMessage())"/> <aop:before pointcut-ref="fuga" method="foo"/> <aop:after pointcut-ref="fuga" method="var"/> <aop:around pointcut-ref="fuga" method="hoge"/> </aop:aspect> </aop:config> 43
  • 44.
    それでも書きたい定義ファイル(2) <aop:config> <aop:aspect id="myAspect2" ref="aspectMessage2"> <aop:pointcut id="fuga2" expression="execution(* getMessage())"/> <aop:after pointcut-ref="fuga2" method="hoge"/> </aop:aspect> </aop:config> ・・・ <bean id="aspectMessage" class="sample.aop.AspectMsg" /> <bean id="aspectMessage2" class="sample.aop.AspectMsg2" /> ・・・ 44
  • 45.
    コーディング例 public class AspectMessage{ public void after() { // メソッド終了後に動作するAdvice System.out.println("after called"); } public void before() { // メソッド開始時に動作するAdvice System.out.println("before called"); } ・・・以下省略 45
  • 46.
    問題 消すことできる? <aop:config> <aop:aspect id="myAspect2" ref="aspectMessage2"> <aop:pointcut id="fuga2" expression="execution(* getMessage())"/> beanを定義している2行 <aop:after pointcut-ref="fuga2" method="hoge"/> </aop:aspect> </aop:config> ・・・ <bean id="aspectMessage2" class="sample.aop.AspectMsg2" /> <bean id="aspectMessage2" class="sample.aop.AspectMsg" /> ・・・ 46
  • 47.
  • 48.
    アーキテクチャ・リファクタリング(1/5) Employee Employee FindAction MySql RDB Service Dao ブラウザ •  表示と永続化のフレームワーク導入済 •  インタフェース未使用(もちろんDI,AOPも) ‒  チーム開発がしずらい ‒  変更、機能拡張、テストが容易ではない •  連続性も阻害 ‒  Conecctionの引き回し、検査時例外の伝搬 48
  • 49.
    アーキテクチャ・リファクタリング(2/5) インタフェースの導入 Employee Employee Service Dao <<Singleton>> <<Singleton>> FindAction Employee Employee MySQL ServiceImpl DaoMySql ブラウザ RDB 利用 利用 生成 生成 Factory •  メリット ‒  インタフェースを区切りとして、チーム開発がやりやすくなった ‒  変更、機能拡張、テストが容易になった •  デメリット ‒  Factoryを実装する必要がある ‒  クラスはFactoryに依存する 49
  • 50.
    アーキテクチャ・リファクタリング(3/5) DIの導入 Employee Employee Service Dao Employee Employee FindAction MySQL ServiceImpl DaoMySql ブラウザ RDB 生成 生成 Injection Injection DIコンテナ •  Factoryを実装する必要がない •  クラスはDIコンテナに依存しない 50
  • 51.
    アーキテクチャ・リファクタリング(4/5) DIコンテナ導入後のソースコード •  トランザクション管理やログ出力、例外処理が残っている Employee ‒  分岐が多いため、テストの量が多くなる ServiceImpl ‒  共通化できる部分が隠蔽できていない ‒  例外処理とConnectionの引き渡しによる連続性の阻害がある public class EmployeeServiceImpl" } catch(Exception e) {! implements EmployeeService{" conn.rollback();! @Autowired! ・・・ private EmployeeDao dao;" } finally {! ・・・ conn.close();!  public List findAll() throws Exception {! ・・・ if(Log.flag) { System.out.println(“***Start”); }! }! Connection conn = null;" if(Log.flag) { System.out.println(“***End”); }" ・・・ return employeeList;" //EmployeeDao dao ! }" // = (EmployeeDao)Factory.create(KEY);! ・・・ List employeeList = null;" try {" employeeList = dao.findAll(conn);" conn.commit();! 51
  • 52.
    アーキテクチャ・リファクタリング(5/5) AOP導入後のソースコード •  共通ライブラリを廃止してAOPを導入 •  連続性の確保 ‒  トランザクション管理、ログ出力、例外処理はAOPで実現しているため、ソースコード上から はなくなっている ‒  Advice実装されており、なくなっている訳ではない •  ソースコードの記述量が減り、バグの数も低下、開発者の作業も軽減 •  テストの容易性も向上 public class EmployeeServiceImpl ! 書くことがなくなりました・・・! implements EmploeeService {! @Autowired! private EmployeeDao dao;!   public List findAll() {! return dao.findAll();! }! ・・・ 52
  • 53.
    アーキテクチャ・リファクタリングの嘘 •  AOPで業務例外(例えば、在庫がなかった時に どうする)は処理できないから、そんなに奇麗 に例外は消えない(多分…) •  だって、AOPを使う基盤チームは業務を知らな い(多分…)。だから、業務例外はAOPで提供 できない •  それに業務例外がAOPになったら、業務プログ ラムが読めない! •  そもそも、業務例外にExceptionを使うのって どうよ(!?)という問題でもある 53
  • 54.
  • 55.
    Spring MVCとは •  SpringFrameworkに含まれるWeb MVCフレームワーク ‒  初期のSpring Frameworkの段階から含まれ ている ‒  StrutsやJSFと競合 •  特徴 ‒  DIコンテナとの親和性 ‒  きれいな設計 •  インタフェースを使用して部品化 55
  • 56.
    Spring MVCのController DIの導入 Employee Employee Service Dao Employee Employee XxxController MySQL ServiceImpl DaoMySql ブラウザ RDB 生成 生成 生成 Injection Injection DIコンテナ 56
  • 57.
    難しいと評判(?)のSpring MVC •  Spring1.xのSpringMVC ‒  設定が難しい ‒  作り方がよくわからない ‒  日本語の情報が少ない ‒  そもそも知らない 57
  • 58.
    簡単になったSpring MVC •  Spring3.xのSpring MVC ‒  Springの新機能を導入 •  アノテーションにより設定がシンプルになり、わ かりやすくなった •  component-scanにより設定ファイルが最低限で すむようになった ‒  あいかわらず日本語の情報は少ない •  英語のマニュアルを読みましょう 58
  • 59.
    デモ •  STS(SpringSource ToolSuite)で作成 1.  メニューからNew->Project 2.  SpringSource Tool Suite->Spring Template Projectを選択 3.  Spring MVC Projectを選択 59
  • 60.
    動作概要 <<controller>> Model HomeController "serverTime" Date home() ③ (現在の日時) Dispatcher ブラウザ ① Servlet (ほか色々) <<jsp>> /WEB-INF/views/home.jsp ⑥ 60
  • 61.
    HomeController DIコンテナにより 自動で読み込まれる (component-scan) HTTPメソッドがGETで @Controller 「/」へアクセスした際に public class HomeController { 実行される @RequestMapping(value = "/", method = GET) public String home(Model model) { Viewに渡したいオブジェクトを Date date = new Date(); 設定する model.addAttribute("serverTime", date); return "home"; } View名をreturnする } ※少し手を加えシンプルにしています 61
  • 62.
    home.jsp <html> <head> <title>Home</title> Modelに設定したオブジェクトは </head> 自動的にHttpServletRequestに 設定されている <body> <h1>Hello world! </h1> <p>The time on the server is ${serverTime}.</p> </body> </html> ※少し手を加えシンプルにしています 62
  • 63.
    @RequestMapping色々 // 一つのメソッドに複数のURLを割り当て @RequestMapping({"/", "/home"}) publicString home() { ・・・ // 一つのURLでHTTPメソッドごとにメソッドを切り分け @RequestMapping(value="/foo", method=GET) public String doGetMethod() { ・・・ @RequestMapping(value="/foo", method=POST) public String doPostMethod() { ・・・ 63
  • 64.
    引数色々① // リクエストパラメータを取得(「/person?id=10」などでアクセス) @RequestMapping(value ="/person", method=GET) public String showPerson1 ( @RequestParam("id") int id, Model model) { Person person = findById(id); model.addAttribute("person", person); ・・・ // URLの値を取得(「/person/10」などでアクセス) @RequestMapping(value = "/person/{id}", method=GET) public String showPerson2( @PathVariable("id") int id, Model model) { Person person = findById(id); model.addAttribute("person", person); ・・・ 64
  • 65.
    引数色々② // 画面からの入力をマッピング(formからデータを送信) @RequestMapping(value ="/person", method = POST) public String registerPerson(@ModelAttribute Person person) { register(person); ・・・ // ほかにも色々 @RequestMapping("/foo") public String foo( Model model, WebRequest req, WebResponse res, Cookie cookie, Locale locale, HttpServletRequest sreq, HttpServletResponse sres) { ・・・ 65
  • 66.
    その他の機能 •  Session管理 ‒  @SessionAttributes(model名)をクラスに設定すると、Modelに追加 したオブジェクトはHttpSessionに追加される ‒  @ModelAttribute(model名)を引数に設定すると、Sessionのオブジェ クトが引数に渡される ‒  SessionStatus#setComplete()でHttpSession内のオブジェクトが破 棄される •  ControllerごとにSession内のオブジェクトを管理可能 •  入力チェック(Validation) ‒  JSR-303(Bean Validation)に対応 •  @NotNull String id; •  @Length(max = 30) String name; ‒  Validation対象の引数に@Validを設定する •  例外処理 ‒  例外発生時に実行するメソッドに@ExceptionHandler (FooException.class) 66
  • 67.