View
1.533
Download
0
Embed Size (px)
DESCRIPTION
JSUG 第2回初心者向け勉強会資料
Citation preview
AOPとMVC for Beginner
2012/2/20
問題 • インスタンス変数に宣言したのと同じ型のインスタンスをインジェクションしてもらうためのアノテーションは?
• インスタンス化され、インジェクションされるために、クラス宣言の前に書くアノテーションは?
• 結局、DIコンテナって何のためにあるのか?
2
前回の続き • SpringのDIを使って、部品化はできた(気がする)
• でも・・・ ‒ ログなどの共通処理部分が部品に残っている ‒ 例外処理も部品化を壊している
• それに・・・ ‒ トランザクション管理は面倒だし、難しい
3
AOPの基本 Springの説明の前に・・・
4
AOPを使ってもっと部品化する • AOPを使えば処理を後からクラスに追加できる
‒ 例:トレースログを追加する
5
public class DaoImpl extends Dao{ ・・・ public List find() { List list = select(); return list; } }
>java ・・・ 16:00:01 *Start* find() DaoImpl 16:00:02 *End* find() DaoImpl 17:02:12 *Start* find() DaoImpl 17:02:13 *End* find() DaoImpl
DaoImpl
find() ServiceImpl
find()を呼ぶ
Dao
実行結果
AOPをもう少し正しく
6
Aspect
ソースコード
Pointcut
Pointcut
Advice
Advice
Joinpoint
Adviceを追加できるときがJoinpointとなる Joinpointを条件で絞り込む
フィルター
追加したい処理
Joinpoint • Adviceを追加できるとき(ポイント) • AOPの仕様であり、実装者は変更できない • Joinpointの仕様例
‒ メソッドの開始時、終了時 ‒ プロパティが利用されたとき
7
DaoImpl
add() delete() find() update() Joinpoint
例:メソッドの開始時や終了時
Pointcut • Joinpointに達した命令を、Adviceまで到達させるか否かフィルタリングするフィルター
8
DaoImpl
add() delete() find() update()
Joinpoint
Pointcut
Advice
追加したい処理
Joinpointを条件「add*」で絞り込む
ServiceImpl
Dao
Befor Advice • Joinpointの前で実行される
9
Client Servant
method()
return
Before Advice
Exception
メソッドの実行前に割り込む
After Advice • Joinpointの後で実行される
10
Client Servant
method()
return
Exception
メソッドの実行後に割り込む
After Advice
After Returning Advice • Joinpointが正常終了した後に実行される
11
Client Servant
method()
return
Exception
After Returning Advice
メソッドの実行後に正常終了時に割り込む
Around Advice • Joinpointの前後で実行される
12
Client Servant
method() return
Exception
Around Advice
method()
メソッドの実行前と実行後、例外発生時にも割り込む
Throw Advice • Joinpointで例外が発生した時に実行される
13
Client Servant
method()
return
Exception Throw Advice
例外発生時に割り込む
問題 • メソッドの開始と終了のログをとりたかったら?
• メソッドが正常終了したログをとりたかったら?
• メソッドが異常終了したときのログをとりたかったら?
14
AOPの仕組み例 • Proxyベースの場合、ProxyオブジェクトはSpringが自動生成する
1-15
:Proxy
:Bean
Interface :Client
Bean定義 ファイル
Adviceの呼び出し
自動生成
:Advice
:Spring
Pointcutの参照
処理の 依頼
処理の依頼
AOPの主な利用方法 • 各クラスに記述されている同一の処理を抜き出し、ひとまとめにして、既存のクラスに後から追加する ‒ ライブラリとの違い
• ライブラリは呼び出さないといけない • AOPは勝手に追加される
• 追加すると便利な処理 ‒ トランザクション管理
• トランザクション管理は難しいくプログラマに任せられない ‒ ログ管理
• メソッドの開始と終了のトレースログが正しく出力されない ‒ 誰もフォーマットを守らない ‒ トレースログを追加し忘れる
‒ 例外管理 • 処理の途中でExceptionが握りつぶされてしまう
‒ Exceptionを実行時例外にする
16
AOPでやらない方が良いこと • 個別の処理(特定業務の処理、デバッグ) ‒ プログラマが個別にAOPをいれるのは不可
• そこで何をやっているのかが分からなくなる
• 業務アプリのプログラマではなく、基盤チームとかが使う技術!?
17
SpringのAOP • 定義ファイルの利用
‒ Spring1.x系では基本 • アノテーションの利用
‒ Spring2.x系以降、アノテーションの利用が増えている(大規模開発や大手SI便だでは定義ファイルの利用が多い)
18
問題 書いてないけど? • 例えば
‒ アノテーション • メリット:定義ファイルの管理が不要 • デメリット:プログラマにアノテーションを意識
‒ とか、定義ファイルのメリットとかデメリット
19
AOP アノテーションを使った
20
アスペクトの例 • アノテーションの利用
21
@Aspect public class AspectMessage {
@After("execution(* exMethod())") public void hoge() { // メソッド終了後に動くAdvice System.out.println("after called"); } }
アノテーション一覧
アノテーション 説明 @Aspect Adviceとなるクラスを指定するアノテーション @Around Around Adviceとなるメソッドを指定するアノテーション @Before Before Adviceとなるメソッドを指定するアノテーション @After After Adviceとなるメソッドを指定するアノテーション @AfterReturning After Returning Adviceとなるメソッドを指定するアノテーション @AfterThrowing After Throwing Adviceとなるメソッドを指定するアノテーション
22
• アノテーションを利用したAOP ‒ Bean定義ファイルの記述が簡潔になる ‒ ソースコードに記述することで管理が煩雑
アドバイス詳細(1) • Before, After
‒ @After(“Primitiveポイントカット”) • メソッド名は任意、メソッドのパラメータと戻り値はなしでも可能。メソッド内で、アスペクト対象となっているメソッド名やパラメータ,戻り値などの取得をする場合は、パラメータにJoinPoint
• メソッド内で、アスペクト対象となっているメソッドを呼び出す必要はない
23
@Before(“execution(* exMethod())”) public void hoge() { ・・・ }
アドバイス詳細(2) • Around
‒ @Around(“Primitiveポイントカット”) • メソッド名は任意、メソッドのパラメータには必ずProceedingJoinPointが必要、戻り値はアスペクト対象のメソッドにあわせる
• メソッド内で、アスペクト対象となっているメソッドを呼び出す必要がある ‒ ProceedingJoinPoint#proceed()メソッド
» Object proceed() throws Trowable • メソッド内で、アスペクト対象となっているメソッド名やメソッドのパラメータの取得はProceedingJoinPointを介しておこなう
24
アドバイス詳細(3) • Around
‒ 戻り値はアスペクト対象にあわせる
25
public String getMessage() { return “hello!”; }
@Around(“execution(* getMessage())”) public String fuga(ProceedingJoinPoint pjp) throws Throwable {; String msg = (String)pjp.proceed(); return msg; }
アスペクト対象のメソッド
アドバイス詳細(4)
26
public int getFigure() { return 100; }
@Around(“execution(* getFigure())”) public int fuga(ProceedingJoinPoint pjp) throws Throwable {; Integer figure= (Integer)pjp.proceed(); return figure.intValue(); }
アスペクト対象のメソッド
アドバイス詳細(5) • Around
‒ ProceedingJoinPointの使い方
27
Signature sig = pjp.getSignature(); System.out.println("Sig: " + sig.getName());
メソッド名の取得
Object[] os = pjp.getArgs(); System.out.println("Args: " + os[0]);
パラメータの取得(最初のパラメータ)
問題~重複してたらどうなる?
28
@Around(“execution(* add())”) public int hoge (ProceedingJoinPoint pjp) throws Throwable {; Integer figure= (Integer)pjp.proceed(); return figure.intValue(); }
private int x; public int add(int i) { x = x + i; return x; }
アスペクト対象のメソッド
これと同じhogehogeメソッド が存在したら?
アドバイス詳細(6) • AfterReturning
‒ @AfterReturning(value=“Primitiveポイントカット”, returnig = “戻り値の変数名”) • メソッド名は任意、メソッドのパラメータはアスペクト対象となっているメソッドの戻り型とアノテーションのretuning属性で指定した変数名
• メソッド内で、アスペクト対象となっているメソッドを呼び出す必要はない
29
アドバイス詳細(7)
30
@AfterReturning(value=“execution(* exMethod())”, returning=“ret”) public String hoge(String ret) { System.out.println(“Return: “ + ret); }
public String exMethod() { return “hello!”; }
アスペクト対象のメソッド
アドバイス詳細(8) • AfterThrowing
‒ @AfterReturning(value=“Primitiveポイントカット”, throwing = “例外の変数名”) • メソッド名は任意、メソッドのパラメータはアスペクト対象となっているメソッドの戻り型とアノテーションのthrowing属性で指定した変数名
• メソッド内で、アスペクト対象となっているメソッドを呼び出す必要がない
31
アドバイス詳細(9)
32
@AfterThrowing(value=“execution(* exMethod())”, throwing=“ex”) public String foo(HogeException ex) { System.out.println(“Exception Msg: “ + ex.getMessage()); }
アドバイス詳細(10) • AfterThrowing
33
@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()); }
問題~どのアドバイス? • 前ページの実装があったとき、どこかのプログラムがExceptionを投げてよこしました
• どのアドバイスが動くでしょう?
34
Primitiveポイントカット Primitive ポイントカット 概要 execution 呼出先の「メソッド」、「コンストラクタ」を指定する。 within 呼出元の「クラス」を指定する。
withinをPointcutに指定すると、指定されたクラスから呼出される、メソッド等が選択されることになる。対象は、指定されたクラスで宣言されたメソッドに限定され、指定されたクラスの親クラスで宣言されたメソッド内は対象外となる。
this 呼出元の「クラス」を指定する。thisをPointcutに指定すると、指定されたクラスから呼出される、メソッド等が選択されることになる。withinとは、親クラスで定義されたメソッドの呼出しも対象とする点が異なる。
target 呼出先の「クラス」を指定する。ただし、呼出先のstaticフィールドは対象外となる。
args 呼出先「メソッド」の引数の型を指定する。
35
※Primitiveポイントカット:あらかじめ用意されているポイントカットのこと
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を指定
コーディング例(1)
38
@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"); }
コーディング例(2)
39
@Around("execution(* exMethod())") public void around(ProceedingJoinPoint pjp) throws Throwable { // メソッド呼出の前後に動作するAdvice System.out.println("pre proceed"); pjp.proceed(); System.out.println("post proceed"); }
コーディング例(3)
40
@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()); } }
定義ファイル
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>
AOP 定義ファイルには書かないでしょ!?
42
それでも書きたい定義ファイル(1)
43
・・・ <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>
それでも書きたい定義ファイル(2)
44
<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" /> ・・・
コーディング例
45
public class AspectMessage {
public void after() { // メソッド終了後に動作するAdvice System.out.println("after called"); }
public void before() { // メソッド開始時に動作するAdvice System.out.println("before called"); }
・・・以下省略
問題 消すことできる?
46
<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="aspectMessage2" class="sample.aop.AspectMsg2" /> <bean id="aspectMessage2" class="sample.aop.AspectMsg" /> ・・・
beanを定義している2行
DIとAOPのまとめ アーキテクチャ・リファクタリング
今までの知識を使ってWebアプリケーションを改善する
47
アーキテクチャ・リファクタリング(1/5)
• 表示と永続化のフレームワーク導入済 • インタフェース未使用(もちろんDI,AOPも)
‒ チーム開発がしずらい ‒ 変更、機能拡張、テストが容易ではない
• 連続性も阻害 ‒ Conecctionの引き回し、検査時例外の伝搬
48
FindAction Employee Service
Employee MySql Dao
RDB
ブラウザ
アーキテクチャ・リファクタリング(2/5)
• メリット ‒ インタフェースを区切りとして、チーム開発がやりやすくなった ‒ 変更、機能拡張、テストが容易になった
• デメリット ‒ Factoryを実装する必要がある ‒ クラスはFactoryに依存する
49
FindAction <<Singleton>>
Employee ServiceImpl
<<Singleton>> Employee DaoMySql
MySQL
ブラウザ RDB
Employee Dao
Employee Service
Factory
生成 生成 利用 利用
インタフェースの導入
アーキテクチャ・リファクタリング(3/5)
• Factoryを実装する必要がない • クラスはDIコンテナに依存しない
50
FindAction Employee ServiceImpl
Employee DaoMySql MySQL
ブラウザ RDB
Employee Dao
Employee Service
DIコンテナ
生成 生成
Injection Injection
DIの導入
アーキテクチャ・リファクタリング(4/5)
• トランザクション管理やログ出力、例外処理が残っている ‒ 分岐が多いため、テストの量が多くなる ‒ 共通化できる部分が隠蔽できていない ‒ 例外処理とConnectionの引き渡しによる連続性の阻害がある
51
public class EmployeeServiceImpl" implements EmployeeService{" @Autowired! private EmployeeDao dao;" ・・・ public List findAll() throws Exception {! if(Log.flag) { System.out.println(“***Start”); }! Connection conn = null;" ・・・ //EmployeeDao dao ! // = (EmployeeDao)Factory.create(KEY);! List employeeList = null;" try {" employeeList = dao.findAll(conn);" conn.commit();!
} catch(Exception e) {! conn.rollback();! ・・・ } finally {! conn.close();! ・・・ }! if(Log.flag) { System.out.println(“***End”); }" return employeeList;" }"・・・
Employee ServiceImpl
DIコンテナ導入後のソースコード
アーキテクチャ・リファクタリング(5/5)
• 共通ライブラリを廃止してAOPを導入 • 連続性の確保
‒ トランザクション管理、ログ出力、例外処理はAOPで実現しているため、ソースコード上からはなくなっている
‒ Advice実装されており、なくなっている訳ではない • ソースコードの記述量が減り、バグの数も低下、開発者の作業も軽減 • テストの容易性も向上
52
public class EmployeeServiceImpl ! implements EmploeeService {! @Autowired! private EmployeeDao dao;!
public List findAll() {! return dao.findAll();! }!・・・
書くことがなくなりました・・・!
AOP導入後のソースコード
アーキテクチャ・リファクタリングの嘘 • AOPで業務例外(例えば、在庫がなかった時にどうする)は処理できないから、そんなに奇麗に例外は消えない(多分…)
• だって、AOPを使う基盤チームは業務を知らない(多分…)。だから、業務例外はAOPで提供できない
• それに業務例外がAOPになったら、業務プログラムが読めない!
• そもそも、業務例外にExceptionを使うのってどうよ(!?)という問題でもある
53
Spring MVC かる~く
54
Spring MVCとは • Spring Frameworkに含まれるWeb MVCフレームワーク ‒ 初期のSpring Frameworkの段階から含まれている
‒ StrutsやJSFと競合 • 特徴
‒ DIコンテナとの親和性 ‒ きれいな設計
• インタフェースを使用して部品化
55
Spring MVCのController
56
XxxController Employee ServiceImpl
Employee DaoMySql MySQL
ブラウザ RDB
Employee Dao
Employee Service
DIコンテナ
生成 生成
Injection Injection
DIの導入
生成
難しいと評判(?)の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
動作概要
60
<<jsp>> /WEB-INF/views/home.jsp
<<controller>> HomeController
home()
ブラウザ Dispatcher
Servlet (ほか色々)
Model
Date (現在の日時)
"serverTime"
①
③
⑥
HomeController
61
@Controller public class HomeController {
@RequestMapping(value = "/", method = GET) public String home(Model model) { Date date = new Date();
model.addAttribute("serverTime", date);
return "home"; } }
DIコンテナにより 自動で読み込まれる (component-scan)
HTTPメソッドがGETで 「/」へアクセスした際に 実行される
Viewに渡したいオブジェクトを 設定する
View名をreturnする
※少し手を加えシンプルにしています
home.jsp
62
<html> <head> <title>Home</title> </head> <body> <h1>Hello world! </h1> <p>The time on the server is ${serverTime}.</p> </body> </html>
Modelに設定したオブジェクトは 自動的にHttpServletRequestに 設定されている
※少し手を加えシンプルにしています
@RequestMapping色々
63
// 一つのメソッドに複数のURLを割り当て @RequestMapping({"/", "/home"}) public String home() { ・・・
// 一つのURLでHTTPメソッドごとにメソッドを切り分け @RequestMapping(value="/foo", method=GET) public String doGetMethod() { ・・・
@RequestMapping(value="/foo", method=POST) public String doPostMethod() { ・・・
引数色々①
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); ・・・
引数色々②
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) { ・・・
その他の機能 • 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