引言
补充OOP存在的缺陷——当多个不具有继承关系的对象引入同一个公共行为,这些行为与业务无关但是又对多个对象有影响,这样就引入代码冗余,耦合性高,代码难维护的问题,所以就引入了Aop。AOP会对业务处理过程中的切面(抽取并封装为一个可重用的模块,又被称为切面)进行提取,达到业务代码与公共行为代码之间低耦合性的隔离效果。
Spring AOP(Aspect-Oriented Programming)是Spring框架提供的一种面向切面编程的特性。是指在运行程序期间动态的将某段代码切入到方法指定位置进行运行的编程方式(使用动态代理或拦截器等技术来实现对程序执行过程的拦截和干预),是对OOP的扩展,同时实现了代码的复用。
aop适用于功能统一,使用地方又多的地方。
使用场景
AOP可以统一用户登陆判断,日志记录,返回给前端的格式设置,统一异常处理以及事务的的开启和提交。
性能监控:例如,记录数据库查询耗时、接口调用耗时等。
异常处理:例如,将业务异常转换为通用的错误码和错误消息。
安全验证(权限):例如,验证用户身份、权限检查等操作。
日志记录:例如,在方法执行前后记录方法名、参数、返回值等信息)。
用一个具体的例子来看一下:
实现性能的监控,有些重要的方法需要我们计算运行时间,我们可以有两种方法:
1.在每一个需要性能监控的方法前后都写上时间,最后在减;
2.直接在切入每个方法的前后拦截这些方法的执行,然后执行增强功能。
第二种方式,只需关注业务,把不涉及的业务的代码放在切面类。
Spring AOP构成
切面(Aspect)
在程序中处理具体问题的类,包含切点和通知。
在 AOP 中,横切关注点(横跨多个模块的关注点)被定义为与核心业务逻辑无关的关注点,如日志记录、安全性检查、事务管理等。这些横切关注点通常跨越了多个模块或类,并且可能在程序的不同部分重复出现。为了将这些横切关注点与核心业务逻辑分离,AOP 引入了切面(Aspect)的概念。切面是一个独立的模块,包含了与横切关注点相关的逻辑。在运行时,AOP 框架会动态地将切面中的逻辑切入到程序的特定点上,从而实现对程序执行过程的拦截和干预。
切点(Pointcut)
用来进行指定主动拦截的规则(决定哪些方法需要被增强)
通知(Advice)
用于实现切面的具体逻辑。并且也是需要增加到业务方法中的公共代码。
- 前置通知(@Before):在切点方法执行之前执行的通知。可以在方法执行之前做一些准备工作或参数校验等操作。
- 后置通知(@AfterReturning):在切点方法成功执行后执行的通知。可以获取切点方法的返回值,并进行一些后续处理。
- 异常通知(@AfterThrowing):在切点方法抛出异常时执行的通知。可以捕获异常并进行相应的处理操作,例如日志记录或异常处理。
- 最终通知(@After):无论切点方法正常执行还是抛出异常,最终通知都会在切点方法执行完毕后执行。它类似于finally块中的代码,不管是否发生异常都会执行。
- 环绕通知(@Around):环绕通知是最为灵活的通知类型,它可以完全控制切点方法的执行。在切点方法执行之前和之后都可以插入自定义的逻辑。需要在环绕通知中显式地调用 proceed() 方法来触发切点方法的执行。
这些通知注解可以与切点(Pointcut)注解一起使用,来定义在哪些方法或类上应用通知。通过在切面(Aspect)类中结合使用这些注解,可以实现对目标方法的增强功能。
连接点(Join Point)
可能触发APO规则的点(请求),指定被增强的业务方法就是连接点
从代码层面理解
添加依赖
在maven中央仓库中找这个,选择对应的版本
版本可以在这里看
我选的2.7.16
创建切面
使用注解@Aspect搭配Spring中的五大注解
@Aspect//切面类 @Component//随着Spring的启动而启动 public class UserAspect { }
创建切点
/** * 切点————配置拦截规则 */ @Pointcut("execution(* com.example.demoapo.controller.UserController.*(..))") public void pointcut(){ }
execution是最常见的切点函数:
aspect支持三种
*
..
+
创建通知
/** * 前置通知 * */ @Before("pointcut()") public void beforeAdvice(){ System.out.println("执行了前置通知"); } /** * * */ @After("pointcut()") public void afterAdvice(){ System.out.println("执行了后置通知"); }
注意环绕通知写法
/** * 环绕通知 * */ @Around("pointcut()") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("执行环绕"); Object obj=null; //执行目标方法; obj=joinPoint.proceed(); System.out.println("退出环绕"); return obj; }
原理实现
Spring AOP的实现原理主要基于动态代理机制和反射。
动态代理:
Spring AOP使用动态代理来生成代理对象,并将切面逻辑织入到目标对象的方法中。通过代理对象,可以在目标对象的方法执行前、执行后或异常抛出时插入额外的逻辑,即通知(Advice)。Spring AOP提供了两种动态代理方式:基于JDK动态代理和基于CGLib动态代理。spring对aop的拦截仅仅局限于方法,
织入
为目标对象创建动态代理的过程被称为织入
- 编译期:AspectJ织入的编译器就是这种
- 类加载期:切面在目标类被加载到JVM的时候进行织入
- 运行期
springaop被织入的方式就是动态代理
动态代理
是指在程序运行时动态生成代理对象的机制。它可以在不修改目标对象的情况下,通过代理对象来对目标对象进行间接访问和控制。
动态代理vs静态代理
- 静态代理:静态代理是指在编译时就已经确定了代理类和目标类之间的关系,代理类通过实现目标类的接口来拦截目标方法的调用。在运行时,代理类会被直接实例化,并通过调用目标类的接口方法来实现对目标方法的拦截和处理。
- 动态代理:动态代理是指在运行时动态生成代理类,并通过该代理类来拦截目标方法的调用。动态代理通常使用 Java 的反射机制来实现,通过动态生成字节码来创建代理类,并在运行时将其加载到内存中。
jdk动态代理(默认使用)
jdk只提供接口的代理,不提供类的代理
CGLib动态代理
如果代理类没有实现接口,Spring AOP会选择CGlib来动态代理目标类,代理对象通过继承目标类来拦截方法调用。
无法对final类,praivate方法,static方法实现代理。
需要注意的是,对于 private 方法和 static 方法,虽然无法通过动态代理来实现拦截,但可以通过其他方式来实现类似的功能,例如使用拦截器、过滤器等技术来实现对方法调用的拦截和处理。static 方法是指不需要实例化对象就可以直接调用的方法,它属于类而不是对象。由于动态代理是通过实例化代理类来实现对目标方法的拦截,而 static 方法不需要实例化对象就可以直接调用,因此无法通过动态代理来拦截 static 方法。
Spring的AOP在哪里创建的动态代理
1.bean的生命周期初始化之后;
2.在Bean属性注入时存在的循环依赖的情况下;
AOP 的工作原理
是发生在 Bean 的生命周期过程中的:
Spring 生成 bean 对象时,先实例化出来一个对象,也就是 target 对象
再对 target 对象进行属性填充
在初始化后步骤中,会判断 target 对象有没有对应的切面
如果有切面,就表示当前 target 对象需要进行 AOP
通过 Cglib 或 JDK 动态代理机制生成一个代理对象,作为最终的 bean 对象