Spring 冷知識:一個提前 AOP 的機會

張哥說技術發表於2023-11-02

來源:江南一點雨


今天再來聊一個 Spring 中的冷門知識:Bean 的處理不走正常流程,而是提前進行 AOP。

本文算是前面文章(Spring Bean 名稱暗藏玄機,這樣取名就不會被代理)內容的一個補充,如果還沒閱讀前文,建議先閱讀,這樣有利於更好的理解本文。

1. Bean 建立流程

前面文章中,松哥和大家梳理了,在 Bean 建立的過程中,會先給 BeanPostProcessor 一個返回代理物件的機會:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
  throws BeanCreationException 
{
 //省略。。。
 try {
  // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
  Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
  if (bean != null) {
   return bean;
  }
 }
 catch (Throwable ex) {
  throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
    "BeanPostProcessor before instantiation of bean failed", ex);
 }
 try {
  Object beanInstance = doCreateBean(beanName, mbdToUse, args);
  if (logger.isTraceEnabled()) {
   logger.trace("Finished creating instance of bean '" + beanName + "'");
  }
  return beanInstance;
 }
    //省略。。。
}

小夥伴們看,這裡的 resolveBeforeInstantiation 方法就是給 BeanPostProcessor 一個返回代理物件的機會,在這個方法中,最終就會觸發到 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法,而在 postProcessBeforeInstantiation 方法中,會先判斷當前 bean 是否是 AOP 相關類等:

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
 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;
  }
 }
 
 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;
}

前面 if 分支中的內容,松哥在前面的文章中已經和大家分析過了,這裡就不再贅述。

這裡主要來說說 getCustomTargetSource 中的邏輯。

先來說什麼情況下會走到 getCustomTargetSource 方法:當前 Bean 不是代理物件,也不是 AOP 相關的類,就是一個普普通通的常規類,那麼就會走到 getCustomTargetSource 方法這裡來,這裡失去查詢到一個 TargetSource 物件,然後根據該物件建立當前 bean 的代理物件並返回,如果返回了代理物件,那麼後續的 bean 建立流程就不執行了。

我們來看下這個方法的原始碼:

@Nullable
protected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) {
 // We can't create fancy target sources for directly registered singletons.
 if (this.customTargetSourceCreators != null &&
   this.beanFactory != null && this.beanFactory.containsBean(beanName)) {
  for (TargetSourceCreator tsc : this.customTargetSourceCreators) {
   TargetSource ts = tsc.getTargetSource(beanClass, beanName);
   if (ts != null) {
    return ts;
   }
  }
 }
 // No custom TargetSource found.
 return null;
}

可以看到,這裡就是當前類 AbstractAutoProxyCreator 中有一個 customTargetSourceCreators 變數,現在就是遍歷該變數,透過這個集合中儲存的 TargetSourceCreator 來建立 TargetSource 物件。

TargetSourceCreator 是一個介面,這個介面只有一個抽象類 AbstractBeanFactoryBasedTargetSourceCreator,我們來看下 AbstractBeanFactoryBasedTargetSourceCreator 中的 getTargetSource 方法是怎麼執行的:

@Override
@Nullable
public final TargetSource getTargetSource(Class<?> beanClass, String beanName) {
 AbstractBeanFactoryBasedTargetSource targetSource =
   createBeanFactoryBasedTargetSource(beanClass, beanName);
 if (targetSource == null) {
  return null;
 }

 DefaultListableBeanFactory internalBeanFactory = getInternalBeanFactoryForBean(beanName);
 // We need to override just this bean definition, as it may reference other beans
 // and we're happy to take the parent's definition for those.
 // Always use prototype scope if demanded.
 BeanDefinition bd = getConfigurableBeanFactory().getMergedBeanDefinition(beanName);
 GenericBeanDefinition bdCopy = new GenericBeanDefinition(bd);
 if (isPrototypeBased()) {
  bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE);
 }
 internalBeanFactory.registerBeanDefinition(beanName, bdCopy);
 // Complete configuring the PrototypeTargetSource.
 targetSource.setTargetBeanName(beanName);
 targetSource.setBeanFactory(internalBeanFactory);
 return targetSource;
}

首先,TargetSource 物件是透過 createBeanFactoryBasedTargetSource 方法來建立的,這個方法是一個抽象方法,將來在子類中被實現。

接下來會呼叫 getInternalBeanFactoryForBean 方法建立一個新的內部容器 internalBeanFactory,本質上這個 internalBeanFactory 其實是一個子容器,現有的容器將作為這個子容器的父容器。

接下來就是獲取到當前 beanName 所對應的 BeanDefinition,然後進行屬性配置,並註冊到內部容器中,最後返回 targetSource 物件。

我們來看下這裡的 getInternalBeanFactoryForBean 方法:

protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) {
 synchronized (this.internalBeanFactories) {
  return this.internalBeanFactories.computeIfAbsent(beanName,
    name -> buildInternalBeanFactory(getConfigurableBeanFactory()));
 }
}

protected DefaultListableBeanFactory buildInternalBeanFactory(ConfigurableBeanFactory containingFactory) {
 // Set parent so that references (up container hierarchies) are correctly resolved.
 DefaultListableBeanFactory internalBeanFactory = new DefaultListableBeanFactory(containingFactory);
 // Required so that all BeanPostProcessors, Scopes, etc become available.
 internalBeanFactory.copyConfigurationFrom(containingFactory);
 // Filter out BeanPostProcessors that are part of the AOP infrastructure,
 // since those are only meant to apply to beans defined in the original factory.
 internalBeanFactory.getBeanPostProcessors().removeIf(beanPostProcessor ->
   beanPostProcessor instanceof AopInfrastructureBean);
 return internalBeanFactory;
}

這個其實就是正常的容器建立,倒也沒啥好說的,但是有幾個需要注意的點:

  1. 在呼叫 buildInternalBeanFactory 方法構建容器的時候,會先呼叫 getConfigurableBeanFactory 方法獲取到當前容器作為父容器,如果當前容器不存在,那麼就會丟擲異常。這就意味著,當我們自己提供 TargetSourceCreator 例項的時候,一定要指定一個容器。
  2. 在建立了內部容器之後,會從內部容器中移除所有 AopInfrastructureBean 型別的 BeanPostProcessor,也就是內部容器將來建立出來的 bean,不再走 AopInfrastructureBean 型別後置處理器,因為這種型別的後置處理器主要是用來處理 AOP 的,現在,AOP 代理當場就生成了,就不再需要這些後置處理器了。

好了,這就是大致的 AOP 提前生成原理,接下來松哥寫一個案例我們一起來看下。

2. 實踐

首先,我們先來自定義一個 TargetSource:

public class UserServiceTargetSource extends AbstractBeanFactoryBasedTargetSource {
    @Override
    public Object getTarget() throws Exception {
        return getBeanFactory().getBean(getTargetBeanName());
    }

    @Override
    public boolean isStatic() {
        return true;
    }
}

關於 TargetSource 本身,松哥在之前的 Spring 原始碼影片中已經和大家介紹過很多了,這裡我就不再囉嗦了。

接下來自定義 TargetSourceCreator:

public class CustomTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator {

    @Override
    protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource(Class<?> beanClass, String beanName) {
        if (getBeanFactory() instanceof ConfigurableListableBeanFactory) {
            if (beanClass.isAssignableFrom(UserService.class)) {
                return new UserServiceTargetSource();
            }
        }
        return null;
    }
}

如果要建立的 bean 是 UserService 的話,那麼就給返回一個 UserServiceTargetSource 物件。

最後,也是最關鍵的一步,根據前面的分析,TargetSourceCreator 是存在於 AnnotationAwareAspectJAutoProxyCreator 這樣一個 InstantiationAwareBeanPostProcessor 型別的後置處理器中的,因此,我們要想辦法把自定義的 TargetSourceCreator 設定給 AnnotationAwareAspectJAutoProxyCreator,如下:

@Component
public class SetCustomTargetSourceCreator implements BeanPostProcessorPriorityOrderedBeanFactoryAware {

    private BeanFactory beanFactory;

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof AnnotationAwareAspectJAutoProxyCreator) {
            AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator = (AnnotationAwareAspectJAutoProxyCreator)bean;
            CustomTargetSourceCreator customTargetSourceCreator = new CustomTargetSourceCreator();
            customTargetSourceCreator.setBeanFactory(beanFactory);
            annotationAwareAspectJAutoProxyCreator.setCustomTargetSourceCreators(customTargetSourceCreator);
        }
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

AnnotationAwareAspectJAutoProxyCreator 本身就是一個 BeanPostProcessor,我們現在要做的就是修改這個 BeanPostProcessor,BeanPostProcessor 是在 Spring 容器啟動時候的 refresh 方法中去初始化的,整個的初始化過程松哥在之前的BeanPostProcessor 是在何時介入 Bean 建立的?一文中已經詳細介紹過了。

BeanPostProcessor 初始化的時候,先初始化實現了 PriorityOrdered 介面的,再初始化實現了 Ordered 介面的,最後再去初始化那些沒有實現任何排序介面的 BeanPostProcessor。

而我們這裡 SetCustomTargetSourceCreator 一定要趕在 AnnotationAwareAspectJAutoProxyCreator 之前進行初始化,這樣,當 AnnotationAwareAspectJAutoProxyCreator 進行初始化的時候,就會用到 SetCustomTargetSourceCreator 這樣一個後置處理器,進而在該處理器中修改 AnnotationAwareAspectJAutoProxyCreator 的屬性。

AnnotationAwareAspectJAutoProxyCreator 類間接實現了 Ordered 介面,預設優先順序是最低,但是在 Spring 容器啟動時,在處理 BeanFactoryPostProcessor 時(具體是 ConfigurationClassPostProcessor),將其優先順序設定為最高。

所以,我們如果想要讓自定義的 SetCustomTargetSourceCreator 搶在 AnnotationAwareAspectJAutoProxyCreator 之前執行,那麼就只能讓 SetCustomTargetSourceCreator 去實現 PriorityOrdered 介面了,實現 PriorityOrdered 介面之後,重寫 getOrder 方法,這個方法返回值是什麼無所謂,反正都會在實現了 Ordered 介面的 BeanPostProcessor 之前執行。

最後,我們再在啟動類上開啟自動代理即可:

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class JavaConfig {
}

大功告成。

這樣,當 Spring 容器建立一個 Bean 的時候,就會提前被 BeanPostProcessor 攔截,然後給出一個 TargetSource,進而據此建立代理物件,這樣就不需要後續常規的 Bean 建立流程了。好啦,感興趣的小夥伴可以自己去試一試哦~


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2992442/,如需轉載,請註明出處,否則將追究法律責任。

相關文章