nimbus (1.2.4) | 2018-01-25 20:02 |
nimbus-sample (1.2.4) | 2018-01-26 17:06 |
アプリケーション開発において、通常、手続き的にプログラムを構築していきます。
しかし、そのように開発したアプリケーションに対して、横断的に処理を追加したい場合があります。
例えば、デバッグ目的で処理をトレースするログを出力したい場合や、特定の機能に対して流量制御を行いたい場合などです。
このような横断的な機能の付加を行う開発手法をアスペクト指向プログラミングと呼びます。
Nimbusのアスペクト指向は、何らかの呼び出しの前後に機能を付加する方式を採ります。
この呼び出しの前後に付加する機能を抽象化したのがInterceptorです。
Interceptorは、連鎖させて機能を順次付加していく事ができます。その際のInterceptorの順序を示す機能を抽象化したのがInterceptorChainListで、それが示す順序どおりにInterceptorを連鎖させる機能を抽象化したのがInterceptorChainです。InterceptorChainが辿る連鎖の先には、本来の呼び出しを行う機能を抽象化したInvokerが存在します。
このInterceptorChainを生成する機能を抽象化したのが、InterceptorChainFactoryです。
このInterceptorChainをアプリケーションに横断的に織り込む方法は、織り込みのタイミングがランタイム時かそうでないかで、2つに分ける事ができます。
です。
動的アスペクト及び静的アスペクトの一部では、アスペクトをクラスファイルに織り込むアスペクトコンパイルを行います。
このアスペクトコンパイルを行う機能を抽象化したインタフェースが、AspectTranslatorです。
関連するパッケージは、以下です。
アスペクト指向インタフェースInterceptorは、呼び出しの前後に処理を付加するためのインタフェースです。
このインタフェースを実装したサービスの一覧は以下のとおりです。
サーブレットフィルタ専用のInterceptor実装サービスの一覧は以下のとおりです。
アスペクト指向インタフェースInterceptorChainListは、Interceptorを連ねるためのインタフェースです。
このインタフェースを実装したサービスの一覧は以下のとおりです。
実装サービス | 実装概要 |
jp.ossc.nimbus.service.aop.DefaultInterceptorChainListService | デフォルト実装サービス |
jp.ossc.nimbus.service.aop.SelectableServletFilterInterceptorChainListService | サーブレットフィルター用で、リクエストURL毎にInterceptorChainListを振り分ける |
アスペクト指向インタフェースInvokerは、アスペクトされた本来のメソッド呼び出しを行うインタフェースです。
このインタフェースを実装したサービスの一覧は以下のとおりです。
実装サービス | 実装概要 |
jp.ossc.nimbus.service.aop.invoker.MethodReflectionCallInvokerService | リフレクションAPIで本来のメソッド呼び出しを行う |
jp.ossc.nimbus.service.aop.javassist.WrappedMethodReflectionCallInvokerService | リフレクションAPIでMethodInterceptorAspectServiceによってラップされた本来のメソッド呼び出しを行う |
jp.ossc.nimbus.service.proxy.invoker.RemoteClientMethodCallInvokerService | プロキシ機能用の実装で、RMIでリモートのサービスを呼び出す |
jp.ossc.nimbus.service.proxy.invoker.JMXClientRMICallInvokerService | プロキシ機能用の実装で、JMXでリモートのサービスを呼び出す |
jp.ossc.nimbus.service.proxy.invoker.RemoteClientEJBCallInvokerService | プロキシ機能用の実装で、EJBでリモートのサービスを呼び出す |
jp.ossc.nimbus.service.proxy.invoker.LocalClientMethodCallInvokerService | プロキシ機能用の実装で、ローカルのサービスを呼び出す |
アスペクト指向インタフェースInterceptorChainFactoryは、Interceptorを連鎖させたInterceptorChainを生成するインタフェースです。
このインタフェースを実装したサービスの一覧は以下のとおりです。
実装サービス | 実装概要 |
jp.ossc.nimbus.service.aop.DefaultInterceptorChainFactoryService | デフォルト実装サービス |
アスペクト指向インタフェースAspectTranslatorは、アスペクトをクラスファイルの特定の場所に織り込むためのインタフェースです。
アスペクトを織り込む場所の事をポイントカットと呼び、アスペクトをポイントカットに織り込むためにクラスファイルを編集する事をアスペクトコンパイルと呼びます。
このインタフェースを実装したサービスの一覧は以下のとおりです。
実装サービス | 実装概要 |
jp.ossc.nimbus.service.aop.javassist.MethodInterceptorAspectService | Javassistを使ってアスペクトコンパイルする |
動的アスペクトとは、クラスローダーを差し替えて、クラスのロード時にクラスファイルを編集し、InterceptorChainを織り込む方法です。
この方法は、ランタイム時に織り込みを行うため、動的アスペクトと呼びます。
利点は、モジュールに対して非破壊的にアスペクトが可能な事です。欠点は、クラスローダーの差し替えが必要となるため、スタンドアローンなJavaVMでは簡単にできますが、アプリケーションサーバのようなクラスローダーが実装されているJavaVM上では、クラスローダーの差し替え自体が困難である事です。
まず、事前準備として、アスペクトするInterceptorの組み合わせをInterceptorChainListサービスで定義します。 更に、そのInterceptorChainListのポイントカットを決めて、アスペクトコンパイルするアスペクトサービスを定義します。
aspect-definition.xml
- <?xml version="1.0" encoding="Shift_JIS"?>
- <!DOCTYPE server PUBLIC
- "-//Nimbus//DTD Nimbus 1.0//JA"
- "http://nimbus.sourceforge.jp/dtd/nimbus-service_1_0.dtd">
- <server>
- <default-log>
- <debug output="false"/>
- <information output="true"/>
- <warning output="true"/>
- <error output="true"/>
- <fatal output="true"/>
- </default-log>
- <manager name="Aspect">
- <!-- アスペクトで織り込むInterceptorChainListサービス -->
- <service name="InterceptorChainList"
- code="jp.ossc.nimbus.service.aop.DefaultInterceptorChainListService">
- <!-- 連鎖させるInterceptorサービスのサービス名を順次設定する -->
- <attribute name="InterceptorServiceNames">
- #MethodMetricsInterceptor
- #TraceLoggingInterceptor
- </attribute>
- <depends>
- <!-- メソッド呼び出しをの処理メトリクスを取得するInterceptorサービス -->
- <service name="MethodMetricsInterceptor"
- code="jp.ossc.nimbus.service.aop.interceptor.MethodMetricsInterceptorService"/>
- </depends>
- <depends>
- <!-- トレースログを出力するInterceptorサービス -->
- <service name="TraceLoggingInterceptor"
- code="jp.ossc.nimbus.service.aop.interceptor.TraceLoggingInterceptorService"/>
- </depends>
- </service>
- <!-- InterceptorChainListを織り込むポイントカットを定義しアスペクトコンパイルを行うAspectTranslatorサービス -->
- <service name="AspectTranslator"
- code="jp.ossc.nimbus.service.aop.javassist.MethodInterceptorAspectService">
- <!-- アスペクト対象となるクラスのクラス名を完全修飾クラス名で指定する。
- クラス名は、java.util.regexの正規表現で指定する。
- -->
- <attribute name="TargetClassName">sample\.service\.MessengerService</attribute>
- <!-- アスペクト対象となるメソッド名を指定する。
- メソッド名は、java.util.regexの正規表現で指定する。
- -->
- <attribute name="TargetMethodName">getMessage</attribute>
- <!-- アスペクトで織り込むInterceptorChainListサービスのサービス名を指定する。 -->
- <attribute name="InterceptorChainListServiceName">#InterceptorChainList</attribute>
- <depends>InterceptorChainList</depends>
- </service>
- </manager>
- </server>
アスペクト対象のサービスを定義します。
アスペクト対象は、NimbusClassLoaderでロードさえ行えば、サービスである必要性はありませんが、サービスは暗黙的にNimbusClassLoaderでロードされるため、ここでは説明の簡略化のために、サービスをアスペクト対象とします。
service-definition.xml
アスペクトサービス定義を読み込むとNimbusClassLoaderにAspectTranslatorが登録される。
その後、アスペクト対象のサービスのサービス定義を読み込むと、アスペクト対象のサービスはNimbusClassLoaderによってロードされ、その際に動的にAspectTranslatorによってクラスファイルが編集されてアスペクトが織り込まれる。
- import jp.ossc.nimbus.core.ServiceManagerFactory;
- import sample.service.Messenger;
- // アスペクトサービス定義を読み込む
- if(ServiceManagerFactory.loadManager("aspect-definition.xml") && ServiceManagerFactory.checkLoadManagerCompletedBy("Aspect")){
- // サービス定義を読み込む
- if(ServiceManagerFactory.loadManager("service-definition.xml") && ServiceManagerFactory.checkLoadManagerCompleted()){
- // アスペクト対象のサービスを取得する
- Messenger messenger = (Messenger)ServiceManagerFactory.getServiceObject("Messenger");
- // アスペクト対象のメソッドを呼び出す
- System.out.println(messenger.getMessage());
- }
- }
静的アスペクトとは、ランタイム前に織り込みを行う方法で、3つの方法があります。
1つ目は、クラスファイルを事前に編集し、InterceptorChainを織り込む方法です。
利点は、動的アスペクトのようなクラスローダーの縛りがないため、どのようなJavaVMでも問題ない事です。欠点は、クラスファイルを事前に編集する必要があるためモジュールに対して破壊的である事です。
まず、事前準備として、InterceptorChainListのポイントカットを決めて、アスペクトコンパイルするアスペクトサービスを定義します。
compile-definition.xml
- <?xml version="1.0" encoding="Shift_JIS"?>
- <!DOCTYPE server PUBLIC
- "-//Nimbus//DTD Nimbus 1.0//JA"
- "http://nimbus.sourceforge.jp/dtd/nimbus-service_1_0.dtd">
- <server>
- <manager name="Compile">
- <!-- InterceptorChainListを織り込むポイントカットを定義しアスペクトコンパイルを行うAspectTranslatorサービス -->
- <service name="AspectTranslator"
- code="jp.ossc.nimbus.service.aop.javassist.MethodInterceptorAspectService">
- <!-- アスペクト対象となるクラスのクラス名を完全修飾クラス名で指定する。
- クラス名は、java.util.regexの正規表現で指定する。
- -->
- <attribute name="TargetClassName">sample\.service\.MessengerService</attribute>
- <!-- アスペクト対象となるメソッド名を指定する。
- メソッド名は、java.util.regexの正規表現で指定する。
- -->
- <attribute name="TargetMethodName">getMessage</attribute>
- <!-- アスペクトで織り込むInterceptorChainListサービスのサービス名を指定する。
- 静的アスペクトの場合は、このアスペクト定義ファイルには、InterceptorChainListサービス自体は定義する必要はない。
- -->
- <attribute name="InterceptorChainListServiceName">Aspect#InterceptorChainList</attribute>
- <!-- アスペクトで挟み込むインタセプタの最後に呼び出すInvokerサービスのサービス名を指定する。
- 静的アスペクトの場合は、このアスペクト定義ファイルには、Invokerサービス自体は定義する必要はない。
- -->
- <attribute name="InvokerServiceName">Aspect#Invoker</attribute>
- <!-- 静的コンパイルに使用する事を設定する -->
- <attribute name="StaticCompile">true</attribute>
- </service>
- </manager>
- </server>
上記のコンパイル用のアスペクトサービス定義を使って、アスペクト対象のクラスファイルをアスペクトコンパイルします。
java -classpath .;nimbus.jar;jmx.jar;javassist.jar jp.ossc.nimbus.service.aop.Compiler -servicepath compile-definition.xml -d . sample.service.*
次に、アスペクトするInterceptorの組み合わせをInterceptorChainListサービスで定義します。
aspect-definition.xml
- <?xml version="1.0" encoding="Shift_JIS"?>
- <!DOCTYPE server PUBLIC
- "-//Nimbus//DTD Nimbus 1.0//JA"
- "http://nimbus.sourceforge.jp/dtd/nimbus-service_1_0.dtd">
- <server>
- <manager name="Aspect">
- <!-- アスペクトで織り込むInterceptorChainListサービス -->
- <service name="InterceptorChainList"
- code="jp.ossc.nimbus.service.aop.DefaultInterceptorChainListService">
- <!-- 連鎖させるInterceptorサービスのサービス名を順次設定する -->
- <attribute name="InterceptorServiceNames">
- #MethodMetricsInterceptor
- #TraceLoggingInterceptor
- </attribute>
- <depends>
- <!-- メソッド呼び出しをの処理メトリクスを取得するInterceptorサービス -->
- <service name="MethodMetricsInterceptor"
- code="jp.ossc.nimbus.service.aop.interceptor.MethodMetricsInterceptorService"/>
- </depends>
- <depends>
- <!-- トレースログを出力するInterceptorサービス -->
- <service name="TraceLoggingInterceptor"
- code="jp.ossc.nimbus.service.aop.interceptor.TraceLoggingInterceptorService"/>
- </depends>
- </service>
- <!-- アスペクトコンパイルによってラップされたメソッドを呼び出すInvokerサービス -->
- <service name="Invoker"
- code="jp.ossc.nimbus.service.aop.javassist.WrappedMethodReflectionCallInvokerService"/>
- </manager>
- </server>
次に、アスペクト対象のサービスを定義します。
アスペクト対象は、サービスである必要性はありませんが、動的アスペクトとの対比のため、サービスをアスペクト対象とします。
service-definition.xml
アスペクトサービス定義を読み込むとクラスファイルに事前に織り込まれたInterceptorChainが使用するInterceptorChainListサービスの準備が整います。
その後、アスペクト対象のサービスのサービス定義を読み込み、アスペクト対象のメソッドを呼び出すと、事前にアスペクトコンパイルされたコードによって、アスペクトが働きます。
- import jp.ossc.nimbus.core.ServiceManagerFactory;
- import sample.service.Messenger;
- // アスペクトサービス定義を読み込む
- if(ServiceManagerFactory.loadManager("aspect-definition.xml") && ServiceManagerFactory.checkLoadManagerCompletedBy("Aspect")){
- // サービス定義を読み込む
- if(ServiceManagerFactory.loadManager("service-definition.xml") && ServiceManagerFactory.checkLoadManagerCompleted()){
- // アスペクト対象のサービスを取得する
- Messenger messenger = (Messenger)ServiceManagerFactory.getServiceObject("Messenger");
- // アスペクト対象のメソッドを呼び出す
- System.out.println(messenger.getMessage());
- }
- }
2つ目は、プロキシ機能を使って、InterceptorChainを織り込む方法です。
利点は、クラスファイルを編集しないので、非破壊である事と事前準備が少ない事です。欠点は、織り込める場所がサービスのインタフェースに限定される事です。
以下に、サービスをプロキシ化して、InterceptorChainListを挟み込むサービス定義を示します。
service-definition.xml
- <?xml version="1.0" encoding="Shift_JIS"?>
- <!DOCTYPE server PUBLIC
- "-//Nimbus//DTD Nimbus 1.0//JA"
- "http://nimbus.sourceforge.jp/dtd/nimbus-service_1_0.dtd">
- <server>
- <default-log>
- <debug output="false"/>
- <information output="true"/>
- <warning output="true"/>
- <error output="true"/>
- <fatal output="true"/>
- </default-log>
- <manager>
- <!-- アスペクトで織り込むInterceptorChainListサービス -->
- <service name="InterceptorChainList"
- code="jp.ossc.nimbus.service.aop.DefaultInterceptorChainListService">
- <!-- 連鎖させるInterceptorサービスのサービス名を順次設定する -->
- <attribute name="InterceptorServiceNames">
- #MethodMetricsInterceptor
- #TraceLoggingInterceptor
- </attribute>
- <depends>
- <!-- メソッド呼び出しをの処理メトリクスを取得するInterceptorサービス -->
- <service name="MethodMetricsInterceptor"
- code="jp.ossc.nimbus.service.aop.interceptor.MethodMetricsInterceptorService"/>
- </depends>
- <depends>
- <!-- トレースログを出力するInterceptorサービス -->
- <service name="TraceLoggingInterceptor"
- code="jp.ossc.nimbus.service.aop.interceptor.TraceLoggingInterceptorService"/>
- </depends>
- </service>
- <!-- MessengerサービスのプロキシとなるRemoteClientサービス-->
- <service name="Messenger"
- code="jp.ossc.nimbus.service.proxy.RemoteClientService">
- <attribute name="RemoteInterfaceClassName">sample.service.Messenger</attribute>
- <attribute name="InvokerServiceName">#MessengerInvoker</attribute>
- <!-- インターセプタのチェインを定義するInterceptorChainListサービスのサービス名を設定する-->
- <attribute name="InterceptorChainListServiceName">#InterceptorChainList</attribute>
- <depends>
- <!-- プロキシする実体を呼び出すInvokerサービス
- ローカルのサービスを呼び出す。
- -->
- <service name="MessengerInvoker"
- code="jp.ossc.nimbus.service.proxy.invoker.LocalClientMethodCallInvokerService">
- <attribute name="LocalServiceName">#RealMessenger</attribute>
- <depends>RealMessenger</depends>
- </service>
- </depends>
- <depends>#InterceptorChainList</depends>
- </service>
- <!-- プロキシ対象のサービス -->
- <service name="RealMessenger"
- code="sample.service.MessengerService"/>
- </manager>
- </server>
以下に、プロキシでラップしたサービスにアクセスするコードを示します。
- import jp.ossc.nimbus.core.ServiceManagerFactory;
- import sample.service.Messenger;
- // サービス定義を読み込む
- if(ServiceManagerFactory.loadManager("service-definition.xml") && ServiceManagerFactory.checkLoadManagerCompleted()){
- // プロキシでラップしたサービスを取得する
- Messenger messenger = (Messenger)ServiceManagerFactory.getServiceObject("Messenger");
- // アスペクト対象のメソッドを呼び出す
- System.out.println(messenger.getMessage());
- }
3つ目は、サーブレットフィルタや業務フローなどのアスペクト機能前提のサービスを使って、InterceptorChainを織り込む方法です。
利点は、クラスファイルを編集しないので、非破壊である事と事前準備が少ない事です。欠点は、織り込める場所が限定される事です。
サンプルは、以下。