Spring-SpringAOP原始碼學習(上)

半夏之沫 發表於 2022-06-10
Spring

前言

本篇文章是SpringAOP的原始碼學習分享,分為上下兩篇,本篇是對SpringAOP中切面織入業務bean時為業務bean生成動態代理物件的這一塊的原始碼學習。

正文

一. 示例工程搭建

通過引入Springboot來完成引入Spring的相關依賴,依賴項如下所示。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

使用的Springboot版本為2.4.1,對應的Spring版本為5.3.2

首先自定義一個註解,用於在切點中定位到目標方法,如下所示。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyMethod {
}

然後定義業務介面和實現類,如下所示。

public interface IMyService {

    void executeTask(String message);

    void tempExecute(String message);

}

@Service
public class MyService implements IMyService {

    @Override
    @MyMethod
    public void executeTask(String message) {
        System.out.println(message);
    }

    @Override
    public void tempExecute(String message) {
        executeTask(message);
    }

}

然後定義一個切面,已知切面 = 切點 + 通知,在本示例工程中,切點是所有由@MyMethod註解修飾的方法,並且選擇前置通知和後置通知,如下所示。

@Aspect
@Component
public class MyAspect {

    @Pointcut("@annotation(com.learn.aop.aspect.MyMethod)")
    public void myMethodPointcut() {}

    @Before(value = "myMethodPointcut()")
    public void commonBeforeMethod(JoinPoint joinPoint) {
        System.out.println("Common before method.");
    }

    @After(value = "myMethodPointcut()")
    public void commonAfterMethod(JoinPoint joinPoint) {
        System.out.println("Common after method.");
    }

}

自定義一個配置類,和上面所有類放在同一包路徑下,如下所示。

@ComponentScan
@EnableAspectJAutoProxy
public class MyConfig {}

最後編寫一個測試程式,如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);

        IMyService iMyService = applicationContext.getBean(IMyService.class);

        iMyService.tempExecute("Real method execute.");
        iMyService.executeTask("Real method execute.");
    }

}

執行測試程式,列印如下。

Spring-SpringAOP原始碼學習(上)

可以看到執行executeTask()方法時,切面邏輯生效了。

二. 時序圖

SpringAOP中動態代理物件的生成,可以分為兩個大的步驟。

  • 步驟一:將作用於當前bean的通知獲取出來,得到通知鏈
  • 步驟二:基於通知鏈為當前bean生成AOP動態代理物件,並根據配置和目標bean決定是使用CGLIB動態代理還是JDK動態代理。

通知鏈可以表示如下。

List<Advisor> chain

Advisor介面是SpringAOP中對通知的一個頂層抽象,其有兩個子介面,類圖如下所示。

Spring-SpringAOP原始碼學習(上)

PointcutAdvisor是對切點相關的通知的抽象,可以將PointcutAdvisor理解為對通知方法切點的封裝,由於本文的示例工程中的切面中的通知全部是切點相關的通知,所以無特殊說明時,Advisor均指PointcutAdvisor,並且也可以不太嚴謹的將Advisor稱為通知。

在理清了概念之後,下面給出時序圖,時序圖有兩張,一張是通知鏈的獲取時序圖,一張是AOP動態代理物件的生成時序圖,如下所示。

  • 通知鏈的獲取時序圖。

Spring-SpringAOP原始碼學習(上)

  • AOP動態代理物件的生成時序圖。

Spring-SpringAOP原始碼學習(上)

後續原始碼的分析可以結合上述時序圖進行理解。

三. SpringAOP動態代理物件生成時間點

在示例工程中,如果通過斷點除錯的方法,觀察iMyService欄位,可以發現其是一個動態代理物件,如下所示。

Spring-SpringAOP原始碼學習(上)

bean生命週期中,bean的例項化,屬性注入和初始化都在AbstractAutowireCapableBeanFactorydoCreateBean()方法中,在該方法中會先呼叫createBeanInstance()方法將bean例項化出來,然後呼叫populateBean()方法為bean例項完成屬性注入,最後呼叫initializeBean()方法來初始化bean。下面看一下initializeBean()方法的實現。

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    
    ......
    
    if (mbd == null || !mbd.isSynthetic()) {
        //呼叫BeanPostProcessors的postProcessBeforeInitialization()方法
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    ......
    
    if (mbd == null || !mbd.isSynthetic()) {
        //呼叫BeanPostProcessors的postProcessAfterInitialization()方法
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

在初始化bean的時候,就會呼叫到BeanPostProcessorsbean後置處理器)的postProcessBeforeInitialization()postProcessAfterInitialization()方法,在BeanPostProcessors的實現類AnnotationAwareAspectJAutoProxyCreatorpostProcessAfterInitialization()方法中,就會為bean織入切面(為bean生成動態代理物件)。

下面給出AnnotationAwareAspectJAutoProxyCreator的類圖。

Spring-SpringAOP原始碼學習(上)

四. 通知鏈的獲取

上一節中已知,在BeanPostProcessors的實現類AnnotationAwareAspectJAutoProxyCreatorpostProcessAfterInitialization()方法中,就會為bean織入切面(為bean生成動態代理物件),那麼就以AnnotationAwareAspectJAutoProxyCreatorpostProcessAfterInitialization()方法為入口,開始分析原始碼。其實AnnotationAwareAspectJAutoProxyCreator沒有對postProcessAfterInitialization()方法做實現,那麼實際呼叫到的是AnnotationAwareAspectJAutoProxyCreator父類AbstractAutoProxyCreatorpostProcessAfterInitialization()方法,如下所示。

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            //如果bean是切面作用目標,就為bean生成動態代理物件
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

繼續看wrapIfNecessary()方法的實現,如下所示。

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;
    }

    //把作用在當前bean的通知獲取出來
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //為當前bean生成動態代理物件
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        //返回當前bean的動態代理物件
        return proxy;
    }

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

wrapIfNecessary()方法會先將作用於當前bean的通知鏈獲取出來,然後再呼叫createProxy()方法為當前bean建立動態代理物件,那麼本小節重點分析getAdvicesAndAdvisorsForBean()方法的實現。getAdvicesAndAdvisorsForBean()實現如下。

protected Object[] getAdvicesAndAdvisorsForBean(
        Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    //將作用於當前bean的通知獲取出來,並且通知會被封裝成Advisor的實現類
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

繼續看findEligibleAdvisors()方法,如下所示。

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    //找到容器中所有由@Aspect註解修飾的切面,並將切面中的每個通知方法都封裝成一個Advisor的實現類
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    //在candidateAdvisors中將作用於當前bean的Advisor獲取出來
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        //對Advisor進行排序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

findEligibleAdvisors()方法中會先呼叫到AnnotationAwareAspectJAutoProxyCreatorfindCandidateAdvisors()方法,在findCandidateAdvisors()方法中會再呼叫BeanFactoryAspectJAdvisorsBuilderbuildAspectJAdvisors()方法來遍歷當前容器中的每個由@Aspect註解修飾的切面,然後將每個切面的通知封裝成Advisor並返回,同時每遍歷一個切面,都會將這個切面的所有Advisor快取,以便下次獲取時直接從快取獲取。

findEligibleAdvisors()方法中在獲取到當前容器中的所有Advisor後,會再呼叫findAdvisorsThatCanApply()方法來找出能夠作用於當前beanAdvisor,判斷依據就是根據Advisor中的Pointcut來判斷。

findEligibleAdvisors()方法最後還會對作用於當前bean的所有Advisor進行排序,這個後面再分析。所以findEligibleAdvisors()方法執行完,就獲取到了能夠作用於當前bean的所有通知對應的Advisor,也就獲取到了通知鏈。

最後再分析一下AnnotationAwareAspectJAutoProxyCreatorfindCandidateAdvisors()方法中是如何獲取容器中所有通知以及是如何將每個通知封裝成Advisor的。BeanFactoryAspectJAdvisorsBuilderbuildAspectJAdvisors()方法如下所示。

public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = this.aspectBeanNames;
    //aspectNames不為null表示獲取過切面bean的通知並把這些通知進行了快取,那麼直接從快取獲取通知
    if (aspectNames == null) {
        synchronized (this) {
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new ArrayList<>();
                aspectNames = new ArrayList<>();
                //把容器中的bean的名字獲取出來
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Object.class, true, false);
                //遍歷每個bean
                for (String beanName : beanNames) {
                    if (!isEligibleBean(beanName)) {
                        continue;
                    }
                    Class<?> beanType = this.beanFactory.getType(beanName, false);
                    if (beanType == null) {
                        continue;
                    }
                    //判斷bean是否是切面bean
                    if (this.advisorFactory.isAspect(beanType)) {
                        //把切面bean的名字新增到集合中,以便後續快取起來
                        aspectNames.add(beanName);
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                            MetadataAwareAspectInstanceFactory factory =
                                    new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                            //呼叫到ReflectiveAspectJAdvisorFactory的getAdvisors()方法來獲取切面bean裡的通知
                            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                            if (this.beanFactory.isSingleton(beanName)) {
                                //如果切面bean是單例,則快取切面bean的通知
                                this.advisorsCache.put(beanName, classAdvisors);
                            }
                            else {
                                //如果切面bean不是單例,則快取切面bean的工廠
                                //通過切面bean的工廠可以每次都生成切面bean的通知
                                this.aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        }
                        else {
                            if (this.beanFactory.isSingleton(beanName)) {
                                throw new IllegalArgumentException("Bean with name '" + beanName +
                                        "' is a singleton, but aspect instantiation model is not singleton");
                            }
                            MetadataAwareAspectInstanceFactory factory =
                                    new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                            this.aspectFactoryCache.put(beanName, factory);
                            advisors.addAll(this.advisorFactory.getAdvisors(factory));
                        }
                    }
                }
                this.aspectBeanNames = aspectNames;
                //返回容器中的所有通知
                return advisors;
            }
        }
    }

    //執行到這裡表示已經生成過通知並進行了快取
    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    }
    List<Advisor> advisors = new ArrayList<>();
    for (String aspectName : aspectNames) {
        //將每個切面bean的通知從快取中獲取出來並加到結果集合中
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        }
        else {
            //非單例切面bean就使用其對應的工廠新生成通知,然後也加入到結果集合中
            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    //返回容器中的所有通知
    return advisors;
}

上面buildAspectJAdvisors()方法中主要是Spring對切面bean的通知的一個快取策略,主要思想就是第一次獲取時會真實的將所有切面bean的通知獲取出來並生成Advisor,然後快取起來,後續再獲取通知時就從快取中獲取。下面繼續深入分析一下切面bean的通知是如何被封裝成Advisor的,實際的邏輯發生在ReflectiveAspectJAdvisorFactorygetAdvisors()方法中,如下所示。

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    //得到切面bean的Class物件
    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    //得到切面bean的名字
    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    validate(aspectClass);

    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
            new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

    List<Advisor> advisors = new ArrayList<>();
    //呼叫getAdvisorMethods()方法來把非切點方法獲取出來,並遍歷
    for (Method method : getAdvisorMethods(aspectClass)) {
        //先將通知上的切點構造成AspectJExpressionPointcut,然後再建立通知對應的Advisor
        //建立出來的Advisor實際為InstantiationModelAwarePointcutAdvisorImpl
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
        //當前method如果是通知方法,則將通知方法對應的Advisor新增到結果集合中
        //如果不是通知方法,得到的Advisor會為null,就不會新增到結果集合中
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        advisors.add(0, instantiationAdvisor);
    }

    for (Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    return advisors;
}

至此獲取通知鏈的原始碼分析完畢。下面對獲取作用於某個bean的通知鏈步驟進行小節。

  • 如果是第一次獲取通知鏈,那麼會遍歷容器中每個由@Aspect註解修飾的切面bean然後將其通知封裝成Advisor並快取起來,如果不是第一次獲取,就直接從快取中將所有Advisor獲取出來;
  • 然後篩選得到作用於當前beanAdvisor,並加入集合中;
  • 返回篩選得到的集合,作為後續建立AOP動態代理物件的通知鏈。

五. AOP動態代理物件的建立

已知在AbstractAutoProxyCreatorwrapIfNecessary()方法中會先呼叫getAdvicesAndAdvisorsForBean()方法獲取作用於當前bean的通知鏈,那麼下一步就應該基於通知鏈為當前bean生成AOP動態代理物件,生成動態代理物件的邏輯在AbstractAutoProxyCreatorcreateProxy()方法中,如下所示。

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
        @Nullable Object[] specificInterceptors, TargetSource targetSource) {

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }

    //建立ProxyFactory來建立動態代理物件
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);

    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    //為ProxyFactory設定通知鏈
    proxyFactory.addAdvisors(advisors);
    //為ProxyFactory設定目標物件
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    //呼叫ProxyFactory的getProxy()方法建立動態代理物件
    return proxyFactory.getProxy(getProxyClassLoader());
}

createProxy()方法中會先建立一個ProxyFactory工廠,然後為ProxyFactory工廠設定通知鏈和目標物件,後續的動態代理物件的建立就是由ProxyFactory工廠來完成。ProxyFactory工廠類圖如下所示。

Spring-SpringAOP原始碼學習(上)

所以ProxyFactory其實是一個AdvisedSupportProxyFactorygetProxy()方法如下所示。

public Object getProxy(@Nullable ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}

ProxyFactorygetProxy()方法中會先呼叫到其父類ProxyCreatorSupport中的createAopProxy()方法,createAopProxy()方法會有兩種返回值,一個是JdkDynamicAopProxy,負責JDK動態代理物件的生成,另一個是CglibAopProxy,負責CGLIB動態代理物件的生成,下面看一下ProxyCreatorSupportcreateAopProxy()方法的實現。

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    //getAopProxyFactory()會返回一個AopProxyFactory
    //AopProxyFactory的createAopProxy()會返回一個AopProxy
    //根據不同的目標類和不同的配置,會最終決定AopProxy是JdkDynamicAopProxy還是CglibAopProxy
    //建立AopProxy時還會將ProxyFactory自己傳入,所以建立出來的AopProxy也就持有了通知鏈和目標物件
    return getAopProxyFactory().createAopProxy(this);
}

ProxyCreatorSupportcreateAopProxy()方法中建立AopProxy時會將ProxyFactory傳入,所以建立出來的AopProxy也就通過ProxyFactory持有了通知鏈和目標物件。現在回到ProxyFactorygetProxy()方法,在拿到JdkDynamicAopProxy或者CglibAopProxy之後,就會呼叫其getProxy()方法來生成動態代理物件,下面以JdkDynamicAopProxygetProxy()方法為例進行說明,JdkDynamicAopProxygetProxy()方法如下所示。

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    //呼叫Proxy的newProxyInstance()方法來生成動態代理物件
    //proxiedInterfaces中有bean實現的介面
    //JdkDynamicAopProxy自身是實現了InvocationHandler介面,所以這將JdkDynamicAopProxy傳到了newProxyInstance()方法中
    return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

JdkDynamicAopProxygetProxy()方法就是呼叫ProxynewProxyInstance()方法來建立JDK動態代理物件。

至此SpringAOP中建立AOP動態代理物件的原始碼分析完畢。下面給出為bean基於通知鏈建立動態代理物件的步驟小節。

  • 建立ProxyFactory,為ProxyFactory設定通知鏈和目標物件,後續通過ProxyFactory建立動態代理物件;
  • 通過ProxyFactory先建立AopProxy,根據使用的動態代理方式的不同,建立出來的AopProxy可以為JdkDynamicAopProxy或者ObjenesisCglibAopProxy,並且ProxyFactory在建立AopProxy時傳入了自身,所以建立出來的AopProxy也就持有了通知鏈和目標物件;
  • 通過建立出來的AopProxy生成動態代理物件。

總結

Spring中有使用者自定義的切面以及Spring框架提供的切面,這些切面會在bean的生命週期呼叫到BeanPostProcessorspostProcessAfterInitialization()方法時織入bean,織入的形式就是為bean生成AOP動態代理物件。為bean生成動態代理物件前會先獲取到容器中所有能夠作用於這個bean的通知,這些通知會被封裝成Advisor的實現類並加入到集合中,可以稱這個Advisor的集合為通知鏈,獲取到通知鏈後,會建立一個ProxyFactory工廠來幫助建立動態代理物件,建立前會先通過ProxyFactory建立AopProxy,根據使用的動態代理方式的不同,建立出來的AopProxy可以為JdkDynamicAopProxy或者ObjenesisCglibAopProxy,並且ProxyFactory在建立AopProxy時傳入了自身,所以建立出來的AopProxy也就持有了通知鏈和目標物件,最後就是通過AopProxy將實際的動態代理物件生成出來。

相關文章