個人的な簡単な紹介: Java 分野の新星クリエーター; アリババ クラウド テクノロジー ブロガー、スター ブロガー、エキスパート ブロガー; Java の学習途中、学習プロセスの記録~ 個人ホームページ: .29. のブログ 学習コミュニティ
:入っ
て、お散歩〜
AOP - アスペクト指向プログラミング
1. AOP について簡単に説明する
AOP - アスペクト指向プログラミング。プリコンパイルと実行時の動的プロキシにより、プログラム機能の統一的なメンテナンスを実現する技術。
Spring フレームワークの重要なコンテンツです。AOP は、ビジネス ロジックのさまざまな部分を分離するために使用できます。これにより、ビジネス ロジックのさまざまな部分間の結合度が減少し、プログラムの再利用性が向上し、同時に開発の効率が向上します。
AOP の役割:
-
コードを単純化する:メソッド内の固定された位置で繰り返されるコードを抽出し、抽出されたメソッドがコア機能により集中し、まとまりを改善できるようにします。
-
コードの拡張: 特定の関数をアスペクト クラスにカプセル化し、必要な場所に適用するとアスペクト ロジックで適用されるメソッドがアスペクトによって拡張されます。
2. AOP の基本原則
AOP はアスペクト指向プログラミングであり、最下層で動的プロキシを使用して実装され、次の 2 つの状況に分けることができます。
- インターフェイスがある場合は、JDK 動的プロキシを使用します
- インターフェイスがない場合は、CGLIB 動的プロキシを使用します。
JDK动态代理
: インターフェイス実装クラスのプロキシ オブジェクトを作成し、クラスのメソッドを拡張します;
CGLIB动态代理
: サブクラスのプロキシ オブジェクトを作成し、クラスのメソッドを拡張します;
3.動的プロキシを実現する(事例)
使用的相关Maven依赖:
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
① 声明计算器接口Calculator,包含加减乘除的抽象方法
:
/**
* @author .29.
* @create 2023-02-04 15:01
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
② 创建接口实现类,实现加减乘除等方法
:
import org.springframework.stereotype.Component;
/**
* @author .29.
* @create 2023-02-04 15:02
*/
//简单功能的实现类
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
③ 编写被代理对象的工厂类
:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author .29.
* @create 2023-02-04 22:05
*/
public class ProxyFactory {
//目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
//返回代理对象
public Object getProxy(){
//动态代理返回对象
//调用Proxy类中的newProxyInstance(),获取动态代理对象
/*
* newProxyInstance()中需要传入三个参数
* ① ClassLoader:加载动态生成代理类的 类加载器
* ② Class[] interfaces: 目标对象实现的所有接口
* ③ InvocationHandler: 设置代理对象实现目标对象方法的过程
* */
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler(){
//参数一:代理对象
//参数二:需要重写目标对象的方法
//参数三:method方法(参数二)中的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法调用前输出的日志
System.out.println("[动态代理][日志]"+method.getName()+",参数:"+ Arrays.toString(args));
//调用目标方法
Object result = method.invoke(target, args);
//方法调用后输出的日志
System.out.println("[动态代理][日志]"+method.getName()+",结果:"+ result);
return result;
}
};
//调用Proxy类中的newProxyInstance(),获取动态代理对象
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
}
④ 测试
:
/**
* @author .29.
* @create 2023-02-04 22:24
*/
public class TestCalculator {
public static void main(String[] args) {
//创建代理对象
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
//通过代理对象获取目标对象
Calculator proxy = (Calculator) proxyFactory.getProxy();
//调用目标对象方法
proxy.add(1,1);
System.out.println();
proxy.div(1,1);
System.out.println();
proxy.mul(1,1);
System.out.println();
proxy.sub(1,1);
System.out.println();
}
}
4. AOP 用語
1. 横切关注点
:
ユーザー認証、ログ管理、トランザクション処理、データ キャッシングなど、同じ問題を解決するために各モジュールに分散されているのは、すべて分野横断的な問題です。
各メソッドから抽出された同じクラスのノンコア ビジネス。同じプロジェクトで、関連するメソッドのいくつかの異なる側面を強化するために、複数の分野横断的な懸念を使用できます。
この概念は構文レベルではありませんが、追加関数の論理的な必要性に従っています。10 個の追加関数があり、10 個の分野横断的な関心事があります。
2. 通知
:
分野横断的な関心事で行われるすべてのことは、メソッドを記述することによって実装する必要があります. このようなメソッドは通知メソッドと呼ばれます.
- 事前通知: @Before アノテーションを使用して、プロキシされたターゲット メソッドの前に実行します
- リターン通知: @AfterReturning アノテーションでマークされ、プロキシされたターゲット メソッドが正常に終了した後に実行されます( end of life )
- 例外通知: @AfterThrowing アノテーションを使用して、プロキシされたターゲット メソッドが異常終了した後に実行します(予期せず終了します) 。
- 通知後: @After アノテーションを使用して、プロキシされたターゲット メソッドが最終的に終了した後に識別して実行します(最終的な結論)
- サラウンド通知: @Around アノテーションを使用して識別し、try...catch...finally 構造を使用して、上記の 4 つの通知に対応するすべての場所を含む、プロキシされたターゲット メソッド全体を囲みます。
さまざまな通知の実行順序:
- Spring バージョン 5.3.x より前:
- 予告
- 対象操作
- 投稿通知
- リターン通知または例外通知
- Spring バージョン 5.3.x 以降:
- 予告
- 対象操作
- リターン通知または例外通知
- 投稿通知
3. 切面
:
つまり、通知メソッドをカプセル化するクラスです。
4. 目标
:
つまり、プロキシされるターゲット オブジェクト。
5. 代理
:
通知をターゲット オブジェクトに適用した後に作成されるプロキシ オブジェクト。
6. 连接点
:
これも純粋に論理的な概念であり、文法的に定義されたものではありません。
メソッドを一列に並べ、横切る位置をx軸方向、メソッドを上から下に実行する順番をy軸とし、その交点をx 軸と y 軸は接続点です。簡単に言えば、春に通知を使用できる場所です
7. 切入点
:
接続ポイントを見つける方法。
各クラスメソッドには複数のジョインポイントが含まれているため、ジョインポイントはクラスの客観的な (論理的に言えば) ものです。
結合ポイントをデータベース内のレコードと考えると、ポイントカットはレコードを照会する SQL ステートメントです。
Spring の AOP テクノロジーは、ポイントカットを通じて特定の接続ポイントを見つけることができます。平たく言えば、実際に強化する方法
ポイントカットは、結合ポイントのクエリ条件としてクラスとメソッドを使用する org.springframework.aop.Pointcut インターフェースによって記述されます。
5. AOPアノテーション方式の実現
注意:
エントリ ポイント式の構文:
実行 (許可修飾子拡張メソッドの戻り値の型拡張メソッドが配置されているクラスのフル パス。メソッド名 (メソッド パラメーター))
1. 导入Maven依赖
:
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
2. 准备被代理的 接口+实现类
:
/**
* @author .29.
* @create 2023-02-04 15:01
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
import org.springframework.stereotype.Component;
/**
* @author .29.
* @create 2023-02-04 15:02
*/
//简单功能的实现类
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
※3 创建并配置切面类
.:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author .29.
* @create 2023-02-04 22:47
*/
//切面类
//@Aspect代表这是一个切面类
//@Component代表将此类交给Spring处理,能够放入IOC容器中去
@Aspect
@Component
public class LogAspect {
//设置切入点 和 通知类型
//切入点表达式:execution(权限修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))
//重用切入点表达式:使用@Pointcut注解,设置需要重复使用的切入点表达式
@Pointcut(value = "execution(public int com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")
public void pointCut(){
}
//之后,在使用通知时,将切入点表达式用 方法名 替换,即pointCut()(同一个切面)
//若在不同的切面,需要加上重用切入点表达式方法的全类名:com.haojin.spring.aop.annoaop.LogAspect.pointCut()(不同的切面)
//通知类型:
// @Before("切入点表达式配置切入点") 前置
@Before(value = "pointCut()") //重用切入点表达式,使用方法名替换
public void beforeMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();//获取增强方法的名字
Object[] args = joinPoint.getArgs(); //获取增强方法的参数数组
System.out.println("Logger-->前置通知,增强方法为:"+name+",参数:"+ Arrays.toString(args));
}
// @AfterReturning() 返回
//返回通知 可以获取到返回值,在切入表达式中增加返回值属性:returning = ""
@AfterReturning(value = "pointCut()",returning = "result")
//增强方法中需要添加 返回值参数result,参数名与切入点表达式保持一致(result)
public void afterReturningMethod(JoinPoint joinPoint,Object result){
//可以存在参数JoinPoint,以此来获取信息
String name = joinPoint.getSignature().getName();//获取增强方法的名字
System.out.println("Logger-->返回通知,增强方法为:"+name+",返回结果:"+result);
}
// @AfterThrowing() 异常
//目标方法出现异常时,此通知会执行,在切入表达式中增加属性:
@AfterThrowing(value = "pointCut()",throwing = "exception")
//增加 Throwable类型参数,参数名必须与切入点表达式保持一致(exception)
public void aAfterThrowingMethod(JoinPoint joinPoint,Throwable exception){
//可以存在参数JoinPoint,以此来获取信息
String name = joinPoint.getSignature().getName();//获取增强方法的名字
System.out.println("Logger-->异常通知,增强方法为:"+name+"异常信息:"+exception);
}
// @After() 后置
@After(value = "execution(* com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint){
//可以存在参数JoinPoint,以此来获取信息
String name = joinPoint.getSignature().getName();//获取增强方法的名字
System.out.println("Logger-->后置通知,增强方法为:"+name);
}
// @Around() 环绕通知,可以通过try-catch-finally,使得增强方法在所有阶段执行(增强方法可设置返回值)
@Around("execution(public int com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")
//可设置 ProceedingJoinPoint属性参数,以此调用增强方法(JoinPoint的子接口,JoinPoint没有调用目标方法的功能)
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String name = joinPoint.getSignature().getName();//通过ProceedingJoinPoint参数获取增强方法名
Object[] args = joinPoint.getArgs(); //获取增强方法参数数组
String argStr = Arrays.toString(args); //参数数组转字符串
Object result = null;
try{
System.out.println("环绕通知 == 增强方法执行前执行");
//通过ProceedingJoinPoint类型参数调用增强方法
result = joinPoint.proceed();
System.out.println("环绕通知 = 增强方法返回值之后执行");
}catch(Throwable throwable){
//捕捉Throwable类型异常
throwable.printStackTrace();
System.out.println("环绕通知 = 增强方法异常时执行");
}finally {
System.out.println("环绕方法 = 增强方法执行完毕执行");
}
return result;
}
アスペクトの優先度:
アスペクトの優先度は、
アスペクトの内側と外側の入れ子の順序を制御します 外側のアスペクト: 優先度が高い
内側のアスペクト: 優先度が低い...
@Order() アノテーションを使用して優先
順位
を設定することができます
4. 配置Spring配置文件(这里命名为 bean.xml)
:
<?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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<context:component-scan base-package="com.haojin.spring.aop.annoaop"></context:component-scan>
<!--开启AspectJ的自动代理-->
<aop:aspectj-autoproxy />
</beans>
5. 测试
:
import com.haojin.spring.aop.annoaop.Calculator;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author .29.
* @create 2023-02-06 9:34
*/
public class TestAop {
//以实现类中的加法功能为例
@Test
public void testAdd(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Calculator bean = context.getBean(Calculator.class);
bean.add(3,13);
}
}