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è)問題呢?Spring提供了多種方式來獲取原型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、通過CGLIB生成Car子類代理對(duì)象,每次都從容器內(nèi)獲取bean執(zhí)行。
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Car {
}
3、通過**@Lookup**注解的方式,方法體已經(jīng)不重要了,代理對(duì)象每次都會(huì)從容器中重新獲取bean。
@Component
public class Person {
@Lookup
public Car getCar() {
return null;
}
}
3. 源碼分析
為什么方法上加了@Lookup注解,調(diào)用該方法就能拿到原型bean了呢?其實(shí)縱觀上述三種方式,要想拿到原型bean,底層原理都是一樣的,那就是每次都通過ApplicationContext#getBean()方法從容器中重新獲取,只要Car本身是原型的,Spring就會(huì)保證每次拿到的都是新創(chuàng)建的Car實(shí)例。
**@Lookup**注解也是通過生成代理類的方式,重寫被標(biāo)記的方法,每次都從ApplicationContext獲取bean。
1、Spring加載Person的時(shí)候,容器內(nèi)不存在該bean,那首先就是要實(shí)例化Person對(duì)象。Spring會(huì)通過SimpleInstantiationStrategy#instantiate()方法去實(shí)例化Person。
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
/**
* 如果bean沒有要重寫的方法,直接反射調(diào)用構(gòu)造函數(shù)創(chuàng)建對(duì)象
* 反之,需要通過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
* 通過CGLIB生成代理類
*/
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
BeanDefinition有一個(gè)屬性**methodOverrides**,它里面存放的是當(dāng)前bean里面是否有需要被重寫的方法,這些需要被重寫的方法可能是**lookup-method**或**replaced-method**。如果沒有需要重寫的方法,則直接通過反射調(diào)用構(gòu)造函數(shù)來實(shí)例化對(duì)象;如果有需要重寫的方法,這個(gè)時(shí)候就不能直接實(shí)例化對(duì)象了,需要通過CGLIB來創(chuàng)建增強(qiáng)子類,把父類的方法給重寫掉。
于是會(huì)調(diào)用instantiateWithMethodInjection()方法來實(shí)例化bean,最終是通過CglibSubclassCreator來實(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ì)通過Enhancer來創(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()方法。邏輯很簡單,就是每次都調(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ì)通過LookupOverrideMethodInterceptor攔截掉被@Lookup注解標(biāo)記的方法。方法體重寫的邏輯也很簡單,就是每次都通過BeanFactory獲取bean,只要bean本身是原型的,每次拿到的都將是不同的實(shí)例。
到此這篇關(guān)于Spring @Lookup深入分析實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Spring @Lookup內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)分布式任務(wù)調(diào)度的詳細(xì)步驟
隨著互聯(lián)網(wǎng)應(yīng)用的規(guī)模和復(fù)雜度不斷增加,單節(jié)點(diǎn)任務(wù)調(diào)度系統(tǒng)已經(jīng)難以滿足高并發(fā)、大數(shù)據(jù)量的處理需求,分布式任務(wù)調(diào)度成為了解決這一問題的重要手段,本文將介紹如何在Spring Boot中實(shí)現(xiàn)分布式任務(wù)調(diào)度,需要的朋友可以參考下2024-08-08
JAVA正則表達(dá)式及字符串的替換與分解相關(guān)知識(shí)總結(jié)
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí)總結(jié),文章圍繞著JAVA正則表達(dá)式及字符串的替換與分解展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Java 實(shí)現(xiàn)簡單靜態(tài)資源Web服務(wù)器的示例
這篇文章主要介紹了Java 實(shí)現(xiàn)簡單靜態(tài)資源Web服務(wù)器的示例,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-11-11
基于JSON和java對(duì)象的互轉(zhuǎn)方法
下面小編就為大家?guī)硪黄贘SON和java對(duì)象的互轉(zhuǎn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
Spring Boot集成Resilience4J實(shí)現(xiàn)限流/重試/隔離
在Java的微服務(wù)生態(tài)中,對(duì)于服務(wù)保護(hù)組件,像springcloud的Hystrix,springcloud?alibaba的Sentinel,以及當(dāng)Hystrix停更之后官方推薦使用的Resilience4j,所以本文給大家介紹了Spring Boot集成Resilience4J實(shí)現(xiàn)限流/重試/隔離,需要的朋友可以參考下2024-03-03
使用Java程序模擬實(shí)現(xiàn)新冠病毒傳染效果
這篇文章主要介紹了用Java程序模擬實(shí)現(xiàn)新冠病毒傳染效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
SpringBoot集成Spring Security用JWT令牌實(shí)現(xiàn)登錄和鑒權(quán)的方法
這篇文章主要介紹了SpringBoot集成Spring Security用JWT令牌實(shí)現(xiàn)登錄和鑒權(quán)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05

