Spring Bean 名稱暗藏玄機,這樣取名就不會被代理
來源:江南一點雨
一些使用小細節就是在不斷的原始碼探索中逐步發現的,今天就來和小夥伴們聊一下透過 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 的基礎之上,增加了額外的能力:
在 bean 例項化之前先做一些預處理,例如直接建立代理物件,代替後續的 bean 生成。 在 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;
}
這個方法實際上幹了兩件事:
檢查當前 bean 是否需要代理,如果不需要代理,那麼就存入到一個 map 集合 advisedBeans 中,key 是 bean 的名字,value 如果為 true 則表示這個 bean 是需要代理的,value 為 false,則表示這個 bean 是不需要代理的。 如果有我們有自定義的 TargetSource,則根據自定義的 TargetSource 去建立代理物件。
這裡我要和大家說的是第一點。
在判斷一個 bean 是否需要代理的時候,主要依據兩個方法:
isInfrastructureClass:這個方法主要是檢查當前 bean 是否是 Advice/Advisor/Pointcut 等型別,或者這個類上是否有 @Aspect 註解,這個松哥在之前的文章中其實和大家介紹過了:聽說 Spring Bean 的建立還有一條捷徑?。 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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 評審程式碼時,這樣寫就不會被懟了
- 評審程式碼時,這樣寫就不會被懟了,乾貨
- 這座暗藏玄機的“智慧島”,竟然出自華為之手
- 聊聊spring bean名稱命名的那些事兒SpringBean
- Python靜態方法,其實暗藏玄機Python
- Spring竟然可以建立“重複”名稱的bean?—一次專案中存在多個bean名稱重複問題的排查SpringBean
- 新款iPad Pro「背後」暗藏玄機:鐳射雷達還能這麼小?iPad
- Spring名稱空間解析Spring
- 微軟被指責暗藏Windows API微軟WindowsAPI
- spring bean別名的兩種配置方式SpringBean
- RAC安裝完成後,就不能輕易更改主機名稱了
- 學會了ES6,就不會寫出那樣的程式碼
- CSS 級聯樣式表名稱解析CSS
- spring框架中的名稱空間Spring框架
- 在鏈圈,我們會稱這樣的科技為“去中心化”中心化
- 博主真會玩,Spring單例bean中使用多例bean,你未必會玩?Spring單例Bean
- Spring Boot 開箱即用,內藏玄機Spring Boot
- 動態代理會不會被ASM,Instrument取代?ASM
- 怎麼樣在JBUILDERX中實現會話BEAN訪問實體BEAN??UI會話Bean
- 什麼樣的類需要作成EJB(會話Bean)?會話Bean
- [Spring]BeanSpringBean
- spring動態註冊bean會使AOP失效?SpringBean
- 網際網路大會上為何這4家企業被"點名"
- 【菜鳥學Java】13:代理模式——動態代理這樣玩!Java模式
- 《網際網路防裁指南》: 熟讀並背誦,就不會被裁!
- Spring 可以這樣配置嗎?Spring
- 玄機
- 在SPRING自動代理事務中.怎樣讓指定的攔截器對應指定的目標BEAN?SpringBean
- spring上 -基於註解配置bean,動態代理,AOP筆記SpringBean筆記
- 為什麼軟體會被稱為“軟體”
- Spring系列第十四講 單例bean中使用多例bean,你未必會玩?Spring單例Bean
- 微軟預測Win10紅石2正式版本號:14946版本中暗藏玄機微軟Win10
- Spring中Bean及@Bean的理解SpringBean
- 原始碼分析 | 手寫mybait-spring核心功能(乾貨好文一次學會工廠bean、類代理、bean註冊的使用)原始碼AISpringBean
- Spring Bean容器SpringBean
- 【Spring】Bean管理SpringBean
- Nginx代理websocket為什麼要這樣做?NginxWeb
- PbootCMS提示:URL名稱與模型URL名稱衝突,請換一個名稱!boot模型