いまさら人には聞けない DI AOP 入門 2009 2009.9.12 小森裕介 (komo@littleforest.jp) 1
はじめまして! 名前 : 小森裕介 Blog:http://d.hatena.ne.jp/y-komori/ こもりん日記 所属 : ウルシステムズ株式会社 (http://www.ulsystems.co.jp) 主な仕事 : 各種 IT コンサルティング 各種 SI 支援 教育 各種執筆活動 : 日経ソフトウエア とことん作って覚える! Java 入門 連載 なぜ あなたは Java でオブジェクト指向開発ができないのか Seasar2 とのかかわり Uruma コミッタ S2Container コミッタ S2JMS コミッタ 2
はじめに DI AOP が世に出て早数年 Seasar や Spring をなんとなく使ってるけど なにが良いの? と聞かれてもうまく説明できない 自分の開発現場にも導入したいのだけど 上司や同僚を納得させる自信がない そんなアナタに DI AOP の本質をお伝えします! 3
アジェンダ 1. 依存性 の問題点 2. 依存性 との戦いの歴史 3. POJOによる 継承関係 実装関係の依存 からの脱却 4. DIによる オブジェクト利用の依存 からの脱却 5. DIによる 実装クラスへの依存 からの脱却 6. AOPはなぜ必要か 7. AOPの考え方 8. AOPの実現方法 4
DI AOP の必要性 DI AOPはなぜ必要か? それは 依存性からの脱却 を促進するため 5
1. 依存性 の問題点依存性とはなにか オブジェクト指向における 依存性 は 3 種類ある 継承関係 実装関係の依存 ClassA ClassB InterfaceC ClassD オブジェクト指向における 依存性 オブジェクト利用の依存 ClassB ClassA ClassC ClassA 実装クラスへの依存 InterfaceB Creates ClassB ClassC 6
1. 依存性 の問題点継承関係 実装関係の依存 (1/2) 継承関係 実装関係による依存 フレームワークの提供するクラス インタフェースを利用 例 1 Struts の Action クラス 例 2 EJB2 の SessionBean Action Interface SessionBean EmployeeAction フレームワークの世界 StockFinder ユーザアプリの世界 Struts Action EmployeeAction FW 既知のクラス インタフェースでないと呼び出せない FW 側で実施する前処理等 ユーザ本来の処理 このシーケンス図は模式的なものであり 実際の Struts の動作とは異なります 7
1. 依存性 の問題点継承関係 実装関係の依存 (2/2) 継承関係 実装関係の依存 の問題点 フレームワークにロックオンされる 継承 実装関係があるため ユーザが作成したコードを他のフレームワーク下でそのまま利用できない クラス単体でテストがしにくい フレームワークがインスタンス生成をしていると単体テスト時にインスタンス作成できず テストが困難 EJB 環境 Interface SessionBean StockFinder 非 EJB 環境 Interface SessionBean StockFinder 再利用性の低下 品質の低下 他の環境ではソースコード修正無しに再利用不可能 8
1. 依存性 の問題点オブジェクト利用の依存 (1/2) あるオブジェクトが他のオブジェクト利用する場合 BookFinder StockFinder Logger DataSource JNDI からのルックアップ Factory からの取得 public class BookFinder { private StockFinder stockfinder; private DataSource datasource; private Logger logger; public BookFinder(StockFinder stockfinder) { this.stockfinder = stockfinder; Context ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/MySQL"); this.logger = LoggerFactory.getLogger(this.getClass()); public List<Book> findbook(condition cond) {... コンストラクタ経由の参照渡し 9
1. 依存性 の問題点オブジェクト利用の依存 (2/2) オブジェクト利用の依存 の問題点 学習量が多い インスタンス取得のための様々な作法を知らなくてはならない コード量が多くなる オブジェクト組み立て のための本質的ではない大量のコード記述が必要 生産性の低下 保守性の低下 private Logger logger; public BookFinder(StockFinder stockfinder) { this.stockfinder = stockfinder; Context ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/MySQL"); this.logger = LoggerFactory.getLogger(this.getClass()); 10
1. 依存性 の問題点実装クラスへの依存 (1/2) あるオブジェクト内部で他のオブジェクトを new している場合 BookFinder Interface StockFinder Creates DBStockFinder RemoteStockFinder public class BookFinder { private StockFinder stockfinder; public BookFinder() { this.stockfinder = new DBStockFinder(); 利用側で実装クラスを直接 new している public class BookFinder { private StockFinder stockfinder; public BookFinder() { this.stockfinder = new RemoteStockFinder(); 実装クラスを変更するためには 利用側のコードを書き換えなければならない 11
1. 依存性 の問題点実装クラスへの依存 (2/2) 実装クラスへの依存 の問題点 仕様と実装の分離 を徹底できない せっかくインタフェースで仕様を分離しているのに インスタンス生成のために実装クラスへ依存している モックオブジェクトへの差し替えが難しい テスト用のモックオブジェクトを使用するにはテスト対象コードの変更が必要 保守性の低下 品質の低下 public class BookFinder { private StockFinder stockfinder; public BookFinder() { this.stockfinder = new DBStockFinder(); 利用側で実装クラスを直接 new している public class BookFinder { private StockFinder stockfinder; public BookFinder() { this.stockfinder = new RemoteStockFinder(); 実装クラスを変更するためには 利用側のコードを書き換えなければならない 12
2. 依存性 との戦いの歴史 依存性 の権化 EJB2.0 高度な機能を提供するため とても複雑になった インターフェースが特殊なので EJB 環境以外では再利用できない クライアント EJB にアクセスするための手続きが面倒 EJBコンテナ EJB EJB EJB EJBコンテナ EJB EJB EJB Web ブラウザ Web サーバ EJB コンテナがないとテストできない 分散オブジェクトはあまり使わない EntityBean は重い DB サーバ もっと簡単にコンポーネント技術を利用する方法はないの? 13
2. 依存性 との戦いの歴史なぜこの状況が放置されたのか? Java のエンタープライズ利用は Web アプリケーションが主流となった Struts をはじめとする Web アプリケーションフレームワーク が台頭 中 小規模のシステム開発では EJB の提供する機能がなくてもあまり困らなかった フレームワークの軽量化で使い方は簡単になった Web/AP サーバ Hibernate など O/R マッピングツールの普及で楽になった Action Logic DAO Web ブラウザ AP サーバ上でなければテストできない Action と Logic が分離されないこともある 再利用の妨げに 依存するオブジェクトが用意できないとテストできない DB サーバ 本質的な問題は未解決 大規模システムでは影響が顕著に 14
POJO with DI AOP による依存性からの脱却 依存性 が生み出す様々な問題を POJO with DI AOP で解決しよう!! Plain Old Java Object ( ポジョ ) Dependency Injection コード量が多い Aspect Oriented Programming ( 依存性注入 ) ( アスペクト指向プログラミング ) 依存性 15
3.POJO による 継承関係 実装関係の依存 からの脱却 Before POJO POJO(Plain Old Java Object) という考え方 FW 特有の I/F 親クラスを持たない 昔ながらのただの Java オブジェクト Before POJO 例 1 Struts の Action クラス 例 2 EJB2 の SessionBean Action Struts が提供する親クラス Interface SessionBean EJB 仕様の規定するインタフェース EmployeeAction 子クラスから利用できるメソッド ( 機能 ) を提供 StockFinder Struts の API に依存する EJB として利用するために特定メソッドの実装が強制される 自由な再利用や Junit による単体テストの妨げになった 16
3.POJO による 継承関係 実装関係の依存 からの脱却 After POJO POJO(Plain Old Java Object) という考え方 FW 特有の I/F 親クラスを持たない 昔ながらのただの Java オブジェクト After POJO 例 1 Struts の Action クラス 例 2 EJB2 の SessionBean Action フレームワーク固有のクラスを継承しない Interface SessionBean フレームワーク固有のインタフェースを実装しない EmployeeAction StockFinder 簡単にかける! どこでも使える! フレームワーク利用者の記述するコードは POJO にする フレームワークの影響を受けず再利用が可能再利用性の向上 クラス単体でのテストがしやすくなる品質の向上 17
4.DI による オブジェクト利用の依存 からの脱却 Before DI 依存性注入 (Dependency Injection) という考え方 必要なオブジェクトは DI コンテナが生成して注入する Before DI StockFinder Client BookFinder Logger DataSource public class BookFinder { private StockFinder stockfinder; private DataSource datasource; private Logger logger; public BookFinder(StockFinder stockfinder) { this.stockfinder = stockfinder; Context ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/MySQL"); this.logger = LoggerFactory.getLogger(this.getClass()); public List<Book> findbook(condition cond) { logger.log( 書籍検索を開始します ); List<Book> books = datasource.select(); logger.log( 書籍検索を終了しました ); return books; オブジェクトの組み立てが大変だった 大量のオブジェクト組み立てコード ビジネスロジック本来の処理 18
4.DI による オブジェクト利用の依存 からの脱却 After DI 依存性注入 (Dependency Injection) という考え方 必要なオブジェクトは DI コンテナが生成して注入する After DI Client BookFinder オブジェクトくださいな へい おまちっ! 出でよ! BookFinder! おぶじぇくとぉ ~ いんじぇくしょぉ ~~ ん!! あいよっ! BookFinder DI コンテナ + stockfinder + datasource + logger StockFinder DataSource Logger 19
4.DI による オブジェクト利用の依存 からの脱却 After DI 依存性注入 (Dependency Injection) という考え方 必要なオブジェクトは DI コンテナが生成して注入する After DI StockFinder Client BookFinder DataSource public class BookFinder { public StockFinder stockfinder; public DataSource datasource; public Logger logger; public List<Book> findbook(condition cond) { logger.log( 書籍検索を開始します ); List<Book> books = datasource.select(); logger.log( 書籍検索を終了しました ); return books; 依存するオブジェクトがどこで生成されるかは気にしなくてよい! ビジネスロジック本来の処理 Logger 学習量が減る コード量が減る 生産性の向上 保守性の向上 20
5.DI による 実装クラスへの依存 からの脱却 Before DI DI による実装クラスの外部定義 インタフェースに対する実装クラス外部定義化 生成は DI コンテナが行う Before DI 利用時はインタフェースのみ参照 BookFinder Creates DBStockFinder Interface StockFinder RemoteStockFinder public class BookFinder { private StockFinder stockfinder; public BookFinder() { this.stockfinder = new DBStockFinder(); 利用側で実装クラスを直接 new している public class BookFinder { private StockFinder stockfinder; public BookFinder() { this.stockfinder = new RemoteStockFinder(); 実装クラスを変更するためには 利用側のコードを書き換えなければならない 仕様と実装の分離 が徹底できなかった 21
5.DI による 実装クラスへの依存 からの脱却 After DI DI による実装クラスの外部定義 インタフェースに対する実装クラス外部定義化 生成は DI コンテナが行う After DI Seasar2 では dicon ファイル public class BookFinder { public StockFinder stockfinder; <component class= DBStockFinder /> BookFinder Interface StockFinder コンポーネント定義ファイル 実装クラスを変更する場合 コンポーネント定義を修正する コンポーネント定義にしたがい DIContainer がインスタンスを生成 実装クラスに依存しなくてよい DBStockFinder Creates DIContainer 仕様と実装 が完全に分離できる テスト用のモックオブジェクトが導入しやすい 保守性の向上 品質の向上 22
6.AOP はなぜ必要か POJO 化によるクラス拡張の欠落 フレームワーク利用者のコードを POJO 化すると フレームワーク側からの機能追加ができなくなる Before POJO After POJO Action フレームワークが提供するスーパークラスで 共通機能を提供できた Action POJO 化により 共通機能が提供できなくなった EmployeeAction EmployeeAction フレームワークの世界 ユーザアプリの世界 Struts Action EmployeeAction FW 側で実施する前処理等 ユーザ本来の処理 このシーケンス図は模式的なものであり 実際の Struts の動作とは異なります 23
6.AOP はなぜ必要か複数クラスから呼び出される共通機能への依存 例 : ロギング処理で発生する問題 BookFinder ロギング処理をなくしたい! BookFinder 利用側にロギング処理呼び出しコードが残ってしまう +getbooklist() +getbooklist() CDFinder +getcdlist() Logger +log() ロギング処理で利用 CDFinder +getcdlist() 共通コンポーネント を利用すると 各所に呼び出しコードが混ざってしまう Logger +log() ロギングコンポーネントの再利用は簡単 24
6.AOP はなぜ必要か非機能要件はモジュール単独分離が難しい 非機能要件に関する処理は 全機能に影響するため モジュールとして分離しにくい ロギング処理も非機能要件の一つ 非機能要件 システムの本来の機能とは関係ないが 信頼性や保守性 使いやすさを向上させるための要件 機能要件 A 機能要件 B 機能要件 C 機能要件 D 非機能要件 Ⅰ 非機能要件 Ⅰ 非機能要件 Ⅰ 非機能要件 Ⅰ 非機能要件 Ⅱ 非機能要件 Ⅱ 非機能要件 Ⅱ 非機能要件 Ⅱ 非機能要件 Ⅲ 非機能要件 Ⅲ 非機能要件 Ⅲ 非機能要件 Ⅲ 非機能要件 Ⅳ 非機能要件 Ⅳ 非機能要件 Ⅳ 非機能要件 Ⅳ 各機能に共通する処理をモジュール化する方法はないの? 25
7.AOP の考え方アスペクト指向は関心事の分離から システムを 2 種類の要件に分け 横断的関心事を分離するのがアスペクト指向の考え方 ビジネス A ビジネス B ビジネス C ビジネス D セキュリティ ( アクセス制御 情報隠蔽 承認 完全性 ) 中心的関心事 (Core concern) 信頼性 ( バックアップ 分散 冗長性の確保 ) 運用情報 ( 監視 稼働状況 負荷管理 障害状況 ) マイグレーション ( 配備 設定 保守 開発計画 ) 横断的関心事 (Cross cutting concern) 出典 Seasar2 で学ぶ DI と AOP (arton 著 技術評論社 )p20 26
7.AOP の考え方ジョインポイントとアドバイス アスペクト指向では 機能の中にジョインポイントを定義し アドバイスをウィービングする ビジネス A ビジネス B ビジネス C ビジネス D アドバイス (Advice) 追加される処理 セキュリティ ( アクセス制御 情報隠蔽 承認 完全性 ) 信頼性 ( バックアップ 分散 冗長性の確保 ) 運用情報 ( 監視 稼働状況 負荷管理 障害状況 ) マイグレーション ( 配備 設定 保守 開発計画 ) ジョインポイント (Joinpoint) 処理を追加する場所 ウィービング (Weaving) 処理を追加すること 27
8.AOP の実現方法アスペクトコンパイラによる AOP の実現 AOP の実現には専用のコンパイラが必要だった Before DI AOP Java ソースコード Java クラスファイル アスペクト (Aspect) アスペクトコンパイラ アドバイスとポイントカットを記述したもの コンパイルと同時にウィービングを行ってクラスファイルを出力する ポイントカット ジョインポイントとアドバイスの結びつきを定義したもの こいつぁ 面倒だ 28
8.AOP の実現方法 DI コンテナと連携した AOP の実現 DI コンテナで生成時にウィービングを実施 After DI AOP Client BookFinder オブジェクトくださいな へい おまちっ! おぶじぇくとぉ ~ いんじぇくしょ ~ ん! あすぺくとぉ ~ うぃ ~ びんぐ!! 出でよ! BookFinder! あいよっ! BookFinder DI コンテナ + dao BookDao ロギング処理ウィービング完了 DI AOP で自動ウィービングが可能に! LogIntercepter 29
DI AOP の効果 POJO の良さを生かしながら フレームワークによるユーザコードの機能拡張を実現 ロギング処理 S2Container(TraceInterceptor) 例外処理 S2Container(ThrowsInterceptor) セッションオブジェクト管理 S2Container(Remove/InvalidateSessionInterceptor) tostring() メソッドの自動実装 S2Container(ToStringInterceptor) トランザクション処理 S2Tx リモートオブジェクト化 S2Remoting Web アプリケーションでのログイン状態チェック 独自実装で可能 30
DI コンテナに対する 5 つのギモン 依存関係はどうやって判断するの? Seasar2 なら インターフェースに基づいて自動判断します 結局設定ファイルをたくさん書くのでは? Seasar2 なら AutoRegister でカンタンに登録! More Info! More Info! 設定ファイルのデバッグが大変? Kijimuna( キジムナ ) で記述の誤りをチェックできます! 結局はリフレクションでしょ 遅くないの? 独自のキャッシュ機構で速度低下はほとんどありません More Info! http://s2container.seasar.org/2.4/ja/dicontainer.html#autobindingmode http://s2container.seasar.org/2.4/ja/dicontainer.html#componentautoregister More Info! http://s2container.seasar.org/ja/benchmark/20060412_seasar_vs_spring.ppt Setter を書くのが面倒くさい! public フィールドインジェクションで setter いらず! http://kijimuna.seasar.org/ More Info! http://s2container.seasar.org/2.4/ja/dicontainer.html#fieldinjection 31
どうやって使っていったらよいの? DI コンテナのメリットはなんとなくわかった でも 開発現場でどう役立てればいいの? DI コンテナの機能をフルに使い切って設計するのは けっこうムズカシイ! そこで まずは S2Container を 100% 生かして作られた周辺プロダクトを使ってください! まずは SAStruts Teeda S2JDBC S2Dao がオススメです! 32
本日のまとめ 依存性 の持つ問題点を理解した 継承関係 実装関係の依存 オブジェクト利用の依存 実装クラスへの依存 POJO with DI AOP による 依存性からの脱却 方法と そこから得られるメリットを理解した 生産性の向上 保守性の向上 拡張性の向上 品質の向上 再利用性の向上 33
お勧め書籍 Seasar2 AOP について もっと知りたい方へ (1/2) Seasar 入門 ~ はじめての DI&AOP~ 監修 : ひがやすを著 : 須賀幸次他 価格 :3,570 円 出版社 : ソフトバンククリエイティブ ISBN:4797331968 DI AOP のキホンをきっちり学習 Seasar2 で学ぶ DI と AOP アスペクト指向による Java 開発 著 :arton アスペクト指向入門 -Java オブジェクト指向から AspectJ プログラミングへ 著 : 千葉滋 価格 : 2,480+ 税 出版社 : 技術評論社 ISBN:4-7741-2581-4 価格 :3,360 円 出版社 : 技術評論社 ISBN: 4774128554 アスペクト指向の考え方をしっかり身につけたい方へ DI AOP の考え方と Web アプリ開発について理解 (S2JSF+S2Dao) 34
お勧め書籍 Seasar2 AOP について もっと知りたい方へ (2/2) Seasar2 によるスーパーアジャイルな Web 開発 著 : ひがやすを 価格 :2,499 円 出版社 : 技術評論社 ISBN:4774134368 Teeda+S2Dao による Web アプリケーション開発について解説 Seasar2 入門 Java によるはじめての Web アプリケーション開発 著 : ひがやすを 価格 :2,730 円 出版社 : ソフトバンククリエイティブ ISBN:4797345241 SAStruts+S2JDBC による Web アプリケーション開発について解説 35
ご静聴 ありがとうございました 36