Spring源码分析之AOP从解析到调用(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Spring源码分析之AOP从解析到调用

在上一篇,我们对IOC核心部分流程已经分析完毕,相信小伙伴们有所收获,从这一篇开始,我们将会踏上新的旅程,即Spring的另一核心:AOP!

首先,为了让大家能更有效的理解AOP,先带大家过一下AOP中的术语:

  • 切面(Aspect):指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。在Spring AOP中,切面可以使用在普通类中以@Aspect注解来实现。
  • 连接点(Join point):在Spring AOP中,一个连接点总是代表一个方法的执行,其实就代表增强的方法。
  • 通知(Advice):在切面的某个特定的连接点上执行的动作。通知有多种类型,包括aroundbeforeafter等等。许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
  • 目标对象(Target):目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。
  • 切点(Pointcut):匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
  • 顾问(Advisor): 顾问是Advice的一种包装体现,Advisor是Pointcut以及Advice的一个结合,用来管理Advice和Pointcut。
  • 织入(Weaving):将通知切入连接点的过程叫织入
  • 引入(Introductions):可以将其他接口和实现动态引入到targetClass中

一个栗子

术语看完了,我们先上个Demo回顾一下吧~

  1. 首先,使用EnableAspectJAutoProxy注解开启我们的AOP
@ComponentScan(basePackages = {"com.my.spring.test.aop"})
@Configuration
@EnableAspectJAutoProxy
public class Main {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
    IService service = context.getBean("service", IService.class);
    service.doService();
  }
}
  1. 写一个接口
public interface IService {
  void doService();
}
  1. 写一个实现类
@Service("service")
public class ServiceImpl implements IService{
  @Override
  public void doService() {
    System.out.println("do service ...");
  }
}
  1. 写一个切面
@Aspect
@Component
public class ServiceAspect {
  @Pointcut(value = "execution(* com.my.spring.test.aop.*.*(..))")
  public void pointCut() {
  }
  @Before(value = "pointCut()")
  public void methodBefore(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法 【" + methodName + "】 的【前置通知】,入参:" + Arrays.toString(joinPoint.getArgs()));
  }
  @After(value = "pointCut()")
  public void methodAfter(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法 【" + methodName + "】 的【后置通知】,入参:" + Arrays.toString(joinPoint.getArgs()));
  }
  @AfterReturning(value = "pointCut()")
  public void methodReturn(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法 【" + methodName + "】 的【返回通知】,入参:" + Arrays.toString(joinPoint.getArgs()));
  }
  @AfterThrowing(value = "pointCut()")
  public void methodThrow(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法 【" + methodName + "】 的【异常通知】,入参:" + Arrays.toString(joinPoint.getArgs()));
  }
}
  1. 测试运行
执行目标方法 【doService】 的【前置通知】,入参:[]
do service ...
执行目标方法 【doService】 的【返回通知】,入参:[]
执行目标方法 【doService】 的【后置通知】,入参:[]

以上

Demo看完了,运行效果也出来了,AOP已生效,但如何生效的呢?相比于我们普通使用Bean的Demo,在这里,我们只不过加上了一个@EnableAspectJAutoProxy注解以及一个标识了@Aspectj的类,那么我们先看看@EnableAspectJAutoProxy这个注解做了什么吧~

开启AOP

以下是笔者所画的大致流程图

EnabelAspectJ.png

其中AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,所以在处理BeanFactoryPostProcessor逻辑时将会调用registerBeanDefinitions方法,此时就会把AnnotationAwareAspectJAutoProxyCreator注册到容器中,其中BeanFactoryPostProcessor的逻辑就不再说了,往期文章有过详细分析。而AnnotationAwareAspectJAutoProxyCreator的类图如下:

AnnotationAwareAspectJ.png

我们发现AnnotationAwareAspectJAutoProxyCreator是实现了BeanPostProcessor接口的类,所以它其实是一个后置处理器,那么,还记得在创建Bean过程中的BeanPostProcessor九次调用时机吗?不记得也没关系,AnnotationAwareAspectJAutoProxyCreator起作用的地方是在bean的实例化前以及初始化后,分别对应着解析切面和创建动态代理的过程,现在,就让我们先来看看解析切面的过程吧~

解析切面

解析切面的流程如下图所示:

切面解析过程.png

我们已经了解到切面解析的过程是由AnnotationAwareAspectJAutoProxyCreator完成的,而AnnotationAwareAspectJAutoProxyCreator又继承了AbstractAutoProxyCreator,所以首先,我们先会来到AbstractAutoProxyCreator#postProcessBeforeInstantiation

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
  // class类型是否为(Advice, Pointcut, Advisor, AopInfrastructureBean)
  // shouldSkip中将会解析切面
  if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return null;
  }
}

调用到子类的AspectJAwareAdvisorAutoProxyCreator#shouldSkip

@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
  // 寻找advisor
  List<Advisor> candidateAdvisors = findCandidateAdvisors();
  for (Advisor advisor : candidateAdvisors) {
    if (advisor instanceof AspectJPointcutAdvisor &&
        ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
      return true;
    }
  }
  return super.shouldSkip(beanClass, beanName);
}

findCandidateAdvisors

protected List<Advisor> findCandidateAdvisors() {
  // 寻找实现了Advisor接口的类, 由于我们一般不会以接口的方式实现切面,这里返回null
  List<Advisor> advisors = super.findCandidateAdvisors();
  if (this.aspectJAdvisorsBuilder != null) {
    // 这里将解析出所有的切面
    advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
  }
  return advisors;
}

buildAspectJAdvisors

public List<Advisor> buildAspectJAdvisors() {
  // aspectBeanNames有值则说明切面已解析完毕
  List<String> aspectNames = this.aspectBeanNames;
  // Double Check
  if (aspectNames == null) {
    synchronized (this) {
      aspectNames = this.aspectBeanNames;
      if (aspectNames == null) {
        List<Advisor> advisors = new ArrayList<>();
        aspectNames = new ArrayList<>();
        // 取出是Object子类的bean,其实就是所有的bean
        String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
          this.beanFactory, Object.class, true, false);
        for (String beanName : beanNames) {
          // 获得该bean的class
          Class<?> beanType = this.beanFactory.getType(beanName);
          // 判断是否有标识@AspectJ注解
          if (this.advisorFactory.isAspect(beanType)) {
            // 将beanName放入集合中
            aspectNames.add(beanName);
            // 将beanType和beanName封装到AspectMetadata中
            AspectMetadata amd = new AspectMetadata(beanType, beanName);
            // Kind默认为SINGLETON
            if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
              MetadataAwareAspectInstanceFactory factory =
                new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
              // 这里会通过@Before @After等标识的方法获取到所有的advisor
              List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
              if (this.beanFactory.isSingleton(beanName)) {
                // 将获取到的所有advisor放入缓存
                this.advisorsCache.put(beanName, classAdvisors);
              }
              advisors.addAll(classAdvisors);
            }
          }
        }
        // 将所有解析过的beanName赋值
        this.aspectBeanNames = aspectNames;
        return advisors;
      }
    }
  }
  // aspectNames不为空,意味有advisor,取出之前解析好的所有advisor
  List<Advisor> advisors = new ArrayList<>();
  // 获取到所有解析好的advisor
  for (String aspectName : aspectNames) {
    List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
    if (cachedAdvisors != null) {
      advisors.addAll(cachedAdvisors);
    }
    return advisors;
  }

advisorFactory.getAdvisors

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
  // 获取到标识了@AspectJ的class,其实就是刚刚封装的class
  Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
  // 获取className
  String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
  List<Advisor> advisors = new ArrayList<>();
  // 拿出该类除了标识@PointCut的所有方法进行遍历 getAdvisorMethods时会对method进行一次排序
  // 排序顺序 Around, Before, After, AfterReturning, AfterThrowing
  for (Method method : getAdvisorMethods(aspectClass)) {
    // 获取到advisor
    Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
    if (advisor != null) {
      // 加入到集合中
      advisors.add(advisor);
    }
  }
}

我们先看下getAdvisorMethods方法

private List<Method> getAdvisorMethods(Class<?> aspectClass) {
  final List<Method> methods = new ArrayList<>();
  // 循环遍历该类和父类的所有方法
  ReflectionUtils.doWithMethods(aspectClass, method -> {
    // 排除@PointCut标识的方法
    if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
      methods.add(method);
    }
  }, ReflectionUtils.USER_DECLARED_METHODS);
  if (methods.size() > 1) {
    // 以Around, Before, After, AfterReturning, AfterThrowing的顺序自定义排序
    methods.sort(METHOD_COMPARATOR);
  }
  return methods;
}

不知道小伙伴们对ReflectionUtils.doWithMethods这个工具类熟不熟悉呢,这个工具类在之前分析Bean创建过程时可是出现了好多次呢,并且我们也是可以使用的

现在,已经获取到切面中的所有方法了,那么接下来就该对这些方法解析并进行封装成advisor了~

getAdvisor

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
      int declarationOrderInAspect, String aspectName) {
  // 获取方法上的切点表达式
  AspectJExpressionPointcut expressionPointcut = getPointcut(
    candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
  // 封装成对象返回,创建对象时将会解析方法创建advice
  return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
                                                        this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

获取切点表达式的过程其实非常简单,即是解析方法上的注解,取出注解上的value即可

getPointcut

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
  // 查找方法上和AspectJ相关注解
  AspectJAnnotation<?> aspectJAnnotation =
    AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
  // 设置切点表达式
  AspectJExpressionPointcut ajexp =
    new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
  // PointcutExpression 为注解上value属性的值
  ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
  if (this.beanFactory != null) {
    ajexp.setBeanFactory(this.beanFactory);
  }
  return ajexp;
}

new InstantiationModelAwarePointcutAdvisorImpl,在这里,才会真正创建出advice

public InstantiationModelAwarePointcutAdvisorImpl(){
  //...省略赋值过程...
  // 实例化出advice
  this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
}
private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
  // 获取advice,aspectJAdviceMethod为方法,aspectName为切面类
  Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,
                                                       this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
  return (advice != null ? advice : EMPTY_ADVICE);
}
public Advice getAdvice(){
  // 根据方法获取到注解信息
  AspectJAnnotation<?> aspectJAnnotation =
        AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
  AbstractAspectJAdvice springAdvice;
  // 根据注解类型返回对象,创建对象的过程都是一样的,都是调用父类的构造方法
  // candidateAdviceMethod为切面的方法,expressionPointcut是切点
  switch (aspectJAnnotation.getAnnotationType()) {
    case AtPointcut
      return null;
    case AtAround:
      springAdvice = new AspectJAroundAdvice(
        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
      break;
    case AtBefore:
      springAdvice = new AspectJMethodBeforeAdvice(
        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
      break;
    case AtAfter:
      springAdvice = new AspectJAfterAdvice(
        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
      break;
      //...省略其他的advice
    default:
      throw new UnsupportedOperationException(
        "Unsupported advice type on method: " + candidateAdviceMethod);
  }
  return springAdvice;
}

springAdvice已创建完毕,意味着切面中的某个方法已经解析完毕了,其他的方法解析过程大致也是相似的

目录
相关文章
|
2月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
121 6
|
1月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
111 25
|
1月前
|
XML 安全 Java
Spring AOP—深入动态代理 万字详解(通俗易懂)
Spring 第四节 AOP——动态代理 万字详解!
86 24
|
2月前
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
86 18
|
2月前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
53 6
|
27天前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
|
3月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
2月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
94 8
|
14天前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
61 12
|
17天前
|
Java 应用服务中间件 Maven
SpringBoot项目打包成war包
通过上述步骤,我们成功地将一个Spring Boot应用打包成WAR文件,并部署到外部的Tomcat服务器中。这种方式适用于需要与传统Servlet容器集成的场景。
36 8

推荐镜像

更多