Spring5源码(34)-SpringAop获取增强(二)

简介: Spring5源码(34)-SpringAop获取增强(二)


在上一篇结尾,我们得到了增强的提取工作交给了List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);。接着分析。

1. getAdvisors获取增强简析

@Override
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    // 1、预处理工作,包括获取切面类,名称,验证等
    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    validate(aspectClass);
    // 我们需要用一个装饰器包装MetadataAwareAspectInstanceFactory,这样它只会实例化一次。
    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
            new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
    // 2、提取增强
    // 2.1、获取切面类的所有方法,循环判断提取合适的切入点,并创建增强
    List<Advisor> advisors = new ArrayList<>();
    for (Method method : getAdvisorMethods(aspectClass)) {
        // 获取增强
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }
    // If it's a per target aspect, emit the dummy instantiating aspect.
    // 2.2、处理perthis和pertarget切面类
    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        // 创建SyntheticInstantiationAdvisor实例
        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        // 将SyntheticInstantiationAdvisor实例加入到advisors集合首位,注意:不是替换
        advisors.add(0, instantiationAdvisor);
    }
    // Find introduction fields.
    // 2.3、处理引入,获取所有的引入并循环创建DeclareParentsAdvisor
    for (Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }
    return advisors;
}

在该方法中提取工作一共分为了三步:提取普通增强、处理处理perthis和pertarget、引介增强。篇幅有限,只分析普通增强的处理过程,该过程也是大家最关心的过程。来看代码:

@Override
@Nullable
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
        int declarationOrderInAspect, String aspectName) {
    // 1、验证
    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
    // 2、提取切入点
    AspectJExpressionPointcut expressionPointcut = getPointcut(
            candidateAdviceMethod,
            aspectInstanceFactory.getAspectMetadata().getAspectClass());
    if (expressionPointcut == null) {
        return null;
    }
    // 3、创建增强
    return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
            this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

在该方法中,终于看到了核心的切点提取和创建增强。下面分别来看这两步是如何实现的。

2.提取切入点

@Nullable
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    // 1、从候选切入点上找出增强表达式
    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }
    // 2、创建AspectJExpressionPointcut对象
    AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
    if (this.beanFactory != null) {
        ajexp.setBeanFactory(this.beanFactory);
    }
    return ajexp;
}
2.1 从候选切入点上找出增强表达式

/**
 * Find and return the first AspectJ annotation on the given method
 * (there <i>should</i> only be one anyway...).
 */
@SuppressWarnings("unchecked")
@Nullable
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    Class<?>[] classesToLookFor = new Class<?>[] {
            Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
    for (Class<?> c : classesToLookFor) {
        AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }
    return null;
}

该过程主要通过findAnnotation方法完成,但是该方法调用较深,也不属于我们分析的范畴,感兴趣的同学可以自己跟踪调试。

2.2 创建AspectJExpressionPointcut对象

/**
 * Create a new AspectJExpressionPointcut with the given settings.
 * @param declarationScope the declaration scope for the pointcut
 * @param paramNames the parameter names for the pointcut
 * @param paramTypes the parameter types for the pointcut
 */
public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
    this.pointcutDeclarationScope = declarationScope;
    if (paramNames.length != paramTypes.length) {
        throw new IllegalStateException(
                "Number of pointcut parameter names must match number of pointcut parameter types");
    }
    this.pointcutParameterNames = paramNames;
    this.pointcutParameterTypes = paramTypes;
}

该创建过程比较简单,将提取的切点表达式的信息实例化为AspectJExpressionPointcut对象即可。

3.创建增强

public InstantiationModelAwarePointcutAdvisorImpl(
            AspectJExpressionPointcut declaredPointcut,
            Method aspectJAdviceMethod,
            AspectJAdvisorFactory aspectJAdvisorFactory,
            MetadataAwareAspectInstanceFactory aspectInstanceFactory,
            int declarationOrder,
            String aspectName) {
    this.declaredPointcut = declaredPointcut;
    this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
    this.methodName = aspectJAdviceMethod.getName();
    this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
    this.aspectJAdviceMethod = aspectJAdviceMethod;
    this.aspectJAdvisorFactory = aspectJAdvisorFactory;
    this.aspectInstanceFactory = aspectInstanceFactory;
    this.declarationOrder = declarationOrder;
    this.aspectName = aspectName;
    // 1、延迟初始化
    // 在Spring AOP中,切面类的实例只有一个,比如前面我们一直使用的MyAspect类,
    // 假设我们使用的切面类需要具有某种状态,以适用某些特殊情况的使用,比如多线程环境,此时单例的切面类就不符合我们的要求了。
    // 在Spring AOP中,切面类默认都是单例的,但其还支持另外两种多例的切面实例的切面,即perthis和pertarget,
    // 需要注意的是perthis和pertarget都是使用在切面类的@Aspect注解中的。
    // 这里perthis和pertarget表达式中都是指定一个切面表达式,其语义与前面讲解的this和target非常的相似,
    // perthis表示如果某个类的代理类符合其指定的切面表达式,那么就会为每个符合条件的目标类都声明一个切面实例;
    // pertarget表示如果某个目标类符合其指定的切面表达式,那么就会为每个符合条件的类声明一个切面实例。
    // 从上面的语义可以看出,perthis和pertarget的含义是非常相似的。如下是perthis和pertarget的使用语法:
    // perthis(pointcut-expression)
    // pertarget(pointcut-expression)
    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        // Static part of the pointcut is a lazy type.
        Pointcut preInstantiationPointcut = Pointcuts.union(
                aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(),
                this.declaredPointcut);
        // Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
        // If it's not a dynamic pointcut, it may be optimized out
        // by the Spring AOP infrastructure after the first evaluation.
        this.pointcut = new PerTargetInstantiationModelPointcut(
                this.declaredPointcut,
                preInstantiationPointcut,
                aspectInstanceFactory);
        this.lazy = true;
    }
    // 2、立刻初始化
    else {
        // A singleton aspect.
        this.pointcut = this.declaredPointcut;
        this.lazy = false;
        // 初始化增强
        this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
    }
}

创建过程中又涉及到perthis和pertarget,无需理会,还是先看单例模式下的创建过程:

/**
 * 根据pointcut初始化增强
 * @param pointcut
 * @return
 */
private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
    Advice advice = this.aspectJAdvisorFactory.getAdvice(
            this.aspectJAdviceMethod, pointcut,
            this.aspectInstanceFactory,
            this.declarationOrder,
            this.aspectName);
    return (advice != null ? advice : EMPTY_ADVICE);
}

@Override
@Nullable
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    // 1、获取增强之前的处理
    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    validate(candidateAspectClass);
    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }
    // If we get here, we know we have an AspectJ method.
    // Check that it's an AspectJ-annotated class
    if (!isAspect(candidateAspectClass)) {
        throw new AopConfigException("Advice must be declared inside an aspect type: " +
                "Offending method '" + candidateAdviceMethod + "' in class [" +
                candidateAspectClass.getName() + "]");
    }
    // 2、针对各种不同的增强,做不同的处理
    AbstractAspectJAdvice springAdvice;
    switch (aspectJAnnotation.getAnnotationType()) {
            // 1、前置增强
        case AtBefore:
            springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
            // 2、后置增强
        case AtAfter:
            springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
            // 3、后置返回增强
        case AtAfterReturning:
            springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            }
            break;
            // 4、后置异常增强
        case AtAfterThrowing:
            springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
            }
            break;
            // 5、环绕增强
        case AtAround:
            springAdvice = new AspectJAroundAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
            // 6、如果是Pointcut,则不做处理
        case AtPointcut:
            return null;
            // 7、未能满足case条件,抛出异常
        default:
            throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);
    }
    // 3、获取增强方法之后,对增强方法进行配置
    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrder);
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) {
        springAdvice.setArgumentNamesFromStringArray(argNames);
    }
    springAdvice.calculateArgumentBindings();
    return springAdvice;
}

看到这里,是不是有种豁然开朗的感觉,Spring针对各种不同的增强创建过程,做了不同的处理。到这里Spring创建增强的过程就完成了,对于不同的增强类型创建,大家可以自己debug跟踪,这里不一一赘述了。

4.关于Advisor的疑问

这两篇在介绍获取增强,但是最受获取到的并不是Advice而是Advisor,可能大家会有所疑问。举例说明一下:

如果我们定义了一个DogAspect类,并用@AspectJ对其进行注解,那么该类仅仅代表一个切面类,会被Spring扫描并解析,仅此而已,该类不代表SpringAop概念中的切面。那么Spring如果通过解析该类得到具体的切面呢?

首先,关于SpringAop中的切面概念,可以理解为 切面=连接点+增强

其次,而标记了@AspectJ注解的类在被Spring解析的时候,

  1. 提取该类的方法上的切点表达式注解:例如-->@Pointcut("execution(* com.lyc.cn.v2.day07..
    (..))"),解析之后,就可以的到具体的切点.
  2. 提取该类的方法上的增强注解:例如:@Before("test()")解析之后,就可以得到具体的增强代码

最后,通过第一步和第二步的操作,就可以得到切点+增强,那么自然就构成了一个切面

但是Advisor接口里只包含了一个Advice,并且Advisor一般不直接提供给用户使用,所以这里也可以理解为获取增强,当然如果理解为切面也是没有问题的。





目录
相关文章
|
8天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
41 0
|
8天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
8天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
26 6
|
8天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
34 3
|
1天前
|
小程序 JavaScript Java
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
7 0
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
|
1天前
|
小程序 JavaScript Java
高校宿舍信息|基于Spring Boot的高校宿舍信息管理系统的设计与实现(源码+数据库+文档)
高校宿舍信息|基于Spring Boot的高校宿舍信息管理系统的设计与实现(源码+数据库+文档)
7 0
|
1天前
|
安全 JavaScript Java
在线问卷调查|基于Spring Boot的在线问卷调查系统的设计与实现(源码+数据库+文档)
在线问卷调查|基于Spring Boot的在线问卷调查系统的设计与实现(源码+数据库+文档)
6 0
|
8天前
|
XML 监控 Java
Spring基础 SpringAOP
Spring基础 SpringAOP
15 0
|
8天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
18 1
|
8天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
109 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式