最近几天遇到一个比较有意思的问题,发现Spring的DependsOn注解失效,令我大为费解。经过一段排查,还是有收获的,记录下来,自己警醒,也给大家避雷。
为了去掉敏感信息,本文所有代码均为示例,并不是实际线上代码!!!
Java:Java8;Spring:Spring5.3.7
我们实例化某个对象时,需要从配置中心热加载里获取某个属性,使得对象初始化时获取配置中心的数据,形如:
public class MyProxy implements FactoryBean {
private Class<?> proxyClass;
public void setProxyClass(Class<?> proxyClass) {
this.proxyClass = proxyClass;
}
@Override
public Object getObject() throws Exception {
return proxyClass.newInstance();
}
@Override
public Class<?> getObjectType() {
return proxyClass;
}
}
@Configuration
public class ProxyConfig {
@Bean
public MyProxy proxyService1() {
System.out.println("init proxyService1");
MyProxy proxy = new MyProxy();
proxy.setProxyClass(ProxyService1.class);
proxy.setInfo(MyConfig.info);
return proxy;
}
@Bean(name = "myConfig", initMethod = "init", destroyMethod = "destroy")
public MyConfig myConfig() {
return new MyConfig();
}
}
myConfig实例化之后会调用init方法,从远程的配置中心拉取配置信息,将MyConfig类里的变量值进行设置;理想情况,在proxyService1()方法执行,并执行实例化的时候读取到的MyConfig.info应该是配置中心里配置的值,而不是MyConfig类里定义的info初始化值。
Test环境验证了一下,可以读取到配置中心的值,就美滋滋的上线了。
结果,线上验收时,没有生效!!!!
猜测了下原因,因为Spring实例化对象的顺序并不能保证每次运行都一致,也不能保证不同环境实例化对象顺序一致,所以线上应该是先执行了proxyService1(),后执行了myConfig();这样的话proxyService1()执行的时候读取不到配置中心配置的info的值。
猜测到原因之后,进行了以下的改动,加了DependsOn注解,强制这俩方法的运行顺序
@Configuration
public class ProxyConfig {
@Bean
@DependsOn("myConfig")
public MyProxy proxyService1() {
System.out.println("init proxyService1");
MyProxy proxy = new MyProxy();
proxy.setProxyClass(ProxyService1.class);
proxy.setInfo(MyConfig.info);
return proxy;
}
@Bean(name = "myConfig", initMethod = "init", destroyMethod = "destroy")
public MyConfig myConfig() {
return new MyConfig();
}
}
Test环境验证了一下,可以读取到配置中心的值,就美滋滋的上线了。
结果,不出意外的情况下又出意外,线上验收时,没有生效!!!!
这次我修改了proxyService1方法的入参,强制执行时使用myConfig对象
@Configuration
public class ProxyConfig {
@Bean
public MyProxy proxyService1(MyConfig myConfig) {
System.out.println("init proxyService1");
MyProxy proxy = new MyProxy();
proxy.setProxyClass(ProxyService1.class);
proxy.setInfo(MyConfig.info);
return proxy;
}
@Bean(name = "myConfig", initMethod = "init", destroyMethod = "destroy")
public MyConfig myConfig() {
return new MyConfig();
}
}
所幸这次成功了
但是,这是为啥呢?
众所周知,按照咱们的理解,Spring不管怎样,要基于某个definition实例化某个对象的时候,都需要调用AbstractBeanFactory下的getBean方法,最终会调用doGetBean方法,此处为了显眼,我们就直接放截图,不相干的逻辑暂时折叠:
实在找不到原因,只好调试源码了。
我们给proxyService1方法增加断点,发现比较奇怪的是,其他实例进行属性注入的时候,会调用该方法。进一步查看,发现它是通过SimpleInstantiationStrategy类里的instantiate方法进行实例化的,没有调用getBean方法,也就没有解析DependsOn注解
FactoryMethod是指加了@Bean注解的方法
那么问题来了,其他service相互注入,并不是注入proxyService1,为什么会调用该factoryMethod呢???
看看调用栈来剖析下吧~
让我们回顾这两篇文章:
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
不过,我们先stop一下,如果我们是Spring的开发者,我们该如何来写这个根据类型查找可用实例的逻辑???
3. 普通加了@Service或者@Component注解的类型,直接用它的类型
4. 加了@Bean的FactoryMethod的方法,根据方法返回值的类型
但是如果是FactoryBean呢???
我们的MyProxy类就是一个FactoryBean!!!!
在DefaultListableBeanFactory#doGetBeanNamesForType方法里,遍历所有的beanDefinition,查找beanDefinition的类型
在AbstractBeanFactory的isTypeMatch方法,调用了getTypeForFactoryBean
我们具体来读一下getTypeForFactoryBean方法的逻辑
protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {
// ....不重要的逻辑先忽略
// Consider factory methods
String factoryBeanName = mbd.getFactoryBeanName();
String factoryMethodName = mbd.getFactoryMethodName();
// Scan the factory bean methods
if (factoryBeanName != null) {
if (factoryMethodName != null) {
// Try to obtain the FactoryBean's object type from its factory method
// declaration without instantiating the containing bean at all.
BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName);
Class<?> factoryBeanClass;
if (factoryBeanDefinition instanceof AbstractBeanDefinition &&
((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) {
factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass();
}
else {
RootBeanDefinition fbmbd = getMergedBeanDefinition(factoryBeanName, factoryBeanDefinition);
factoryBeanClass = determineTargetType(factoryBeanName, fbmbd);
}
if (factoryBeanClass != null) {
// 这个方法很重要,就是解析返回值是FactoryBean的方法,实际交给Spring容器的对象类型
// 而我们的MyProxy在实现FactoryBean时没有指定泛型,导致此处返回的result是个?
// 所以result.resolve()是个null
result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName);
if (result.resolve() != null) {
return result;
}
}
}
// If not resolvable above and the referenced factory bean doesn't exist yet,
// exit here - we don't want to force the creation of another bean just to
// obtain a FactoryBean's object type...
if (!isBeanEligibleForMetadataCaching(factoryBeanName)) {
return ResolvableType.NONE;
}
}
// If we're allowed, we can create the factory bean and call getObjectType() early
if (allowInit) {
FactoryBean<?> factoryBean = (mbd.isSingleton() ?
getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
if (factoryBean != null) {
// Try to obtain the FactoryBean's object type from this early stage of the instance.
// 执行了FactoryBean的方法,获取到FactoryBean的实例后,再调用其getObjectType获取真正的返回类型
Class<?> type = getTypeForFactoryBean(factoryBean);
if (type != null) {
return ResolvableType.forClass(type);
}
// No type found for shortcut FactoryBean instance:
// fall back to full creation of the FactoryBean instance.
return super.getTypeForFactoryBean(beanName, mbd, true);
}
}
//不重要的逻辑先忽略
}
可以看到上面的注释写的是先根据getTypeForFactoryBeanFromMethod查找返回值是FactoryBean时方法的实际要交给Spring容器管理对象的类型,如果查找不到,会调用getSingletonFactoryBeanForTypeCheck方法进行FactoryMethod的实例化。也就是会最终调用SimpleInstantiationStrategy类里的instantiate方法,而不是getBean方法进行实例化,也就忽略了DependsOn注解的处理逻辑。
为了验证上述逻辑,我们新增了指定了FactoryBean泛型的类ProxyFactoryBean和可以在方法调用时指定FactoryBean泛型的MyFactoryBean
public class ProxyFactoryBean implements FactoryBean<ProxyService3> {
@Override
public ProxyService3 getObject() throws Exception {
return ProxyService3.class.newInstance();
}
@Override
public Class<?> getObjectType() {
return ProxyService3.class;
}
}
public class MyFactoryBean<T> implements FactoryBean<T> {
private Class<T> proxyClass;
public void setProxyClass(Class<T> proxyClass) {
this.proxyClass = proxyClass;
}
@Override
public T getObject() throws Exception {
return proxyClass.newInstance();
}
@Override
public Class<T> getObjectType() {
return proxyClass;
}
}
然后我们重写ProxyConfig类
@Configuration
public class ProxyConfig {
@Bean
@Lazy
public MyProxy proxyService1() {
System.out.println("init proxyService1");
MyProxy proxy = new MyProxy();
proxy.setProxyClass(ProxyService1.class);
return proxy;
}
@Bean
@Lazy
// 注意看此处,指定了方法返回值的泛型
public MyFactoryBean<ProxyService2> proxyService2() {
System.out.println("init proxyService2");
MyFactoryBean<ProxyService2> proxy = new MyFactoryBean<>();
proxy.setProxyClass(ProxyService2.class);
return proxy;
}
@Bean
@Lazy
public ProxyFactoryBean proxyService3() {
System.out.println("init proxyService3");
return new ProxyFactoryBean();
}
@Bean
@Lazy
public MyRealService myRealService() {
System.out.println("init myRealService");
return new MyRealService();
}
}
我们的四个对象都没有作为其他对象的属性注入,且都加了@Lazy。理论上应该所有FactoryMethod都不会被调用实例化。我们运行一下看看:
同样是FactoryBean,不管是类型定义时指定了泛型的ProxyFactoryBean还是在方法实现时指定了方法返回值的MyFactoryBean,都没有被启动。只有MyProxy,在其他属性注入时会被强制启动。
如果我们把MyFactoryBean的方法返回值的泛型去掉呢???
@Bean
@Lazy
// 参见此处,我们虽然在方法实现里面指定了MyFactoryBean的proxyClass,但是方法返回值没有明确泛型
public MyFactoryBean proxyService2() {
System.out.println("init proxyService2");
MyFactoryBean<ProxyService2> proxy = new MyFactoryBean<>();
proxy.setProxyClass(ProxyService2.class);
return proxy;
}
可以看到,当FactoryMethod如果通过编译的class信息没有办法分析出FactoryBean返回的实际类型时,该FactoryMethod就会被执行,然后调用FactoryBean实例的getObjectType,以便让Spring确认其真实的返回值类型。
结果实验结果我们可以发现,也就是当放入到spring容器中的对象类型不明确时,就会被调用,不管它是不是Lazy的,而且调用的时候也是直接通过反射调用该方法,不处理其DependsOn注解
尽量不要使得放入到spring容器中的对象类型不明确!!!
但是有时候也不可避免,譬如本人发现该类型的案例是由于公司的rpc组件导致的,利用不明确的FactoryBean实现类,设置不同的对象,来进行rpc的proxy对象生成。