本文主要是介绍Spring6 - AOP,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、需求场景模拟
假设我们现在有一个计算器,能够实现加法计算的功能,
但是此时我们想要为它添加日志的功能
此时我们就不能单纯的把日志提取出来作为一个单独的方法然后调用,因为日志功能是嵌进去的,与核心代码混合了起来,因此我们使用到AOP面向切面编程
二、代理模式
代理模式是一种结构型设计模式,AOP底层就是使用了动态代理
类比一下代理模式就是,
坤坤负责唱跳rap篮球,经纪人负责找鸡棚写律师函,经纪人是坤坤的代理
代理模式的特点就是,对象想要什么功能被代理,其中代理也需要有该功能
经纪人体内也需要有唱跳的方法,但是它不需要会真正的唱跳功能
小黑子要看唱跳rap篮球,就先找到了经纪人,他会先准备场地然后去找坤哥,坤坤这时候才真正的开始music
静态代理
这个类是代理类,其中会调用calculator对象的真正功能代码,代理类本身只实现日志功能
缺点:
静态代理 确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活i行。就拿这个日志功能来书哟,若是将来其他的地方也需要附加日志,那么还得声明多个静态代理类,那就产生了大量的重复代码,没有统一管理。
因此我们提出功能,将日志功能集中到一个代理类中,将来有任何日志需求,都通过这个代理类中来实现,这就是动态代理
动态代理
我们会使用到java.lang包下的Proxy类的newProxyInstance()方法
其中有三个参数
- ClassLoader: 加载动态生成代理类的类加载器
- Class<?>[] interfaces: 目标对象实现的所有接口class类型数组
- InvocationHandler: 设置代理对象实现目标对象方法的过程
我们直接使用现成的代理类来讲解
package com.wal;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class ProxyFactory {//目标对象private Object target;//返回代理对象public ProxyFactory(Object target){this.target = target;}//返回代理对象public Object getProxy(){//1、ClassLoader: 加载动态生成代理类的类加载器ClassLoader classLoader = target.getClass().getClassLoader();//2、Class<?>[] interfaces: 目标对象实现的所有接口class类型数组Class<?>[] interfaces = target.getClass().getInterfaces();//3、InvocationHandler: 设置代理对象实现目标对象方法的过程InvocationHandler invoke = new InvocationHandler(){/*** 参数一: 代理对象* 参数二: 需要重写目标对象的方法* 参数三: method方法里的参数*/@Overridepublic Object invoke(Object proxy,Method method,Object[] args) throws Throwable {//在这里写调用方法前的逻辑System.out.println("[日志]:" + method.getName() + "方法开始了: 参数是 i = " + args[0] + ", j = " + args[1]);//调用目标的方法Object result = method.invoke(target,args);//这里写调用方法后的逻辑System.out.println("[日志]:" + method.getName() + "方法结束了: result = " + args[0] + " + " + args[1] + " = " + result );return result;}};return Proxy.newProxyInstance(classLoader,interfaces,invoke);}
}
Proxy.newProxyInstance()方法能直接生成所需代理类,我们所需要做的就是传入所需三个参数
其中需要注意的是InvocationHandler()方法,我们使用匿名内部类的方式来编辑代理过程,只需要重写invoke方法即可,
其中三个参数分别是
- proxy:代理对象
- method:这个是代理对象的方法
- args:方法里面的参数
我们对比静态代理和动态代理的这部分就好理解了
静态
动态
测试结果
public class TestProxy {public static void main(String[] args) {//创建动态代理类ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());//得到代理对象Calculator proxy = (Calculator) proxyFactory.getProxy();//调用代理对象的add,其中会调用真正的Calculator的add方法proxy.add(1,2);}
}
动态代理分类
JDK动态代理:有接口,生成接口实现类的代理对象,要求代理对象和目标对象要实现同样的接口(任兄弟模式)
cglib动态代理:无接口,生成子类代理对象,通过继承被代理的目标类(认干爹模式)
AspectJ:是AOP思想的一种,本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终的效果是动态的。weaver就是织入器,Spring只是借用了AspectJ中的注解
3、AOP面向切面编程
AOP是一种设计思想,它是面向对象编程的一种补充和完善,它通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的技术。
利用AOP可以队业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序可重用性,提高开发效率。
相关术语
类比一下:
连接点JoinPoint:所有英雄,Sping中的方法
切入点:选择的要增强的英雄,
通知类:buff库
通知:要使用的buff
切面:从Buff库中拿出具体的buff增益给选择英雄的关系
作用:
基于注解的AOP
快速入门
引入依赖
<!-- AOP依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.0.9</version></dependency><!-- spring aspects依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.9</version></dependency>
使用xml文件时记得配置context和aop,直接输入<context:component-scan和<aop:aspectj-autoproxy,idea会自动添加至命名空间
<?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"><context:component-scan base-package="com.wal.aop"/><aop:aspectj-autoproxy/>
</beans>
创建目标资源
我们依旧使用前面动态代理的计算器例子
创建切面类
既然我们已经开启了context自动扫描,我们就可以使用@Component注解让该类被扫描到
设置切入点和通知类型
通知类型
- 前置 @Before( value = "切入点表达式配置切入点" )
- 返回 @AfterReturning()
- 异常 @AfterThrowing()
- 后置 @After()
- 环绕 @Around()
切入表达式
我们写一个例子来解释
execution( 访问修饰符 增强方法的返回类型 增强方法所在类的全部路径.方法名称(方法参数) )
@Before(value = "execution(public int com.wal.aop.CalculatorImpl.*(..))")
在这个切面表达式中
execution()是固定格式,表示是一个切面表达式
public int是增强方法的访问修饰符是public,增强方法的返回值是int
com.wal.aop是访问的包名,
*是全部的意思,包.*就是包下全部类,包..*就是包下全部包和类,类.*就是全部方法
*(..)中的两个点表示参数列表可以任意
我们现在有一个切面类
实现类和其增强方法
测试类
查看效果
这就是前置通知的效果
其他几种通知
其实通知方法中还有一个参数Joinpoint,这个参数能获取通知的信息(方法参数,增强方法名等)
返回通知
返回通知在后置通知之前执行,他的特点是能够得到增强方法的返回值
参数为returning,得到该方法的返回值,其类型是Object
异常通知
目标方法出现异常,这个通知才会执行
我们来模拟一下异常,在调用的增强方法中添加异常
测试
因为出现了异常,返回通知并没有触发,但是后置通知依旧会运行
可以这样记忆,返回通知就是寿终正寝,异常处理就是死于非命,后置通知就是吃席
你不可能同时寿终正寝然后死于非命,不管你是哪种方式,最后都得吃席
环绕通知
使用try...catch...finally结构围绕整个被代理的目标方法,而且能把其他通知的位置给占了
但由于它并不像返回通知一样可以在execution有直接的返回值参数,要想得到方法返回值我们需要使用JoinPoint的子类ProceedingJoinPoint,它拥有更多的功能,
使用proceed方法即可调用目标方法得到其返回值
仅使用环绕通知就实现了刚才的效果
重用切入点表达式
重用 -> 重复使用的切入点表达式
我们回顾刚才的示例代码,发现其实execution()表达式其实重复性都很高,这样一来我们自然就想去复用它
但是我们直接导出使用是不可以的,我们需要这样做
使用Pointcut()注解将execution存在方法pointCut中
注意这样使用需要在同一切面上,不同切面上需要在前面加上包名.类.pointCut方法
切面的优先级
相同目标方法上勇士存在多个切面时,切面的优先级控制切面的内外嵌套顺序
优先级: 外面 > 里面
使用Oreder注解可以控制切面的优先级
Order(数): 数越小,优先级越高
基于xml的aop
我们一般使用全注解开发,这个了解即可
首先切面类上就不要使用@Aspect注解了,这会替换掉xml文件
在xml文件中开启组件扫描,配置切面类
测试
、
尽量还是使用注解开发
这篇关于Spring6 - AOP的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!