前言
這篇文章我們就來具體使用下Spring提供給我們的Lookup方法。
正文
Xml配置lookup-method
首先我們需要定義一個Java的抽象類命名爲Fruit:
public abstract class Fruit {
// 抽象方法獲取水果
protected abstract Fruit getFruit(String fruitName);
}
再定義兩個公共類繼承自上面的Fruit:
public class Apple extends Fruit {
public Apple() {
System.out.println("我得到了一個蘋果" );
}
}
public class Orange extends Fruit {
public Orange() {
System.out.println("我得到了一個橙子" );
}
}
隨後我們通過Xml配置下這兩個對象爲Spring Bean:
<!-- 這是2個非單例模式的bean -->
<bean id="apple" class="com.john.xmlconfig.Apple" scope="prototype"/>
<bean id="orange" class="com.john.xmlconfig.Orange " scope="prototype"/>
最後我們來引入最重要一個Spring Bean,它是帶有lookup-method子標籤的:
<bean id="myFruit" class="com.john.xmlconfig.Fruit">
<lookup-method name="getFruit" bean="apple"/>
</bean>
我們注意到lookup-method的name屬性我們賦爲了Fruit類的getFruit抽象方法,而bean屬性則賦值爲apple這個Spring bean,我們姑且先可以猜測通過Spring容器運作後我們獲取myFruit後再調用它的getFruit方法,不出意外我們會得到apple這個bean。
接下來我們通過代碼啓動下Spring容器,來看看我們的猜想到底是否會生效?
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("classpath:spring-config-lookup.xml");
Fruit myFruit= (Fruit)app.getBean("myFruit");
myFruit.getFruit();
}
控制檯順利地打印出了 我得到了一個蘋果 ,說明我們的猜測是正確的。同理,我們通過配置@Lookup註解來驗證下。
@Lookup註解
我們把上面的所有Bean加上@Component註解,最重要的是要在Fruit類的getFruit方法上加上@Lookup註解:
public abstract class Fruit {
// 抽象方法獲取水果
@Lookup
protected abstract Fruit getFruit(String fruitName);
}
之後我們通過 AnnotationConfigApplicationContext 類來啓動Spring容器驗證下,不出意外也會打印出上面的一行中文 我得到了一個蘋果 。那麼Spring是如何成功獲取到這個抽象類並把抽象類的方法替換掉的呢?答案就是應用cglib提供的強大功能。
SimpleInstantiationStrategy
在第一種Xml配置的方式下Spring首先解析Xml配置後會把lookup-method放入對應beanDefinition的 methodOverrides 成員變量中,隨後在實例化這個beanDefinition的時候,它會判斷是否有methodOverrides ,如果有,那麼它就會調用 instantiateWithMethodInjection 方法,我們拿源碼作證:
//在SimpleInstantiationStrategy類下
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
if (!bd.hasMethodOverrides()) {
//省略部分代碼
}
else {
//todo 像lookup replace等 需要用cglib 代理來實例化
// Must generate CGLIB subclass.
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
接下來就會通過CglibSubclassCreator來創建抽象類的子類了:
public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
//todo 根據beanDefinition類來創建子類 2021-1-14
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);
}
}
// SPR-10785: set callbacks directly on the instance instead of in the
// enhanced class (via the Enhancer) in order to avoid memory leaks.
Factory factory = (Factory) instance;
factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
return instance;
}
我們只要看裏面最重要的一句代碼就是:
factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
它會加入 LookupOverrideMethodInterceptor 這個攔截器,攔截器必然會有最重要的一個攔截方法:
//todo 調用方法的時候纔會進入這個intercept 2020-1-11
@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 {
//如果沒有beanName 那就根據方法返回類型來查找Bean
return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
this.owner.getBean(method.getReturnType()));
}
}
到這裏我們應該會豁然開朗,Spring在我們調用getFruit方法的時候實際上會攔截,隨後就進入這個方法中執行相應的邏輯,最終就是通過beanFactory的getBean方法返回我們在Xml配置的bean。
AutowiredAnnotationBeanPostProcessor
那麼@Lookup註解是什麼時候把方法放入beanDefinition的methodOverrides中的呢?答案就是AutowiredAnnotationBeanPostProcessor 的 推斷候選的構造函數階段 。
Spring在第一次通過構造函數創建Bean實例的時候會去推斷是否有合適的構造函數,如下代碼所示:
@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
throws BeanCreationException {
// Let's check for lookup methods here..
if (!this.lookupMethodsChecked.contains(beanName)) {
try {
ReflectionUtils.doWithMethods(beanClass, method -> {
Lookup lookup = method.getAnnotation(Lookup.class);
if (lookup != null) {
Assert.state(this.beanFactory != null, "No BeanFactory available");
LookupOverride override = new LookupOverride(method, lookup.value());
try {
///todo 把找到的方法加入 MethodOverrides
RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
mbd.getMethodOverrides().addOverride(override);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(beanName,
"Cannot apply @Lookup to beans without corresponding bean definition");
}
}
});
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
}
this.lookupMethodsChecked.add(beanName);
}
//省略部分代碼
}
在這裏它會獲取方法上的Lookup註解,隨後就把獲取到的註解值,也就是bean名稱,還有當前方法放入methodOverrides。之後就會跟Xml配置方式一樣會通過 getInstantiationStrategy().instantiate(mbd, beanName, parent) 方法實例化Bean並且走同樣的邏輯了。