Spring AOP 执行流程及源码分析(上)

简介: Spring AOP 执行流程及源码分析

前期认识

  • 切面(Aspect):切面编程的完整定义模块,包含了何时、对谁、如何等等所有的内容
  • 连接点(Join point):能够植入切面的部分,被运用到哪些方法上
  • 通知(Advice):要对切面添加的功能代码,比如权限、guava 限流、事务,日志等功能代码
  • 切入点(Pointcut):针对哪些方法植入通知,也就是指定具体的拦截地点
  • 引入(Introduction):对目标类添加新方法及属性
  • 目标对象(Target object):被切面处理的对象,目标对象无法感知到切面的存在
  • 代理(proxy):实现 AOP 方式,基于 JDK、CGLIB
  • 织入(Weaving):将切面应用到目标对象来创建新的代理对象的过程,有三种方式:spring 采用的是运行时

通知(Advice)五种类型

  • @Before:前置通知,在调用目标方法之前执行通知定义的任务
  • @After:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
  • @AfterReturning:最终通知,在目标方法执行结束后,无论执行结果如何,都要执行通知定义的任务
  • @AfterThrowing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
  • @Around:环绕通知,在目前方法执行的前后,都需要执行通知定义的任务

我们如何做?

  1. 先编写额外的逻辑类,也就是切面:AspectJ
  2. 具体的哪些方法要被执行处理,需要配置表达式:expression、point-cut
  3. 额外的逻辑处理,有几个通知消息或者说有哪些逻辑可以被执行

AOP 注解方式读取准备工作

先介绍在使用 AOP 时的一些核心接口、类、方法:

  • Advisor:AOP 顶级接口,用来管理 advice、point-cut,其下有两个子接口:PointcutAdvisor、IntroductionAdvisor,它们之间的区别在于

IntroductionAdvisor 应用于类级别的拦截,只能使用 Introduction 类型的 Advice

PointcutAdvisor 应用于方法级别的拦截 ,可以使用任何类型的 Pointcut,以及几乎任何类型的 Advice

  • AspectJPointcutAdvisor:最外层的通知者类,内部含有 Advice > 包含五种实现增强器、AspectJExpressionPointcut 类型
  • AspectJExpressionPointcut:该类属于原型模式,每一个 advisor 都会依赖一个新的该类型,它还实现了两个接口:分别是类表达式匹配器 ClassFilter > 只有一个匹配方法函数式编程接口,只应用于接口和类上的匹配、方法表达式匹配器 MethodMatcher > 内部有三个方法,可以支持无参数的静态匹配,也支持有参数的动态匹配
  • AnnotationAwareAspectJAutoProxyCreator:AspectJAwareAdvisorAutoProxyCreator 子类,使用 AspectJ 语法创建 Advisor 和代理对象的类,<aop:aspectj-autoproxy /> 标签默认注入到 BeanDefinitionNames、BeanDefinitionMaps 中,SpringBoot 方式也是会通过该类型去进行相关类型的解析工作.

Advisor、Advice

resolveBeforeInstantiation -> applyBeanPostProcessorsBeforeInstantiation -> AbstractAutoProxyCreator#postProcessBeforeInstantiation -> shouldSkip(beanClass, beanName)

// 查找通知器
@Override
protected List<Advisor> findCandidateAdvisors() {
  // 找到系统中实现了Advisor接口的bean,如果存在就放入缓存,并进行创建,然后返回
  List<Advisor> advisors = super.findCandidateAdvisors();
  // 找到系统中使用 @Aspect 标注 bean,并且找到该 bean 中使用 @Before,@After 等标注的方法
  if (this.aspectJAdvisorsBuilder != null) {
    // 将这些方法封装为一个个 Advisor
    advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
  }
  return advisors;
}

先是找到所有实现了 Advisor 接口 Bean,再获取到有标识 @AspectJ 注解的类,然后扫描它下面的所有除了标识 @PointCut 注解的 advisor 方法后,设置表达式:可能是方法也可能是表达式,取决于配置的方式,再进行 advisor 实例化,advisor 实例化也是需要三个对象的,分别是 MetadataAwareAspectInstanceFactory、Method、AspectJExpressionPointcut

buildAspectJAdvisors->this.advisorFactory.isAspect(beanType)->this.advisorFactory.getAdvisors(factory)->getAdvisor(method,…) ->new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut,…)->instantiateAdvice(this.declaredPointcut)

private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
    // 入参为切点表达式类
    // 这里是通过调用 aspectJAdvisorFactory 获取 Advice
    // aspectJAdvisorFactory 实例是 ReflectiveAspectJAdvisorFactory 
    // 所以最终我们还是要到 ReflectiveAspectJAdvisorFactory 中去分析 Advice 获取过程
    // ReflectiveAspectJAdvisorFactory 是一个重要的类 Advisor 和 Advice 获取都是在这个类中完成的
    // 入参为:通知方法、切点表达式类、切面实例、切面的一个顺序、切面类名
    Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
    return (advice != null ? advice : EMPTY_ADVICE);
}

到这里,就会提前把每个 Bean 需要增强的通知提前准备好,存入到缓存中

总结创建过程

  1. 创建 AspectJPointcutAdvisor#0-4,先通过带参的构造方法进行对象的创建,但是想使用带参数的构造参数,必须要把参数对象提前准备好,因此要先准备创建好内置包含的对象,例如:AspectJAroundAdvice
  2. 创建 AspectJAroundAdvice,也需要通过带参的构造函数进行创建,提前准备好具体的参数对象,包含三个参数:MethodLocatingFactoryBean、AspectJExpressionPointcut、SimpleBeanFactoryAwareAspectInstanceFactory
  3. 分别创建好上述的三个对象,其创建过程都是调用无参的构造方法,直接反射生成即可
  4. 总体来说,XML 文件配置 AOP、注解配置 AOP 在流程上是差不多的,只是注解是一次性全部到位,但 XML 要提前做 BeanDefinition 准备工作

代码部分

顶级接口

public interface Calculator {
    public Integer add(Integer i,Integer j) throws NoSuchMethodException;
    public Integer sub(Integer i,Integer j) throws NoSuchMethodException;
    public Integer mul(Integer i,Integer j) throws NoSuchMethodException;
    public Integer div(Integer i,Integer j) throws NoSuchMethodException;
}

接口实现类

// 实现接口采用的就是 JDK 代理
public class MyCalculator /*implements Calculator*/ {
    public Integer add(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i+j;
        return result;
    }
    public Integer sub(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i-j;
        return result;
    }
    public Integer mul(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i*j;
        return result;
    }
    public Integer div(Integer i, Integer j) throws NoSuchMethodException {
        Integer result = i/j;
        return result;
    }
    public Integer show(Integer i){
        System.out.println("show .....");
        return i;
    }
    @Override
    public String toString() {
        return "super.toString()";
    }
}

配置 XML 文件

<bean id="logUtil" class="com.vnjohn.aop.xml.util.LogUtil"/>
<bean id="myCalculator" class="com.vnjohn.aop.xml.service.MyCalculator"/>
<!-- <aop:config>
     <aop:aspect ref="logUtil">
            <aop:pointcut id="myPoint" expression="execution( Integer com.vnjohn.aop.xml.service.MyCalculator.*  (..))"/>
            <aop:around method="around" pointcut-ref="myPoint"/>
            <aop:before method="start" pointcut-ref="myPoint"/>
            <aop:after method="logFinally" pointcut-ref="myPoint"/>
            <aop:after-returning method="stop" pointcut-ref="myPoint" returning="result"/>
            <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/>
        </aop:aspect>
    </aop:config>-->
<aop:aspectj-autoproxy/>

AOP 代理增强类

@Aspect
public class LogUtil {
    @Pointcut("execution(public Integer com.vnjohn.aop.xml.service.MyCalculator.*(Integer,Integer))")
    public void myPointCut(){}
//    @Pointcut("execution(* *(..))")
    public void myPointCut1(){}
    @Before(value = "myPointCut()")
    private int start(JoinPoint joinPoint){
        //获取方法签名
        Signature signature = joinPoint.getSignature();
        //获取参数信息
        Object[] args = joinPoint.getArgs();
        System.out.println("log---"+signature.getName()+"方法开始执行:参数是"+Arrays.asList(args));
        return 100;
    }
//    @AfterReturning(value = "myPointCut()",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        Signature signature = joinPoint.getSignature();
        System.out.println("log---"+signature.getName()+"方法执行结束,结果是:"+result);
    }
//    @AfterThrowing(value = "myPointCut()",throwing = "e")
    public static void logException(JoinPoint joinPoint,Exception e){
        Signature signature = joinPoint.getSignature();
        System.out.println("log---"+signature.getName()+"方法抛出异常:"+e.getMessage());
    }
//    @After("myPointCut()")
    public static void logFinally(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        System.out.println("log---"+signature.getName()+"方法执行结束。。。。。over");
    }
//     @Around("myPointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature();
        Object[] args = pjp.getArgs();
        Object result = null;
        try {
            System.out.println("log---环绕通知start:"+signature.getName()+"方法开始执行,参数为:"+Arrays.asList(args));
            //通过反射的方式调用目标的方法,相当于执行method.invoke(),可以自己修改结果值
            result = pjp.proceed(args);
//            result=100;
            System.out.println("log---环绕通知stop"+signature.getName()+"方法执行结束");
        } catch (Throwable throwable) {
            System.out.println("log---环绕异常通知:"+signature.getName()+"出现异常");
            throw throwable;
        }finally {
            System.out.println("log---环绕返回通知:"+signature.getName()+"方法返回结果是:"+result);
        }
        return result;
    }
}

测试基础类

public class TestAop {
    public static void main(String[] args) throws Exception {
        saveGeneratedCGlibProxyFiles(System.getProperty("user.dir")+"/proxy");
        ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
        MyCalculator bean = ac.getBean(MyCalculator.class);
        System.out.println(bean);
        bean.add(1,1);
        bean.sub(1,1);
    }
    public static void saveGeneratedCGlibProxyFiles(String dir) throws Exception {
        Field field = System.class.getDeclaredField("props");
        field.setAccessible(true);
        Properties props = (Properties) field.get(null);
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, dir);//dir为保存文件路径
        props.put("net.sf.cglib.core.DebuggingClassWriter.traceEnabled", "true");
    }
}

FAQ

当使用 AOP 时,需要进行 N 多个对象的创建,但是在创建过程中需要做很多判断,判断当前对象是否需要被代理,而代理之前,需要的 advisor 对象必须提前准备好,才能进行后续的判断

若定义了一个普通的对象,会进入 resolveBeforeInstantation 方法的处理吗?

不会,普通对象会在执行初始化方法时,调用 BeanPostProcessor#after 方法进行处理;判断是否需要被代理,需要才返回代理对象,否则仍然是普通对象

目录
相关文章
|
15天前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
42 1
|
21天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
2月前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
41 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
26天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
30 1
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
31 1
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
28 1
|
22天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
31 0
|
29天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
22 0
|
3月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
187 2