Spring3.1 aop-mvc

1,433 views
1,350 views

Published on

JSUG 第2回初心者向け勉強会資料

0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,433
On SlideShare
0
From Embeds
0
Number of Embeds
7
Actions
Shares
0
Downloads
23
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Spring3.1 aop-mvc

  1. 1. AOPとMVCfor Beginner 2012/2/20
  2. 2. 問題•  インスタンス変数に宣言したのと同じ型 のインスタンスをインジェクションして もらうためのアノテーションは?•  インスタンス化され、インジェクション されるために、クラス宣言の前に書くア ノテーションは?•  結局、DIコンテナって何のためにあるの か? 2
  3. 3. 前回の続き•  SpringのDIを使って、部品化はできた (気がする)•  でも・・・ ‒  ログなどの共通処理部分が部品に残っている ‒  例外処理も部品化を壊している•  それに・・・ ‒  トランザクション管理は面倒だし、難しい 3
  4. 4. Springの説明の前に・・・AOPの基本 4
  5. 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. 6. AOPをもう少し正しくAdviceを追加できるときが Joinpointとなる Joinpointを条件で絞り込む フィルター Aspect Pointcut Advice ソースコード Pointcut Advice Joinpoint 追加したい処理 6
  7. 7. Joinpoint•  Adviceを追加できるとき(ポイント)•  AOPの仕様であり、実装者は変更できない•  Joinpointの仕様例 ‒  メソッドの開始時、終了時 ‒  プロパティが利用されたとき DaoImpl add() delete() find() update() Joinpoint 例:メソッドの開始時や終了時 7
  8. 8. Pointcut•  Joinpointに達した命令を、Adviceまで到 達させるか否かフィルタリングするフィ ルター Joinpointを条件「add*」で絞り込む DaoImpl Pointcut add() delete() AdviceServiceImpl find() Dao update() Joinpoint 追加したい処理 8
  9. 9. Befor Advice•  Joinpointの前で実行される メソッドの実行前に割り込む method() Before Advice Client Servant return Exception 9
  10. 10. After Advice•  Joinpointの後で実行される method() Client Servant After Advice return Exception メソッドの実行後に割り込む 10
  11. 11. After Returning Advice•  Joinpointが正常終了した後に実行される method() return Client After Returning Advice Servant Exception メソッドの実行後に正常終了時 に割り込む 11
  12. 12. Around Advice•  Joinpointの前後で実行される method() method() Client Around Advice return Servant Exception メソッドの実行前と実行後、例 外発生時にも割り込む 12
  13. 13. Throw Advice•  Joinpointで例外が発生した時に実行される method() Client Servant return Throw Advice Exception 例外発生時に割り 込む 13
  14. 14. 問題•  メソッドの開始と終了のログをとりた かったら?•  メソッドが正常終了したログをとりた かったら?•  メソッドが異常終了したときのログをと りたかったら? 14
  15. 15. AOPの仕組み例•  Proxyベースの場合、ProxyオブジェクトはSpringが自動生成する :Bean :Advice 処理の 処理の依頼 依頼 Adviceの呼び出し :Client :Proxy :Spring Interface 自動生成 Bean定義 Pointcutの参照 ファイル 1-15
  16. 16. AOPの主な利用方法•  各クラスに記述されている同一の処理を抜き出し、ひと まとめにして、既存のクラスに後から追加する ‒  ライブラリとの違い •  ライブラリは呼び出さないといけない •  AOPは勝手に追加される•  追加すると便利な処理 ‒  トランザクション管理 •  トランザクション管理は難しいくプログラマに任せられない ‒  ログ管理 •  メソッドの開始と終了のトレースログが正しく出力されない ‒  誰もフォーマットを守らない ‒  トレースログを追加し忘れる ‒  例外管理 •  処理の途中でExceptionが握りつぶされてしまう ‒  Exceptionを実行時例外にする 16
  17. 17. AOPでやらない方が良いこと•  個別の処理(特定業務の処理、デバッ グ) ‒  プログラマが個別にAOPをいれるのは不可 •  そこで何をやっているのかが分からなくなる•  業務アプリのプログラマではなく、基盤 チームとかが使う技術!? 17
  18. 18. SpringのAOP•  定義ファイルの利用 ‒  Spring1.x系では基本•  アノテーションの利用 ‒  Spring2.x系以降、アノテーションの利用が増 えている(大規模開発や大手SI便だでは定義 ファイルの利用が多い) 18
  19. 19. 問題 書いてないけど?•  例えば ‒  アノテーション •  メリット:定義ファイルの管理が不要 •  デメリット:プログラマにアノテーションを意識 ‒  とか、定義ファイルのメリットとかデメリット 19
  20. 20. アノテーションを使ったAOP 20
  21. 21. アスペクトの例•  アノテーションの利用@Aspectpublic class AspectMessage { @After("execution(* exMethod())") public void hoge() { // メソッド終了後に動くAdvice System.out.println("after called"); }} 21
  22. 22. アノテーション一覧•  アノテーションを利用したAOP ‒  Bean定義ファイルの記述が簡潔になる ‒  ソースコードに記述することで管理が煩雑 アノテーション 説明 @Aspect Adviceとなるクラスを指定するアノテーション @Around Around Adviceとなるメソッドを指定するアノテーション @Before Before Adviceとなるメソッドを指定するアノテーション @After After Adviceとなるメソッドを指定するアノテーション @AfterReturning After Returning Adviceとなるメソッドを指定するアノテーション@AfterThrowing After Throwing Adviceとなるメソッドを指定するアノテーション 22
  23. 23. アドバイス詳細(1)•  Before, After ‒  @After( Primitiveポイントカット ) •  メソッド名は任意、メソッドのパラメータと戻り値はなしでも可能。 メソッド内で、アスペクト対象となっているメソッド名やパラメー タ,戻り値などの取得をする場合は、パラメータにJoinPoint •  メソッド内で、アスペクト対象となっているメソッドを呼び出す必 要はない @Before(“execution(* exMethod())”) public void hoge() { ・・・ } 23
  24. 24. アドバイス詳細(2)•  Around ‒  @Around( Primitiveポイントカット ) •  メソッド名は任意、メソッドのパラメータには必 ずProceedingJoinPointが必要、戻り値はアスペ クト対象のメソッドにあわせる •  メソッド内で、アスペクト対象となっているメ ソッドを呼び出す必要がある ‒  ProceedingJoinPoint#proceed()メソッド »  Object proceed() throws Trowable •  メソッド内で、アスペクト対象となっているメ ソッド名やメソッドのパラメータの取得は ProceedingJoinPointを介しておこなう 24
  25. 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. 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. 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. 28. 問題∼重複してたらどうなる?@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
  29. 29. アドバイス詳細(6)•  AfterReturning ‒  @AfterReturning(value= Primitiveポイント カット , returnig = 戻り値の変数名 ) •  メソッド名は任意、メソッドのパラメータはアス ペクト対象となっているメソッドの戻り型とアノ テーションのretuning属性で指定した変数名 •  メソッド内で、アスペクト対象となっているメ ソッドを呼び出す必要はない 29
  30. 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. 31. アドバイス詳細(8)•  AfterThrowing ‒  @AfterReturning(value= Primitiveポイント カット , throwing = 例外の変数名 ) •  メソッド名は任意、メソッドのパラメータはアス ペクト対象となっているメソッドの戻り型とアノ テーションのthrowing属性で指定した変数名 •  メソッド内で、アスペクト対象となっているメ ソッドを呼び出す必要がない 31
  32. 32. アドバイス詳細(9) @AfterThrowing(value=“execution(* exMethod())”,                              throwing=“ex”) public String foo(HogeException ex) { System.out.println(“Exception Msg: “ + ex.getMessage()); } 32
  33. 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. 34. 問題∼どのアドバイス?•  前ページの実装があったとき、どこかの プログラムがExceptionを投げてよこしま した•  どのアドバイスが動くでしょう? 34
  35. 35. Primitiveポイントカット Primitive ポイントカット 概要 execution 呼出先の「メソッド」、「コンストラクタ」を指定する。 within 呼出元の「クラス」を指定する。
 withinをPointcutに指定すると、指定されたクラスから呼出される、メソッ ド等が選択されることになる。
 対象は、指定されたクラスで宣言されたメソッドに限定され、指定された クラスの親クラスで宣言されたメソッド内は対象外となる。 this 呼出元の「クラス」を指定する。
 thisをPointcutに指定すると、指定されたクラスから呼出される、メソッド 等が選択されることになる。
 withinとは、親クラスで定義されたメソッドの呼出しも対象とする点が異な る。 target 呼出先の「クラス」を指定する。
 ただし、呼出先のstaticフィールドは対象外となる。 args 呼出先「メソッド」の引数の型を指定する。 ※Primitiveポイントカット:あらかじめ用意されているポイントカットのこと 35
  36. 36. executionの基本構文•  execution(メソッドの修飾子△メソッドの戻り値型△ パッケージ.クラスまたはインタフェース.メソッド名 (仮引数の型|,仮引数の型…|) △throws 例外)•  「メソッドの修飾子(publicやprivateは省略可能)」や 「throws△例外」は省略することが可能•  メソッドの戻り値型、パッケージやクラス名、インタ フェース名にはワイルドカード(*)の利用が可能•  仮引数に(..)を記述すると任意の個数の引数と一致さ せることが可能 36
  37. 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. 38. コーディング例(1)@Aspectpublic 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. 39. コーディング例(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
  40. 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. 41. 定義ファイル<?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
  42. 42. 定義ファイルには書かないでしょ!?AOP 42
  43. 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. 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. 45. コーディング例public class AspectMessage { public void after() { // メソッド終了後に動作するAdvice System.out.println("after called"); } public void before() { // メソッド開始時に動作するAdvice System.out.println("before called"); }・・・以下省略 45
  46. 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. 47. 今までの知識を使ってWebアプリケーションを改善するDIとAOPのまとめアーキテクチャ・リファクタリング 47
  48. 48. アーキテクチャ・リファクタリング(1/5) Employee Employee FindAction MySql RDB Service Dao ブラウザ •  表示と永続化のフレームワーク導入済•  インタフェース未使用(もちろんDI,AOPも) ‒  チーム開発がしずらい ‒  変更、機能拡張、テストが容易ではない•  連続性も阻害 ‒  Conecctionの引き回し、検査時例外の伝搬 48
  49. 49. アーキテクチャ・リファクタリング(2/5)インタフェースの導入 Employee Employee Service Dao <<Singleton>> <<Singleton>> FindAction Employee Employee MySQL ServiceImpl DaoMySqlブラウザ RDB 利用 利用 生成 生成 Factory •  メリット ‒  インタフェースを区切りとして、チーム開発がやりやすくなった ‒  変更、機能拡張、テストが容易になった •  デメリット ‒  Factoryを実装する必要がある ‒  クラスはFactoryに依存する 49
  50. 50. アーキテクチャ・リファクタリング(3/5)DIの導入 Employee Employee Service Dao Employee Employee FindAction MySQL ServiceImpl DaoMySqlブラウザ RDB 生成 生成 Injection Injection DIコンテナ •  Factoryを実装する必要がない •  クラスはDIコンテナに依存しない 50
  51. 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. 52. アーキテクチャ・リファクタリング(5/5) AOP導入後のソースコード •  共通ライブラリを廃止してAOPを導入•  連続性の確保 ‒  トランザクション管理、ログ出力、例外処理はAOPで実現しているため、ソースコード上から はなくなっている ‒  Advice実装されており、なくなっている訳ではない•  ソースコードの記述量が減り、バグの数も低下、開発者の作業も軽減•  テストの容易性も向上 public class EmployeeServiceImpl ! 書くことがなくなりました・・・! implements EmploeeService {! @Autowired! private EmployeeDao dao;!   public List findAll() {! return dao.findAll();! }! ・・・ 52
  53. 53. アーキテクチャ・リファクタリングの嘘•  AOPで業務例外(例えば、在庫がなかった時に どうする)は処理できないから、そんなに奇麗 に例外は消えない(多分…)•  だって、AOPを使う基盤チームは業務を知らな い(多分…)。だから、業務例外はAOPで提供 できない•  それに業務例外がAOPになったら、業務プログ ラムが読めない!•  そもそも、業務例外にExceptionを使うのって どうよ(!?)という問題でもある 53
  54. 54. かる∼くSpring MVC 54
  55. 55. Spring MVCとは•  Spring Frameworkに含まれるWeb MVCフレームワーク ‒  初期のSpring Frameworkの段階から含まれ ている ‒  StrutsやJSFと競合•  特徴 ‒  DIコンテナとの親和性 ‒  きれいな設計 •  インタフェースを使用して部品化 55
  56. 56. Spring MVCのControllerDIの導入 Employee Employee Service Dao Employee Employee XxxController MySQL ServiceImpl DaoMySqlブラウザ RDB 生成 生成 生成 Injection Injection DIコンテナ 56
  57. 57. 難しいと評判(?)のSpring MVC•  Spring1.xのSpring MVC ‒  設定が難しい ‒  作り方がよくわからない ‒  日本語の情報が少ない ‒  そもそも知らない 57
  58. 58. 簡単になったSpring MVC•  Spring 3.xのSpring MVC ‒  Springの新機能を導入 •  アノテーションにより設定がシンプルになり、わ かりやすくなった •  component-scanにより設定ファイルが最低限で すむようになった ‒  あいかわらず日本語の情報は少ない •  英語のマニュアルを読みましょう 58
  59. 59. デモ•  STS(SpringSource Tool Suite)で作成 1.  メニューからNew->Project 2.  SpringSource Tool Suite->Spring Template Projectを選択 3.  Spring MVC Projectを選択 59
  60. 60. 動作概要 <<controller>> Model HomeController "serverTime" Date home() ③ (現在の日時) Dispatcherブラウザ ① Servlet (ほか色々) <<jsp>> /WEB-INF/views/home.jsp ⑥ 60
  61. 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. 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. 63. @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
  64. 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. 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. 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. 67. BON VOYAGE! 67

×