Spring @Lookup深入分析實(shí)現(xiàn)原理
1. 前言
在使用Spring的時(shí)候,往單例bean注入原型bean時(shí),原型bean可能會(huì)失效,如下:
@Component public class Person { @Autowired Car car; public Car getCar() { return car; } } @Component @Scope("prototype") public class Car { }
調(diào)用Person#getCar()
方法返回的總是同一個(gè)Car對(duì)象,這也很好理解,因?yàn)镻erson是單例的,Spring在創(chuàng)建Person時(shí)只會(huì)注入一次Car對(duì)象,以后Car都不會(huì)再改變了。
怎么解決這個(gè)問(wèn)題呢?Spring提供了多種方式來(lái)獲取原型bean。
2. 解決方案
解決方案有很多,本文重點(diǎn)分析@Lookup
注解的方式。
1、每次從ApplicationContext重新獲取bean。
@Component public class Person implements ApplicationContextAware { private ApplicationContext applicationContext; @Lookup public Car getCar() { return applicationContext.getBean(Car.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
2、通過(guò)CGLIB生成Car子類代理對(duì)象,每次都從容器內(nèi)獲取bean執(zhí)行。
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) public class Car { }
3、通過(guò)**@Lookup**
注解的方式,方法體已經(jīng)不重要了,代理對(duì)象每次都會(huì)從容器中重新獲取bean。
@Component public class Person { @Lookup public Car getCar() { return null; } }
3. 源碼分析
為什么方法上加了@Lookup
注解,調(diào)用該方法就能拿到原型bean了呢?其實(shí)縱觀上述三種方式,要想拿到原型bean,底層原理都是一樣的,那就是每次都通過(guò)ApplicationContext#getBean()
方法從容器中重新獲取,只要Car本身是原型的,Spring就會(huì)保證每次拿到的都是新創(chuàng)建的Car實(shí)例。
**@Lookup**
注解也是通過(guò)生成代理類的方式,重寫被標(biāo)記的方法,每次都從ApplicationContext獲取bean。
1、Spring加載Person的時(shí)候,容器內(nèi)不存在該bean,那首先就是要實(shí)例化Person對(duì)象。Spring會(huì)通過(guò)SimpleInstantiationStrategy#instantiate()
方法去實(shí)例化Person。
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { /** * 如果bean沒(méi)有要重寫的方法,直接反射調(diào)用構(gòu)造函數(shù)創(chuàng)建對(duì)象 * 反之,需要通過(guò)CGLIB創(chuàng)建增強(qiáng)子類代理對(duì)象 */ if (!bd.hasMethodOverrides()) { Constructor<?> constructorToUse; synchronized (bd.constructorArgumentLock) { constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod; if (constructorToUse == null) { final Class<?> clazz = bd.getBeanClass(); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { if (System.getSecurityManager() != null) { constructorToUse = AccessController.doPrivileged( (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor); } else { constructorToUse = clazz.getDeclaredConstructor(); } bd.resolvedConstructorOrFactoryMethod = constructorToUse; } catch (Throwable ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } } return BeanUtils.instantiateClass(constructorToUse); } else { /** * 存在 lookup-method 和 replaced-method * 通過(guò)CGLIB生成代理類 */ return instantiateWithMethodInjection(bd, beanName, owner); } }
BeanDefinition有一個(gè)屬性**methodOverrides**
,它里面存放的是當(dāng)前bean里面是否有需要被重寫的方法,這些需要被重寫的方法可能是**lookup-method**
或**replaced-method**
。如果沒(méi)有需要重寫的方法,則直接通過(guò)反射調(diào)用構(gòu)造函數(shù)來(lái)實(shí)例化對(duì)象;如果有需要重寫的方法,這個(gè)時(shí)候就不能直接實(shí)例化對(duì)象了,需要通過(guò)CGLIB來(lái)創(chuàng)建增強(qiáng)子類,把父類的方法給重寫掉。
于是會(huì)調(diào)用instantiateWithMethodInjection()
方法來(lái)實(shí)例化bean,最終是通過(guò)CglibSubclassCreator
來(lái)實(shí)例化。
@Override protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, @Nullable Constructor<?> ctor, Object... args) { // Must generate CGLIB subclass... return new CglibSubclassCreator(bd, owner).instantiate(ctor, args); }
2、CglibSubclassCreator創(chuàng)建CGLIB子類,重寫父類方法。Spring會(huì)通過(guò)Enhancer來(lái)創(chuàng)建增強(qiáng)子類,被@Lookup
標(biāo)記的方法會(huì)被LookupOverrideMethodInterceptor攔截。
public Object instantiate(@Nullable Constructor<?> ctor, Object... args) { Class<?> subclass = createEnhancedSubclass(this.beanDefinition); Object instance; if (ctor == null) { instance = BeanUtils.instantiateClass(subclass); } else { try { Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes()); instance = enhancedSubclassConstructor.newInstance(args); } catch (Exception ex) { throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex); } } Factory factory = (Factory) instance; factory.setCallbacks(new Callback[] {NoOp.INSTANCE, new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner), new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)}); return instance; }
3、LookupOverrideMethodInterceptor要攔截被@Lookup
標(biāo)記的方法,必然要實(shí)現(xiàn)intercept()
方法。邏輯很簡(jiǎn)單,就是每次都調(diào)用BeanFactory#getBean()
從容器中獲取bean。
@Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { // Cast is safe, as CallbackFilter filters are used selectively. LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method); Assert.state(lo != null, "LookupOverride not found"); Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all if (StringUtils.hasText(lo.getBeanName())) { return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : this.owner.getBean(lo.getBeanName())); } else { return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : this.owner.getBean(method.getReturnType())); } }
4. 總結(jié)
一個(gè)bean一旦擁有被@Lookup
注解標(biāo)記的方法,就意味著該方法需要被重寫掉,Spring在實(shí)例化bean的時(shí)候會(huì)自動(dòng)基于CGLIB生成增強(qiáng)子類對(duì)象重寫掉父類方法。此時(shí)父類被@Lookup
注解標(biāo)記的方法體已經(jīng)不重要了,不會(huì)被執(zhí)行了,CGLIB子類會(huì)通過(guò)LookupOverrideMethodInterceptor攔截掉被@Lookup
注解標(biāo)記的方法。方法體重寫的邏輯也很簡(jiǎn)單,就是每次都通過(guò)BeanFactory獲取bean,只要bean本身是原型的,每次拿到的都將是不同的實(shí)例。
到此這篇關(guān)于Spring @Lookup深入分析實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Spring @Lookup內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析java基本數(shù)據(jù)類型傳遞與引用傳遞區(qū)別
這篇文章主要介紹了java基本數(shù)據(jù)類型傳遞與引用傳遞區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03深入解析Java的Hibernate框架中的一對(duì)一關(guān)聯(lián)映射
這篇文章主要介紹了Java的Hibernate框架的一對(duì)一關(guān)聯(lián)映射,包括對(duì)一對(duì)一外聯(lián)映射的講解,需要的朋友可以參考下2016-01-01mybatis-plus中更新null值的問(wèn)題解決
本文主要介紹 mybatis-plus 中常使用的 update 相關(guān)方法的區(qū)別,以及更新 null 的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04SpringBoot項(xiàng)目啟動(dòng)后立馬自動(dòng)關(guān)閉的解決方案
這篇文章主要介紹了SpringBoot項(xiàng)目啟動(dòng)后立馬自動(dòng)關(guān)閉的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03Java動(dòng)態(tài)腳本Groovy獲取Bean技巧
這篇文章主要給大家分享的是Java動(dòng)態(tài)腳本Groovy獲取Bean技巧,在Java代碼中當(dāng)我們需要一個(gè)Bean對(duì)象,通常會(huì)使用spring中@Autowired注解,用來(lái)自動(dòng)裝配對(duì)象。下面我們一起進(jìn)入文章學(xué)習(xí)個(gè)表格多 詳細(xì)內(nèi)容吧2021-12-12關(guān)于Java中常見(jiàn)的負(fù)載均衡算法
這篇文章主要介紹了關(guān)于Java中常見(jiàn)的負(fù)載均衡算法,負(fù)載平衡是一種電子計(jì)算機(jī)技術(shù),用來(lái)在多個(gè)計(jì)算機(jī)、網(wǎng)絡(luò)連接、CPU、磁盤驅(qū)動(dòng)器或其他資源中分配負(fù)載,以達(dá)到優(yōu)化資源使用、最大化吞吐率、最小化響應(yīng)時(shí)間、同時(shí)避免過(guò)載的目的,需要的朋友可以參考下2023-08-08Mybatis-plus在項(xiàng)目中的簡(jiǎn)單應(yīng)用
Mybatis-plus是Spring框架中OOM的一大利器,其簡(jiǎn)單易用參考官網(wǎng)文檔即可很快上手,本文主要介紹了邏輯刪除,自動(dòng)填充,分頁(yè)插件等的簡(jiǎn)單使用,感興趣的可以了解一下2021-07-07