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一般不直接提供给用户使用,所以这里也可以理解为获取增强,当然如果理解为切面也是没有问题的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,778评论 6 342
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 12,285评论 6 86
  • 01: 五天,放在一个星期里面好长,放在一年里又好短。我们两年前从财经毕业,五天前才第一次见面,每天在群里聊得热火...
    陈哇噻aa阅读 554评论 2 7
  • 还有一种情况是,家长不是听从医生的话,看孩子是否需要化验,而是一到医院就要求给孩子化验,不管孩子是什么问题。 按照...
    杨金社阅读 211评论 1 0