DependsOn注解失效问题排查

2023-10-09 03:28

本文主要是介绍DependsOn注解失效问题排查,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、现象描述
    • 1.1.背景描述
    • 1.2.第一次修改,使用DependsOn注解
    • 1.3.第二次修改,设置方法入参
  • 二、看看源码
    • 2.1.Spring实例化的源码
    • 2.2.调试
    • 2.3.验证
  • 总结


前言

最近几天遇到一个比较有意思的问题,发现Spring的DependsOn注解失效,令我大为费解。经过一段排查,还是有收获的,记录下来,自己警醒,也给大家避雷。
为了去掉敏感信息,本文所有代码均为示例,并不是实际线上代码!!!


一、现象描述

1.1.背景描述

我们实例化某个对象时,需要从配置中心热加载里获取某个属性,使得对象初始化时获取配置中心的数据,形如:

@Configuration
public class ProxyConfig {@Beanpublic 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环境验证了一下,可以读取到配置中心的值,就美滋滋的上线了。
结果,线上验收时,没有生效!!!!

1.2.第一次修改,使用DependsOn注解

猜测了下原因,因为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环境验证了一下,可以读取到配置中心的值,就美滋滋的上线了。
结果,不出意外的情况下又出意外,线上验收时,没有生效!!!!

1.3.第二次修改,设置方法入参

这次我修改了proxyService1方法的入参,强制执行时使用myConfig对象

@Configuration
public class ProxyConfig {@Beanpublic 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();}
}

所幸这次成功了
但是,这是为啥呢?

二、看看源码

2.1.Spring实例化的源码

众所周知,按照咱们的理解,Spring不管怎样,要基于某个definition实例化某个对象的时候,都需要调用AbstractBeanFactory下的getBean方法,最终会调用doGetBean方法,此处为了显眼,我们就直接放截图,不相干的逻辑暂时折叠:
DependsOn注解生效逻辑
实在找不到原因,只好调试源码了。

2.2.调试

我们给proxyService1方法增加断点,发现比较奇怪的是,其他实例进行属性注入的时候,会调用该方法。进一步查看,发现它是通过SimpleInstantiationStrategy类里的instantiate方法进行实例化的,没有调用getBean方法,也就没有解析DependsOn注解
FactoryMethod实例化
FactoryMethod是指加了@Bean注解的方法

那么问题来了,其他service相互注入,并不是注入proxyService1,为什么会调用该factoryMethod呢???
看看调用栈来剖析下吧~
让我们回顾这两篇文章:

  1. @Resource注解的逻辑
  2. @Autowired注解的逻辑
    不管是使用@Resource注解还是@Autowired注解,多数情况都会进入到根据类型进行注入,可以看上面两篇博客。
    不过在上面两篇博客,我们没有详细分析,根据类型查找可用对象的逻辑,也就是findAutowireCandidates方法中的
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());

不过,我们先stop一下,如果我们是Spring的开发者,我们该如何来写这个根据类型查找可用实例的逻辑???

3. 普通加了@Service或者@Component注解的类型,直接用它的类型
4. 加了@Bean的FactoryMethod的方法,根据方法返回值的类型

但是如果是FactoryBean呢???
恰恰我们的MyProxy类就是一个FactoryBean

public class MyProxy implements FactoryBean {private Class<?> proxyClass;public void setProxyClass(Class<?> proxyClass) {this.proxyClass = proxyClass;}@Overridepublic Object getObject() throws Exception {return proxyClass.newInstance();}@Overridepublic Class<?> getObjectType() {return proxyClass;}
}
  1. 加了@Service、@Component注解,那我们应该会是根据FactoryBean里的泛型或者接口方法getObjectType来判断类型
  2. @Bean的返回值是FactoryBean,那我们根据方法返回值的泛型来判断。
    感性认知的话,我们都会这么干,那我们看看Spring是怎么干的,而且像我们MyProxy这种没有指定FactoryBean的泛型的实现,Spring又是如何处理的呢?
在DefaultListableBeanFactory#doGetBeanNamesForType方法里,遍历所有的beanDefinition,查找beanDefinition的类型
在AbstractBeanFactory的isTypeMatch方法,调用了getTypeForFactoryBean

我们具体来读一下getTypeForFactoryBean方法的逻辑

protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {// ....不重要的逻辑先忽略// Consider factory methodsString factoryBeanName = mbd.getFactoryBeanName();String factoryMethodName = mbd.getFactoryMethodName();// Scan the factory bean methodsif (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()是个nullresult = 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() earlyif (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.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注解的处理逻辑。

2.3.验证

为了验证上述逻辑,我们新增一个指定了FactoryBean泛型的类ProxyFactoryBean

public class ProxyFactoryBean implements FactoryBean<ProxyService3> {@Overridepublic ProxyService3 getObject() throws Exception {return ProxyService3.class.newInstance();}@Overridepublic Class<?> getObjectType() {return ProxyService3.class;}
}

然后我们重写ProxyConfig类

@Configuration
public class ProxyConfig {@Bean@Lazypublic MyProxy proxyService1() {System.out.println("init proxyService1");MyProxy proxy = new MyProxy();proxy.setProxyClass(ProxyService1.class);return proxy;}@Bean@Lazypublic ProxyFactoryBean proxyService3() {System.out.println("init proxyService3");return new ProxyFactoryBean();}@Bean@Lazypublic MyRealService myRealService() {System.out.println("init myRealService");return new MyRealService();}}

我们的三个对象都没有作为其他对象的属性注入,且都加了@Lazy。理论上应该所有FactoryMethod都不会被调用实例化。我们运行一下看看:
实验结果
结果实验结果我们可以发现,也就是当放入到spring容器中的对象类型不明确时,就会被调用,不管它是不是Lazy的,而且调用的时候也是直接通过反射调用该方法,不处理其DependsOn注解

总结

尽量不要使得放入到spring容器中的对象类型不明确!!!
但是有时候也不可避免,譬如本人发现该类型的案例是由于公司的rpc组件导致的,利用不明确的FactoryBean实现类,设置不同的对象,来进行rpc的proxy对象生成。

这篇关于DependsOn注解失效问题排查的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/170003

相关文章

Pyserial设置缓冲区大小失败的问题解决

《Pyserial设置缓冲区大小失败的问题解决》本文主要介绍了Pyserial设置缓冲区大小失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录问题描述原因分析解决方案问题描述使用set_buffer_size()设置缓冲区大小后,buf

resultMap如何处理复杂映射问题

《resultMap如何处理复杂映射问题》:本文主要介绍resultMap如何处理复杂映射问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录resultMap复杂映射问题Ⅰ 多对一查询:学生——老师Ⅱ 一对多查询:老师——学生总结resultMap复杂映射问题

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3

如何解决mmcv无法安装或安装之后报错问题

《如何解决mmcv无法安装或安装之后报错问题》:本文主要介绍如何解决mmcv无法安装或安装之后报错问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mmcv无法安装或安装之后报错问题1.当我们运行YOwww.chinasem.cnLO时遇到2.找到下图所示这里3.

浅谈配置MMCV环境,解决报错,版本不匹配问题

《浅谈配置MMCV环境,解决报错,版本不匹配问题》:本文主要介绍浅谈配置MMCV环境,解决报错,版本不匹配问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录配置MMCV环境,解决报错,版本不匹配错误示例正确示例总结配置MMCV环境,解决报错,版本不匹配在col

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La