AOP面向切面编程
2021/12/20 22:19:55
本文主要是介绍AOP面向切面编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
AOP面向切面编程
静态代理和动态代理
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
动态代理
将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现,JDK本身就支持动态代理,这是反射技术的一部分。
public class TestStaticProxy { @Test public void testProxy(){ //1. 创建一个CalculatorPureImpl对象:被代理者对象 Calculator calculator = new CalculatorPureImpl(); //2. 创建代理者对象:使用动态代理技术创建 //动态代理技术分为两种: //1. JDK内置的动态代理技术:要求被代理者必须实现接口,它的底层其实就是使用反射创建接口的实现类对象 //2. CGLIB的动态代理技术(需要引入依赖):被代理者可以不实现接口,它的底层其实是创建被代理类的子类对象 //我们现在使用JDK的动态代理技术 Class<? extends Calculator> clazz = calculator.getClass(); //参数一:用于定义代理对象的类加载器:和被代理者的类加载器是同一个类加载器 ClassLoader classLoader = clazz.getClassLoader(); //参数二:表示要被代理的接口的集合 //获取参数二的方式一: 获取被代理者实现的所有接口 Class<?>[] interfaces = clazz.getInterfaces(); //获取参数二的方式二: 自己编写一个数组,将你想要代理的接口放里面new Class[]{Calculator.class} //参数三:InvocationHandler接口的实现类对象或者是匿名内部类对象,在它里面真正编写代理逻辑 Calculator calculatorProxy = (Calculator) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //这个方法中就编写具体的代理逻辑 //invoke()方法什么时候会执行:当代理对象调用任何方法的时候都会执行invoke() //proxy参数:就是调用方法的代理对象 //method参数:代理对象调用的那个方法 //args参数:代理对象调用的那个方法的参数 //目标:在被代理者的add、sub、mul、div方法前后添加日志打印 //1. 判断方法名是否是add、sub、mul、div String methodName = method.getName(); if (methodName.equals("add") || methodName.equals("sub") || methodName.equals("mul") || methodName.equals("div")) { //先执行前置打印日志 System.out.println("[日志] "+methodName+" 方法开始了,参数是:" + args[0] + "," + args[1]); //执行被代理者的核心逻辑 Object result = method.invoke(calculator, args); //执行后置打印日志 System.out.println("[日志] "+methodName+" 方法结束了,结果是:" + result); return result; } //返回值是返回给代理对象调用的那个方法 //对于不需要进行代理的方法,就执行被代理者原本的方法 return method.invoke(calculator,args); } }); int result = calculatorProxy.sub(2, 3); System.out.println("在外面打印代理对象执行运算的结果:" + result); } }
使用代理模式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KroIFJ3I-1640004690352)(./img/003.jpg)]
相关术语
①代理:又称之为代理者,用于将非核心luoji剥离出来,封装这些非核心逻辑类、方法、对象
②目标:用于核心逻辑,并把这些代理者非核心逻辑用在类、对象方法上
AOP
简化代码:把固定重复的代码抽取出来,让其方法更专注于自己的核心功能,提高内聚性
代码增强:抽取出来的方法,放在切面类里面,看哪里需要就往哪里套,被套用的切面楼价就被切面类增强了
AOP的核心思路
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRh3PdmJ-1640004690360)(./img/004.jpg)]
相关术语
横切关注点
从每个方法中抽取同一类非核心业务
通知
每个横向切点都需写一个方法来实现
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDBspPJD-1640004690363)(.\img\005.png)]
切面
封装通知的方法类
目标
被代理目标对象
代理
向目标对象应用通知之后创建的代理对象
连接点
各个方法中可以被增强或修改的点
切入点
方法中真正要去配置增强或者配置修改的地方
注解方式配置AOP
1、加入依赖
<dependencies> <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- spring-aspects会帮我们传递过来aspectjweaver --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency> <!--spring整合Junit--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version> </dependency> </dependencies>
2、准备接口
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); }
3、接口的实现类
import org.springframework.stereotype.Component; @Component public class CalculatorPureImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; //int num = 10 / 0; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
4、创建切面类
/** * Aspect注解:指定一个切面类 * Component注解: 对这个切面类进行IOC * * 注解AOP的关键点: * 1. 一定要在配置文件中加上<aop:aspectj-autoproxy />表示允许自动代理 * 2. 切面类一定要加上Aspect注解,并且切面类一定要进行IOC * 3. 其它的类该进行IOC和依赖注入的就一定要进行IOC和依赖注入 * 4. 通知上一定要指定切入点(怎么使用切入点表达式描述切入点又是一个难点) */ @Aspect @Component public class LogAspect { @Before("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))") public void printLogBeforeCore(){ System.out.println("[前置通知]在方法执行之前打印日志..."); } @AfterReturning("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))") public void printLogAfterReturning(){ System.out.println("[返回通知]在方法执行成功之后打印日志..."); } @AfterThrowing("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))") public void printLogAfterThrowing(){ System.out.println("[AOP异常通知]在方法抛出异常之后打印日志..."); } @After("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))") public void printLogFinallyEnd(){ System.out.println("[AOP后置通知]在方法最终结束之后打印日志..."); } }
5、Spring的配置文件
<!--包扫描--> <context:component-scan base-package="com.wwb"/> <!--允许注解AOP--> <aop:aspectj-autoproxy />
6、测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-application.xml") //@ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)联合使用用来测试 public class TestAop { @Autowired private Calculator calculator; @Test public void testAdd(){ //调用CalculatorPureImpl对象的add()方法 System.out.println("返回值是:"+calculator.add(1, 2)); } }
[AOP前置通知] 方法开始了 方法内部 result = 12 [AOP返回通知] 方法成功返回了 [AOP后置通知] 方法最终结束了 方法外部 add = 12
在通知内部获取细节信息
JoinPoint接口
-
要点1:JoinPoint接口通过getSignature()方法获取目标方法的签名
-
要点2:通过目标方法签名对象获取方法名
-
要点3:通过JoinPoint对象获取外界调用目标方法时传入的实参列表组成的数组
// 1.通过JoinPoint对象获取目标方法签名对象 // 方法的签名:一个方法的全部声明信息 Signature signature = joinPoint.getSignature(); // 2.通过方法的签名对象获取目标方法的详细信息 String methodName = signature.getName(); System.out.println("methodName = " + methodName); // 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表 Object[] args = joinPoint.getArgs(); // 4.由于数组直接打印看不到具体数据,所以转换为List集合 List<Object> argList = Arrays.asList(args);
获取目标方法的方法返回值
只有在AfterReturning返回通知中才能够获取目标方法的返回值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M4wzQwaw-1640004690366)(./img/006.jpg)]
切入点
声明切入点
易于维护,一处修改,处处生效。
@Pointcut("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))") public void calculatorPointCut(){ }
同一个类内部引用切入点
通过方法名引入
@Before("calculatorPointCut()") public void printLogBeforeCore(JoinPoint joinPoint){
其它类中引用切入点
@Before("com.wwb.pointcut.wwbPointCut.calculatorPointCut()") public void printLogBeforeCore(JoinPoint joinPoint){}
对项目中的所有切入点进行统一管理
public class wwbPointCut { @Pointcut("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))") public void calculatorPointCut(){ } }
总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqEevW1E-1640004690368)(.\img\007.jpg)]
环绕通知
环绕通知对应整个try…catch…finally结构,可以在目标方法的各个部位进行套用代理逻辑,它能够真正介入并改变目标方法的执行
基于XML方式配置AOP
<!--包扫描--> <context:component-scan base-package="com.wwb"/> <!-- 使用xml方式配置AOP: 1. 切面: 封装非核心逻辑的那个类,非核心逻辑就是封装在切面的方法中 2. 通知: 将非核心逻辑套在核心逻辑上进行执行 3. 切入点: 核心逻辑 --> <aop:config> <!-- 1. 切面: ref属性就是指定作为切面的那个对象的id,order属性表示切面的优先级 --> <aop:aspect id="myAspect" ref="logAspect"> <!--2. 通知--> <!--配置前置通知--> <aop:before method="printLogBeforeCore" pointcut-ref="calculatorPoint"/> <!--配置返回通知--> <aop:after-returning method="printLogAfterReturning" pointcut-ref="calculatorPoint" returning="result"/> <!--配置异常通知--> <aop:after-throwing method="printLogAfterThrowing" pointcut-ref="calculatorPoint" throwing="throwable"/> <!--配置后置通知--> <aop:after method="printLogFinallyEnd" pointcut-ref="calculatorPoint"/> <!--配置环绕通知--> <aop:around method="printLogAround" pointcut-ref="calculatorPoint"/> <!--3. 切入点--> <aop:pointcut id="calculatorPoint" expression="execution(* com.wwb.component.CalculatorPureImpl.*(..))"/> </aop:aspect> </aop:config>
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-application.xml") public class TestAop { @Autowired private Calculator calculator; @Test public void testAdd(){ //调用CalculatorPureImpl对象的add()方法 System.out.println("调用完目标方法之后获取返回值是:"+calculator.sub(5, 3)); } }
这篇关于AOP面向切面编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求
- 2024-10-01使用 golang 将ETH账户的资产平均分散到其他账户
- 2024-10-01JWT用户校验课程:从入门到实践
- 2024-10-01Server Component课程入门指南
- 2024-09-30Dnd-Kit学习:新手快速入门指南