1.関連する概念
AOP
AOP(アスペクト指向プログラミング)はアスペクト指向プログラミングと呼ばれ、OOP(オブジェクト指向プログラミング、オブジェクト指向プログラミング)の補完および改良と言えます。OOPは、カプセル化、継承、ポリモーフィズムなどの概念を導入して、オブジェクト階層を確立します。これは、トランザクションプロセスで垂直モジュールを適切にシミュレートできますが、水平関係を定義するのには適していません。
たとえば、プロセス内のさまざまな操作プロセスは次のとおりです。各操作プロセスはログ記録機能を実行する必要があるため、各操作モジュールはログ記録コードを記述する必要があります。このような各モジュールの分布は、特定のモジュールに依存しません。コードはクロスカッティング(クロスカッティング)と呼ばれ、OOP設計では、さまざまなモジュールの再利用につながらないコードの重複が多数発生します。
AOPテクノロジーは正反対です。「クロスカット」と呼ばれるテクノロジーを使用して、カプセル化されたオブジェクトの内部を分析し、これらの複数のクラスの共通の動作を再利用可能なモジュールにカプセル化して、「アスペクト」という名前を付けます「それが切断面です。システムの反復コードを削減し、モジュール間の結合を削減することは簡単であり、将来の操作性と保守性に有益です。これは主に、ログ、トランザクション、権限待機などのプログラム開発におけるシステムレベルの問題を解決するために使用されますStruts2インターセプターの設計はAOPの概念に基づいており、典型的な例です。
AOPは、ソフトウェアシステムを2つの部分に分割します。コアの関心事と分野横断的な関心事です。ビジネスプロセスのメインプロセスはコアの関心事であり、それに関連しない部分は横断的な関心事です。横断的関心事の特徴の1つは、コア関心事の複数の場所で発生することが多く、基本的には許可認証、ログなど、どこでも類似していることです。AOPの役割は、システム内のさまざまな問題を分離し、横断的な問題からコアの問題を分離することです。
AOPを実装する方法は2つあります。1つはAspectJで表される事前コンパイルされたメソッドで、もう1つはSpringで表される実行時の動的プロキシです。AspectJと比較すると、SpringAOPは包括的なAOPソリューションではなく、開発における一般的な問題を解決するためのSpringIOCコンテナーとの統合のみを提供します。
用語
- 側面:懸念事項を横断するモジュラークラス。エントリポイントと通知を定義できます。
- ターゲットオブジェクト(ターゲットオブジェクト):メインビジネスプロセスの実現の中心的な焦点は、通知されたオブジェクトまたはプロキシオブジェクトです。接続ポイントを含む
- JointPoint(ジョイントポイント):プログラムの実行中にインターセプトされるポイント。通常はオブジェクトの特定のメソッド。
- アドバイス(通知):特定のエントリポイントでAOPによって実行される処理操作
- ポイントカット(pointcut):通知の接続ポイントであり、通知はAOPのポイントカット式に関連付けられています
- AOPプロキシ(AOPプロキシ):AOPフレームワークは、プロキシターゲットクラスを介してオブジェクトを作成します。
- weave(ウィービング):ファセットをターゲットオブジェクトに適用し、AOPプロキシオブジェクトを作成するプロセス
- 導入(導入):コードを変更せずに、導入時にクラスのメソッドまたはフィールドを実行時に動的に追加できます
通知タイプ
- 前:接続ポイントメソッドの実行前に通知します。@ Beforeはポイントカット式を指定するだけで済みます。
- AfterReturning:ターゲットメソッドが正常に完了した後に通知します。@ AfterReturningポイントカット式を指定することに加えて、ターゲットメソッドの戻り値を表す戻り値パラメータ名を指定することもできます。
- AfterThrowing:接続ポイントメソッドは例外をスローし、終了時に通知されます。ポイントカット式を指定することに加えて、@ AfterThrowingは、ターゲットメソッドでスローされたオブジェクトにアクセスするために使用できる戻り値のパラメーター名も指定できます。例外オブジェクト
- 後:ターゲットメソッドが例外をスローしたかどうかに関係なく、ターゲットメソッドが完了した後に実行されます。@Afterはエントリポイント式を指定できます
- Around:サラウンド通知、ターゲットメソッドが完了する前と完了した後に実行する操作を定義します。サラウンド通知は、トランザクション、ログなど、通知の最も重要なタイプの通知です。
2. SpringでAOPを使用する
XMLに基づく設定
最初のステップは、mavenを使用してプロジェクトを作成し、pom.xmlファイルに次のように依存関係をインポートすることです。springとjunitに加えて、spring-aop、aspectjrt、aspectjweaverの3つの依存関係を導入する必要があります。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
</dependencies>
2番目のステップは、単純なJavaプロジェクトを作成することです。下の図に示すように、IOperationインターフェースがあります。operations1および2クラスは、ビジネスロジックの実行の中心となるインターフェースのdoOperation()メソッドを実装します。LogHandlerは、横断的な関心事クラスを表すログ出力アスペクトに使用されます。spring.aopのMethodBeforeAdviceおよびAfterReturningAdviceインターフェースを実装することにより、BeforeおよびAfterReturningタイプの通知のエントリポイントが定義されます。同様に、ThrowsAdviceインターフェースのafterThrowing()メソッドがあります。
public interface IOperation {
void doOperation();
}
public class Operation1 implements IOperation {
public void doOperation() {
System.out.println("执行业务操作1");
}
}
public class Operation2 implements IOperation {
public void doOperation() {
System.out.println("执行业务操作2");
}
}
//LogHandler
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class LogHandler implements MethodBeforeAdvice, AfterReturningAdvice {
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("操作执行前,打印日志...");
}
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("操作执行后,打印日志...");
}
}
3番目の手順は、SpringのAOPを構成することです。次のように、resourceの下にspring-aop.xmlファイルを作成します:最初に、プロキシクラスのBean--o1、o2とアスペクトクラスのlogHandlerを定義します。次に、エントリポイントと切断面を構成します。最後に、AOPプロキシproxy1とproxy2を設定します。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义目标类 -->
<bean id="o1" class="com.aop.Operation1"/>
<bean id="o2" class="com.aop.Operation2"/>
<!-- 定义切面类 -->
<bean id="logHandler" class="com.aop.LogHandler"/>
<!-- 定义切入点,这里定义所有名为doOperaion的方法 -->
<bean id="logPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*doOperation"/>
</bean>
<!-- 配置切面,使切入点与通知相关联 -->
<bean id="logHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="logHandler"/>
<property name="pointcut" ref="logPointcut"/>
</bean>
<!-- 为o1设置代理 -->
<bean id="proxy1" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的目标o1 -->
<property name="target" ref="o1"/>
<!-- 使用切面 -->
<property name="interceptorNames" value="logHandlerAdvisor"/>
<!-- 代理对应的接口 -->
<property name="proxyInterfaces" value="com.aop.IOperation"/>
</bean>
<!-- 为o2设置代理 -->
<bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的目标o2 -->
<property name="target" ref="o2"/>
<!-- 使用切面 -->
<property name="interceptorNames" value="logHandlerAdvisor"/>
<!-- 代理对应的接口 -->
<property name="proxyInterfaces" value="com.aop.IOperation"/>
</bean>
</beans>
4番目のステップは、AOPプロキシproxy1、proxy2を使用して、テストクラスで対応する操作を実行することです。
@Test
void printLog() {
//读取上下文配置文件
ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
IOperation op1 = (IOperation) appCtx.getBean("proxy1"); //通过代理proxy来使用Operation1对象
IOperation op2 = (IOperation) appCtx.getBean("proxy2");
op1.doOperation();
op2.doOperation();
}
実行結果は左図のようになり、Operation1とOperation2が実行され、実行前後にアスペクトクラスのlogHandlerのメソッドが呼び出され、ログが出力されていることがわかります。AOP全体の構造を以下に示します。
aopタグによる設定
xmlを介した構成は複雑すぎるため、それらのほとんどは、Spring2.0バージョン以降の構成にaopタグを使用します。xmlとは異なり、まず、アスペクトクラスは特定のインターフェイスメソッドを実装する必要はありません
//定义用于日志输出的切面类
public class LogHandler {
public void beforeLog() {
System.out.println("操作执行前打印日志...");
}
}
次に、xmlファイルで<aop>タグを使用して、次のように構成します。最初に、<beans>タグにaopタグxmlnsを導入します:aop = "http://www.springframework.org/schema/aop"、次にターゲットクラスを定義しますo1、o2およびアスペクトクラスlogHandlerのBean。次に、<aop:config>を使用してアスペクトを構成し、その中にエントリポイントと通知を構成します。
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义目标类 -->
<bean id="o1" class="com.aop.Operation1"/>
<bean id="o2" class="com.aop.Operation2"/>
<!-- 定义切面类,也就是切入点执行前后需要做的事情 -->
<bean id="logHandler" class="com.aop.LogHandler"/>
<!-- 切面配置 -->
<aop:config>
<!-- 配置切面为logHandler类 -->
<aop:aspect id="logAop" ref="logHandler">
<!-- 配置切入点为com.aop包下所有类的doOperation方法 -->
<aop:pointcut id="operationPoint" expression="execution(* com.aop..*.*doOperation(..))"/>
<!-- 配置before前置通知为beforeLog()方法 -->
<aop:before method="beforeLog" pointcut-ref="operationPoint"/>
</aop:aspect>
</aop:config>
</beans>
エントリポイントを構成するときに実行式が使用されます。一般的に使用されるいくつかの式は次のとおりです。さらに、within()、this()、target()、args()、bean()などの他の一致する式のタイプもあります。たとえば、com.aop.Operation2クラスにはdoOperation()メソッドがあり、
実行(公開* *(..)) | エントリポイントはすべてパブリックメソッドです |
実行(*セット*(..)) | エントリポイントはすべて設定されたメソッドです |
実行(* com.aop.Operation2。*(..)) | エントリポイントは、Operation2クラスのすべてのメソッドです |
実行(* com.aop ..(..)) | エントリポイントは、aopパッケージ下のすべてのクラスのメソッドです |
実行(* com ...(..)) | comのエントリポイントを持つすべてのパッケージとそのサブパッケージのすべてのメソッド |
実行(* com.aop..do *(..)) | エントリポイントは、com.aopの下のすべてのクラスで「do」で始まるメソッドです |
構成通知は、通知の前のタイプを表す<aop:before>タグを使用します。同様に、それぞれ<aop:after-returning>、<aop:after-throwing>、<aop:after>、<aop:around>があります他の通知タイプに対応します。
最後に、AOPがテストクラスで使用されます。xml構成とは異なり、aopタグが構成された後、元のオブジェクトo1およびo2は、プロキシオブジェクトを使用せずに直接使用できます。
@Test
void printLog() {
//读取上下文配置文件
ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
IOperation op1 = (IOperation) appCtx.getBean("o1"); //直接使用Operation1对象
IOperation op2 = (IOperation) appCtx.getBean("o2");
op1.doOperation();
op2.doOperation();
}
はじめに
動的クラスを含む一部の高水準言語と比較して、Javaはコンパイルされるとクラスに新しい関数を追加できなくなります。現時点では、Introductionを使用して、コンパイルされたクラスに新しいメソッドを追加できます。以下に示すように、Operation1クラスに新しいメソッドを導入します。最初に、導入されたインターフェイスIIntroductionを定義します。これは、インターフェイスIntroducedOperationのデフォルト実装クラスです。
public interface IIntroduction {
public void introduceOperate();
}
public class IntroducedOperation implements IIntroduction {
public void introduceOperate(){
System.out.println("执行引入的操作...");
}
}
次に、<aop:config>でOperation1クラスを構成して、上記のインターフェースを導入します。
<aop:config>
<aop:aspect id="logAop" ref="logHandler">
<!-- 为Operation1类引入IIntroduction接口 -->
<aop:declare-parents types-matching="com.aop.Operation1"
implement-interface="com.aop.IIntroduction"
default-impl="com.aop.IntroducedOperation"/>
</aop:aspect>
</aop:config>
テストは、テストでは次のようになります。Operation1を介してオブジェクトo1が導入されたメソッドを使用できることがわかります
@Test
void printLog() {
//读取上下文配置文件
ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
//将Operation1对象o1转化为IIntroduction,并且调用引入的方法
IIntroduction introduction=(IIntroduction) appCtx.getBean("o1");
introduction.introduceOperate();
}
3、AspectJ的AOP
構成を使用
まず、構成ファイルで、アスペクトアスペクトクラスの自動スキャンとプロキシを開きます
<?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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 对com.aop.aspectj包下的类进行自动扫描 -->
<context:component-scan base-package="com.aop.aspectj"/>
<!-- 开启aspectj自动代理 -->
<aop:aspectj-autoproxy/>
</beans>
次に、アスペクトクラスを定義します。AspectJは、@ Componentおよび@Aspectアノテーションを使用して、クラスを自動的にスキャンし、アスペクトクラスとして登録します。ポイントカットポイントは、ファセットクラスの@Pointcutアノテーションを介して機能的に定義されており、その戻り値はvoidです。事前通知は@Beforeによって定義されます。括弧内のパラメータはエントリポイントであり、以前に定義されたエントリポイントまたは式にすることができます。
package com.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAspect {
//定义切入点,其接入点为类Operation1下的所有方法
@Pointcut("execution(* com.aop.aspect.Operation1.*(..))")
public void logPoint(){}
//定义前置通知
@Before("logPoint()")
public void logBefore(){
System.out.println("aspect输出前置通知");
}
}
最後に、ターゲットクラスOperation1のdoOperation()メソッドを定義して使用し、操作を実行して結果を出力できます。
package com.aop.aspectj;
import org.springframework.stereotype.Component;
@Component
public class Operation1 {
public void doOperation(){
System.out.println("执行操作");
}
}
//测试方法
@Test
void aspectJ(){
ApplicationContext appCtx = new ClassPathXmlApplicationContext("aspectj-aop.xml");
Operation1 op1=(Operation1)appCtx.getBean("operation1");
op1.doOperation();
}
アノテーションを使用して構成ファイルを定義し、@ Configurationの構成クラスで@ComponentScanを介してコンポーネントクラスをスキャンし、@ EnableAspectJAutoProxyを介してAspectJの自動プロキシを有効にすることもできます。最後に、aopを使用する場合は、xml構成ファイルを使用する代わりに、AspectConfigクラスを使用して構成クラスをロードします。
package com.aop.aspectj;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.aop.aspectj")
@EnableAspectJAutoProxy
public class AspectConfig {
}
//测试类
@Test
void aspectJ(){
//通过AspectConfig来加载配置类
ApplicationContext appCtx=new AnnotationConfigApplicationContext(AspectConfig.class);
Operation1 op1=(Operation1)appCtx.getBean("operation1");
op1.doOperation();
}
アドバイス通知
上記の例では、@ Beforeを使用して事前通知を定義しています。同様に、@ AfterReturningを使用して、関数が戻ったときの通知を定義しています。そのパラメータpointcutはエントリポイントを指定します。ここでは、以前に定義されたエントリポイントの代わりに式が直接使用されます。戻りパラメーターは、ポイントカット関数によって戻された結果を受け取ります。@AfterThrowingは、関数が例外をスローしたときの通知を定義し、throwingパラメーターを使用して例外オブジェクトを受信できます。@Afterは、関数が例外をスローするかどうかに関係なく、ポスト通知を定義します。
@Component
@Aspect
public class LogAspect {
@AfterReturning(pointcut = "execution(* com.aop.aspectj.Operation1.doReturn(..))",
returning = "returnValue")
public void logReturning(Object returnValue){
System.out.println("返回值:"+returnValue);
}
}
@Aroundで定義されたサラウンド通知の使用に注意する価値があります。サラウンド通知は、ProceedingJoinPointオブジェクトをパラメーターとして受け取り、オブジェクトのproceed()メソッドを使用してpointcutメソッドを実行し、戻り値Objectを取得します。したがって、続行の前後に実行する必要がある事前通知操作と事後通知操作を定義できます()
//切面类的定义
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.aop.aspectj.Operation1.*(..))")
public void logPoint(){}
@Around("logPoint()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知前");
Object obj=pjp.proceed(); //执行切入点操作
System.out.println("环绕通知后,返回值:"+obj);
return obj;
}
}
//目标类Operation1
@Component
public class Operation1 {
public String doReturn(){
System.out.println("执行操作...");
return "这是返回值";
}
}
//测试类
@Test
void aspectJ(){
ApplicationContext appCtx = new ClassPathXmlApplicationContext("aspectj-aop.xml");
Operation1 op1=(Operation1)appCtx.getBean("operation1");
op1.doReturn();
}
結果は次のとおりです。
アドバイスにパラメーターを渡す
argsを使用して、通知内のポイントカット関数のパラメーターをキャプチャし、通知に渡します。
//切面类
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.aop.aspectj.Operation1.*(..))")
public void logPoint(){}
@Before("logPoint() && args(strArg)") //捕获连接点方法的参数
public void logBefore(String strArg){ //将参数传入到方法
System.out.println("Advice接收参数:"+strArg);
}
}
//目标类
@Component
public class Operation1 {
//连接点函数
public void doOperation(String str){
System.out.println("执行操作");
}
}
//测试方法
@Test
void aspectJ(){
ApplicationContext appCtx = new ClassPathXmlApplicationContext("aspectj-aop.xml");
Operation1 op1=(Operation1)appCtx.getBean("operation1");
op1.doOperation("一个字符串参数");
}
結果は
同様に、あなたが使用することができます@Annotationを注釈パラメータの取り込みに:@Before( "logPoint()&& @Annotation(strAnno)")
//自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnno {
String value();
}
//目标类
@Component
public class Operation1 {
@MethodAnno("这是一个注解字符串") //连接点函数添加注解
public void doOperation(){
System.out.println("执行操作");
}
}
//切面类
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.aop.aspectj.Operation1.*(..))")
public void logPoint(){}
@Before("logPoint() && @annotation(anno)") //通知接收注解
public void logBefore(MethodAnno anno){
System.out.println("Advice接收注解:"+anno.value());
}
}
出力結果