前言
这篇文章我们就来具体使用下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并且走同样的逻辑了。