Spring Bean 名稱暗藏玄機,這樣取名就不會被代理

碼農談IT發表於2023-10-25


來源:江南一點雨

一些使用小細節就是在不斷的原始碼探索中逐步發現的,今天就來和小夥伴們聊一下透過 beanName 的設定,可以讓一個 bean 拒絕被代理!

1. 程式碼實踐

假設我有如下一個切面:

@Aspect
@EnableAspectJAutoProxy
@Component
public class LogAspect {
    @Pointcut("execution(* org.javaboy.demo.service.*.*(..))")
    public void pc() {

    }

    @Before("pc()")
    public void before(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + " 方法開始執行了...");
    }
}

這個切面要攔截的方法是 org.javaboy.demo.service 包下的所有類的所有方法,現在,這個包下有一個 BookService 類,內容如下:

@Service("org.javaboy.demo.service.BookService.ORIGINAL")
public class BookService {

    public void hello() {
        System.out.println("hello bs");
    }
}

這個 BookService 的 beanName 我沒有使用預設的 beanName,而是自己配置了一個 beanName,這個 beanName 的配置方式是 類名的完整路徑+.ORIGINAL

當我們按照這樣的規則給 bean 取名之後,那麼即使當前 bean 已經包含在切點所定義的範圍內,這個 bean 也不會被代理了。

這是 Spring5.1 開始的新玩法。

這種寫法的原理是什麼呢?

2. 原理分析

在 Spring 建立 Bean 的時候,小夥伴們都知道,bean 建立完成之後會去各種後置處理器(BeanPostProcessor)中走一圈,所以一般我們認為 BeanPostProcessor 是在 bean 例項建立完成之後執行。但是,BeanPostProcessor 中有一個特例 InstantiationAwareBeanPostProcessor,這個介面繼承自 BeanPostProcessor,但是在 BeanPostProcessor 的基礎之上,增加了額外的能力:

  1. 在 bean 例項化之前先做一些預處理,例如直接建立代理物件,代替後續的 bean 生成。
  2. 在 bean 例項化之後但是屬性填充之前,可以自定義一些屬性注入策略。

大致上就是這兩方面的能力。

具體到程式碼上,就是在建立 bean 的 createBean 方法中:

@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 方法,我們來看看這裡涉及到的跟 AOP 相關的 AbstractAutoProxyCreator#postProcessBeforeInstantiation 方法:

@Override
public Object postProcessBeforeInstantiation(Class {
 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;
}

這個方法實際上幹了兩件事:

  1. 檢查當前 bean 是否需要代理,如果不需要代理,那麼就存入到一個 map 集合 advisedBeans 中,key 是 bean 的名字,value 如果為 true 則表示這個 bean 是需要代理的,value 為 false,則表示這個 bean 是不需要代理的。
  2. 如果有我們有自定義的 TargetSource,則根據自定義的 TargetSource 去建立代理物件。

這裡我要和大家說的是第一點。

在判斷一個 bean 是否需要代理的時候,主要依據兩個方法:

  1. isInfrastructureClass:這個方法主要是檢查當前 bean 是否是 Advice/Advisor/Pointcut 等型別,或者這個類上是否有 @Aspect 註解,這個松哥在之前的文章中其實和大家介紹過了:聽說 Spring Bean 的建立還有一條捷徑?。
  2. shouldSkip:如果 isInfrastructureClass 方法返回 false,那麼就要執行 shouldSkip 了,我們來仔細看下 shouldSkip 方法。
@Override
protected boolean shouldSkip(Class {
 // TODO: Consider optimization by caching the list of the aspect names
 List

這裡首先找到系統中所有的切面,找到之後挨個遍歷,遍歷的時候判斷如果當前要建立的 bean 剛好就是切面,那切面肯定是不需要代理的,直接返回 true。否則就會去呼叫父類的 shouldSkip 方法,我們再來瞅一眼父類的 shouldSkip 方法:

protected boolean shouldSkip(Class {
 return AutoProxyUtils.isOriginalInstance(beanName, beanClass);
}
static boolean isOriginalInstance(String beanName, Class {
 if (!StringUtils.hasLength(beanName) || beanName.length() !=
   beanClass.getName().length() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX.length()) {
  return false;
 }
 return (beanName.startsWith(beanClass.getName()) &&
   beanName.endsWith(AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX));
}

父類 shouldSkip 方法主要是呼叫了一個靜態工具方法 isOriginalInstance 來判斷當前 bean 是否是一個不需要代理的 bean,這個具體的判斷邏輯就是檢查這個 beanName 是否按照 類名的完整路徑+.ORIGINAL 的方式命名的,如果是則返回 true。

當 shouldSkip 方法返回 true 的時候,就會進入到 postProcessBeforeInstantiation 方法的 if 分支中,該分支將當前 beanName 存入到 advisedBeans 集合中,儲存的 key 就是 beanName,value 則是 false,然後將方法 return。

當 bean 建立完成之後,再進入到 AbstractAutoProxyCreator#postProcessAfterInitialization 方法中處理的時候,就會發現這個 bean 已經存入到 advisedBeans 集合中,並且 value 是 false,這就意味著這個 bean 不需要代理,那麼就針對該 bean 就不會進行 AOP 處理了,直接 return 即可。

好啦,一個小小細節,加深大家對 Spring AOP 的理解,感興趣的小夥伴可以去試試哦~



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

相關文章