DependsOn注解失效问题排查

news2024/11/27 16:52:36

文章目录

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


前言

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


一、现象描述

1.1.背景描述

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

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

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 {

    @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();
    }
}

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

二、看看源码

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;
    }

    @Override
    public Object getObject() throws Exception {
        return proxyClass.newInstance();
    }

    @Override
    public 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 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.
				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> {
    @Override
    public ProxyService3 getObject() throws Exception {
        return ProxyService3.class.newInstance();
    }

    @Override
    public Class<?> getObjectType() {
        return ProxyService3.class;
    }
}

然后我们重写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 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都不会被调用实例化。我们运行一下看看:
实验结果
结果实验结果我们可以发现,也就是当放入到spring容器中的对象类型不明确时,就会被调用,不管它是不是Lazy的,而且调用的时候也是直接通过反射调用该方法,不处理其DependsOn注解

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1070268.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

强化学习框环境 - robogym - 学习 - 4

强化学习环境 - robogym - 学习 - 4 文章目录 强化学习环境 - robogym - 学习 - 4项目地址为什么选择 robogym如何消去目标位置的阴影&#xff1f;如何让物体颜色变得正确&#xff1f; 项目地址 https://github.com/openai/robogym 为什么选择 robogym 自己的项目需要做一些机…

小白自学笔记—网络安全(黑客笔记)

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟入…

29 WEB漏洞-CSRF及SSRF漏洞案例讲解

目录 CSRF漏洞解释&#xff0c;原理等CSRF漏洞检测&#xff0c;案例&#xff0c;防御等防御方案2、设置随机Token3、检验referer来源 SSRF漏洞会比csrf漏洞重要一些SSRF_PHP&#xff0c;JAVA漏洞代码协议运用演示案例:SSRF_漏洞代码结合某漏洞利用测试 如何查找ssrf漏洞 SSRF漏…

测量温度的优选模块:新型设备M-THERMO3 16

| 具有16个自由选择通道的新型温度测量设备M-THERMO3 16 IPETRONIK推出的温度测量设备——M-THERMO3 16作为新一代设备的首个模块&#xff0c;它为模块化测量技术确立了标准。该模块具有16个通道&#xff0c;各通道不仅分辨率高达24位ADC&#xff0c;而且能够自由选择热电偶类…

2023Q3数据安全政策、法规、标准及报告汇总(附下载)

数据安全处罚事件逐年升高&#xff0c;2023年呈爆发式增长。 截至2023年8月31日&#xff0c;南都大数据研究院通过各地行政执法公示平台、媒体报道等公开渠道收集到146起依据《数据安全法》作出行政处罚决定的案例。2021年公示5起&#xff0c;2022年公示11起&#xff0c;2023年…

如何通过设备维护管理系统实现全员生产维护TPM

前面我们介绍了《什么是全员生产维护TPM&#xff1f;》&#xff0c;接下来我们探讨如何结合PreMaint的设备维护管理系统来实现全员生产维护TPM。 在现代制造业中&#xff0c;设备的可靠性和生产效率对企业的竞争力至关重要。为了实现全员生产维护&#xff08;Total Productive …

记录vue开发实例

封装的表格组件 <template><div><div style"width: 100%" v-if"showList"><el-table v-loading.lock"loading" :data"dataList":header-cell-style"{background: #F2FCFE,fontSize: 14px,color: #50606D}&…

8.2 JUC - 6.CyclicBarrier

目录 一、是什么&#xff1f;二、使用demo三、注意 一、是什么&#xff1f; CyclicBarrier &#xff1a; 循环栅栏&#xff0c;用来进行线程协作&#xff0c;等待线程满足某个计数。构造时设置计数个数&#xff0c;每个线程执行到某个需要“同步”的时刻调用 await() 方法进行…

【数据结构】栈和队列-- OJ

目录 一 用队列实现栈 二 用栈实现队列 三 设计循环队列 四 有效的括号 一 用队列实现栈 225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09; typedef int QDataType; typedef struct QueueNode {struct QueueNode* next;QDataType data; }QNode;typedef struct …

数据结构 | (三) Stack

栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作 。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO &#xff08; Last In First Out &#xff09;的原则。 压栈&#xff1a;栈…

c#学习系列相关之多线程(三)----invoke和begininvoke

一、invoke和BeginInvoke的作用 invoke和begininvoke方法的初衷是为了解决在某个非某个控件创建的线程中刷新该控件可能会引发异常的问题。说的可能比较拗口&#xff0c;举个例子&#xff1a;主线程中存在一个文本控件&#xff0c;在一个子线程中要改变该文本的值&#xff0c;此…

(四)列表、元组、字典和集合

Python列表&#xff08;list&#xff09;、元组&#xff08;tuple&#xff09;、字典&#xff08;dict&#xff09;和集合&#xff08;set&#xff09;详解 Python 序列&#xff08;Sequence&#xff09;是指按特定顺序依次排列的一组数据&#xff0c;它们可以占用一块连续的内…

【C/C++】关于vector迭代器失效问题

​&#x1f47b;内容专栏&#xff1a; C/C编程 &#x1f428;本文概括&#xff1a; vector迭代器失效问题 &#x1f43c;本文作者&#xff1a; 阿四啊 &#x1f438;发布时间&#xff1a;2023.10.8 迭代器的主要作用就是让算法能够不用关心底层数据结构&#xff0c;其底层实际就…

C++变量默认初始化

初始化不是赋值&#xff0c;初始化是指创建变量时赋予一个初始值&#xff0c;赋值是指将变量的当前值擦除&#xff0c;赋予新值。 如果定义变量时没有初始化&#xff0c;则变量会被系统默认初始化。“默认值”取决于变量的&#xff1a;类型位置 startmindmap * C变量默认初始…

邮件群发工具哪个好

邮件群发是一种通过电子邮件向多个收件人发送邮件的方式。同时&#xff0c;邮件群发也是一种低成本、高回报的营销手段。因此邮件群发被广泛应用于各种营销活动中&#xff0c;例如活动邀请、新品上线、产品促销等等。而群发邮件最有效的方式就是借助邮件群发工具&#xff0c;而…

常用排序算法详解

1.冒泡排序原理示例代码实现 2.快速排序原理示例代码实现 3.插入排序原理示例代码实现 4.希尔排序原理示例代码实现 5.选择排序原理示例代码实现 6.堆排序原理示例代码实现 7.归并排序原理示例代码实现 本文讲述了常见的排序算法的执行过程&#xff0c;有详细实现过程举例 1.冒…

arm 点灯实验代码以及现象

.text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R0,0x50000A28 LDR R1,[R0] ORR R1,R1,#(0x1<<4) 第4位置1 STR R1,[R0] 1.设置GPIOF寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R…

自动定时删除磁盘文件的脚本(从文件日期最早的开始删)

#!/bin/bash# 指定的挂载点 MOUNTPOINT"/media/vm/MyDisk512GB"# 设置磁盘大小的限制 (例如&#xff1a;800G) LIMIT$((800 * 1024 * 1024)) # 单位是KB# 获取挂载点的已使用空间 USED_SPACE$(df -kP "$MOUNTPOINT" | tail -1 | awk {print $3})echo &quo…

强化学习------Qlearning算法

简介 Q learning 算法是一种value-based的强化学习算法&#xff0c;Q是quality的缩写&#xff0c;Q函数 Q(state&#xff0c;action)表示在状态state下执行动作action的quality&#xff0c; 也就是能获得的Q value是多少。算法的目标是最大化Q值&#xff0c;通过在状态state下…

day58:ARMday5,GPIO流水灯实验

汇编指令&#xff1a; .text .global _start _start: 1.设置GPIOE GPIOF寄存器的时钟使能 RCC_MP_AHB4ENSETR[5:4]->1 0x50000a28 LDR R0,0x50000a28 LDR R1,[R0] ORR R1,R1,#(0x3<<4) STR R1,[R0]2.设置PE10、PF10、PE8管脚为输出模式&#xff0c;GPIOE_MODER[21…