资讯专栏INFORMATION COLUMN

Spring AOP 源码初窥(二) 从注解开始

Amio / 2069人阅读

摘要:版本从注解开始由于在本人实际应用中使用的是注解配置,也更倾向于了解的整个实现,而不仅仅是关键实现。于是本篇源码解析,将会从注解开始。那么,便从的源码引用开始吧。的引用先从源码中找有引用到,用来判断是否有该注解的代码。

版本

spring 5.0.8.BUILD-SNAPSHOT

aspectjweaver 1.8.13

从注解开始

由于在本人实际应用中使用的是注解配置AOP,也更倾向于了解Spring AOP的整个实现,而不仅仅是关键实现。于是本篇源码解析,将会从注解开始。了解Spring AOP是怎么扫描Aspect配置,匹配,并生成AOP代理的。

注解@Aspect定了一个类为AOP的配置。那么,便从@Aspect的源码引用开始吧。

@Aspect的引用

先从源码中找有引用到@Aspect,用来判断Class是否有该注解的代码。找到方法。

/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java
...
@Override
public boolean isAspect(Class clazz) {
    return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}

private boolean hasAspectAnnotation(Class clazz) {
    return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}

/**
 * We need to detect this as "code-style" AspectJ aspects should not be
 * interpreted by Spring AOP.
 */
private boolean compiledByAjc(Class clazz) {
    // The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
    // annotation-style aspects. Therefore there is no "clean" way to tell them apart. Here we rely on
    // an implementation detail of the AspectJ compiler.
    for (Field field : clazz.getDeclaredFields()) {
        if (field.getName().startsWith(AJC_MAGIC)) {
            return true;
        }
    }
    return false;
}
...

isAspect(Class clazz)用来判断clazz对象是否包含Aspect.class注解并且未被AspectJ编译过,其中hasAspectAnnotation(Class clazz)内容很明显就不多提。倒是compiledByAjc(Class clazz)的实现比较特别。它的实现是用字段前缀来判断是否为"code-style" aspects,看起来是一种比较Hack的方法。

这里我有一个疑惑点,就是"code-style" aspects和"annotation-style" aspects的具体所指,查了一圈也没有看到明确的解释。只在IDEA的帮助文档Overview of AspectJ support这段上有看到相关的解释。我的理解是"code-style"是由AspectJ Language所定义的aspect,会由AspectJ来编译,而"annotation-style"则是由使用了@Aspect注解的Java语言所定义的aspect,如有错误烦请指出。
入口在Bean的生命周期中

通过一步步阅读和调试,可以一层一层向上找到org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java中有两个入口。

postProcessBeforeInstantiation和postProcessAfterInitialization,是Bean生命周期中的两个步骤:

postProcessBeforeInstantiation: 在Bean实例化之前执行

postProcessAfterInitialization: 在Bean初始化之后执行

postProcessBeforeInstantiation:
/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
...
@Override
public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException {
    Object cacheKey = getCacheKey(beanClass, beanName);

    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

    // Create proxy here if we have a custom TargetSource.
    // Suppresses unnecessary default instantiation of the target bean:
    // The TargetSource will handle target instances in a custom fashion.
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            this.targetSourcedBeans.add(beanName);
        }
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
        Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    return null;
}
...

这个方法的目的是将含有custom TargetSource的bean进行增强处理。可分为两部份,前半部分利用缓存和几个方法判断是否需要增强。后半部分则进入主题判断是否含有custom TargetSource。不过这里我对custom TargetSource不是特别理解,也没有细看,因为通过@Aspect注解配置不会执行这里面的代码,留着以后有时间再看。
这里还有另外两个方法:

isInfrastructureClass(是否是基础类,如Advice、Pointcut、Advisor、AopInfrastructureBean 及其超类)

shouldSkip(主要目的是判断是否是已注册的@Aspect配置Bean,其实扫描@Aspect注解配置的方法就在这里面被调用到了,这个后面再说)

postProcessAfterInitialization
/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
....
/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

/**
 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
 * @param bean the raw bean instance
 * @param beanName the name of the bean
 * @param cacheKey the cache key for metadata access
 * @return a proxy wrapping the bean, or the raw bean instance as-is
 */
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

可以看到关键内容就在wrapIfNecessary里面。顾名思义:必要时转成AOP代理。前半部分判断是否是不需要增强的,跟postProcessBeforeInstantiation的前半部分有点类似。后半部分根据是否有合适的Advice方法,有则将Bean转成代理。
好了,这里其实就是整个流程最关键的两个地方了:

getAdvicesAndAdvisorsForBean(获取适合该Bean的Advice方法,里面包含了扫描@Aspect注解配置Bean的方法)

createProxy(创建AOP代理,里面包含了AOP代理的实现)

这两个方法的具体内容,将在接下来的文章介绍

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76764.html

相关文章

  • Spring AOP 源码初窥(一) 概念

    摘要:而面向切面编程理所当然关注于切面,那么什么是切面可以理解为程序执行时的某个节点,或更具体一点,在某个方法执行之前,执行之后,返回之后等其它节点。术语一个切面,可以理解为一个切面模块,将相关的增强内容写进同一个切面。例如一个负责日志的切面。 AOP是什么 AOP全称 Aspect-Oriented Programming 即面向切面编程。怎么样,是不是感觉很熟悉?对,类似的还有面向过程编...

    CarterLi 评论0 收藏0
  • Spring AOP 源码初窥(三)扫描Advice与Bean匹配

    摘要:版本如何扫描接上一回,讲到了方法,该方法的目的是获取并生成。其中英文为源码注释。那么,以上便是通过扫描配置并生成的过程了。一些总结读到这儿,如何扫描配置,生成类,并匹配对应的整个流程已经很清楚了。 版本 spring 5.0.8.BUILD-SNAPSHOT aspectjweaver 1.8.13 如何扫描Advice 接上一回,讲到了getAdvicesAndAdvisorsF...

    oysun 评论0 收藏0
  • 【好好面试】手把手调试,教你分析Spring-Aop

    摘要:思考之所以会选择为切入点,是因为通过命名可以看出这是用来构建代理强化对象的地方,并且由于是先将目标类加载到内存中,之后通过修改字节码生成目标类的子类,因此我猜测强化是在目标类实例化后触发的时候进行的。 【干货点】 此处是【好好面试】系列文的第11篇文章。看完该篇文章,你就可以了解Spring中Aop的相关使用和原理,并且能够轻松解答Aop相关的面试问题。更重要的是,很多人其实一看源码就...

    aervon 评论0 收藏0
  • Aop?看这篇文章就够了!!!

    摘要:又是什么其实就是一种实现动态代理的技术,利用了开源包,先将代理对象类的文件加载进来,之后通过修改其字节码并且生成子类。 在实际研发中,Spring是我们经常会使用的框架,毕竟它们太火了,也因此Spring相关的知识点也是面试必问点,今天我们就大话Aop。特地在周末推文,因为该篇文章阅读起来还是比较轻松诙谐的,当然了,更主要的是周末的我也在充电学习,希望有追求的朋友们也尽量不要放过周末时...

    boredream 评论0 收藏0
  • 彻底征服 Spring AOP 之 实战篇

    摘要:接上一小节彻底征服之理论篇实战看了上面这么多的理论知识不知道大家有没有觉得枯燥哈不过不要急俗话说理论是实践的基础对有了基本的理论认识后我们来看一下下面几个具体的例子吧下面的几个例子是我在工作中所遇见的比较常用的的使用场景我精简了很多有干扰我 接上一小节彻底征服 Spring AOP 之 理论篇 Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不...

    CNZPH 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<