Spring AOP 源碼初窺(二) 從註解開始

版本

  • spring 5.0.8.BUILD-SNAPSHOT
  • aspectjweaver 1.8.13

從註解開始

因爲在本人實際應用中使用的是註解配置AOP,也更傾向於瞭解Spring AOP的整個實現,而不單單是關鍵實現。因而本篇源碼解析,將會從註解開始。瞭解Spring AOP是怎麼掃描Aspect配置,匹配,並生成AOP代理的。html

註解@Aspect定了一個類爲AOP的配置。那麼,便從@Aspect的源碼引用開始吧。java

@Aspect的引用

先從源碼中找有引用到@Aspect,用來判斷Class是否有該註解的代碼。找到方法。spring

/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中有兩個入口。app

postProcessBeforeInstantiation和postProcessAfterInitialization,是Bean生命週期中的兩個步驟:ide

  • 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註解配置不會執行這裏面的代碼,留着之後有時間再看。
這裏還有另外兩個方法:post

  • 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轉成代理。
好了,這裏其實就是整個流程最關鍵的兩個地方了:this

  • getAdvicesAndAdvisorsForBean(獲取適合該Bean的Advice方法,裏面包含了掃描@Aspect註解配置Bean的方法)
  • createProxy(建立AOP代理,裏面包含了AOP代理的實現)

這兩個方法的具體內容,將在接下來的文章介紹idea

相關文章
相關標籤/搜索