欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring中bean id相同引发故障的分析与解决

程序员文章站 2024-03-02 17:23:10
前言 最近因为同事bean配置的问题导致生产环境往错误的redis实例写入大量的数据,差点搞挂redis。经过快速的问题定位,发现是同事新增一个redis配置文件,并且配...

前言

最近因为同事bean配置的问题导致生产环境往错误的redis实例写入大量的数据,差点搞挂redis。经过快速的问题定位,发现是同事新增一个redis配置文件,并且配置的redissentinelconfiguration的id是一样的,然后在使用@autowired注入bean的时候因为spring bean覆盖的机制导致读取的redis配置不是原来的。

总结起来,有两点问题:

  • 为什么相同bean id的bean会被覆盖
  • @autowired注解不是按照bytype的方式进行注入的吗

代码如下:

public class userconfiguration {

 private int id;

 private string name;

 private string city;

 public int getid() {
  return id;
 }

 public void setid(int id) {
  this.id = id;
 }

 public string getname() {
  return name;
 }

 public void setname(string name) {
  this.name = name;
 }

 public string getcity() {
  return city;
 }

 public void setcity(string city) {
  this.city = city;
 }
}

userclient:

public class userclient {

 private userconfiguration configuration;

 public userclient(userconfiguration configuration) {
  this.configuration = configuration;
 }

 public string getcity() {
  return configuration.getcity();
 }

}

beans.xml:

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
  xsi:schemalocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd">

 <bean id="userconfiguration" class="com.rhwayfun.springboot.starter.rest.userconfiguration">
  <property name="id" value="${user1.id}"/>
  <property name="name" value="${user1.name}"/>
  <property name="city" value="${user1.city}"/>
 </bean>

 <bean id="userclient" class="com.rhwayfun.springboot.starter.rest.userclient" autowire="byname">
  <constructor-arg ref="userconfiguration"/>
 </bean>

</beans>

beans2.xml:

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
  xsi:schemalocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd">

 <bean id="userconfiguration" class="com.rhwayfun.springboot.starter.rest.userconfiguration">
  <property name="id" value="${user2.id}"/>
  <property name="name" value="${user2.name}"/>
  <property name="city" value="${user2.city}"/>
 </bean>

 <bean id="userclient2" class="com.rhwayfun.springboot.starter.rest.userclient">
  <constructor-arg ref="userconfiguration"/>
 </bean>

</beans>

application.properties:

user1.id=1
user1.name=bean1
user1.city=hangzhou

user2.id=2
user2.name=bean2
user2.city=shanghai

applition:

@springbootapplication
public class application{

 @autowired
 userclient userclient2;

 @postconstruct
 public void init() {
  string city = userclient2.getcity();
  system.out.println(city);
 }

 public static void main(string[] args) throws interruptedexception {
  springapplication.run(application.class, args);
  thread.sleep(long.max_value);
 }

}

运行程序,你会发现不管注入的userclient2还是userclient1,输出的结果都是shanghai。但是我们想实现的是,注入userclient1的时候输出的应该是hangzhou,注入userclient2的时候输出的应该是shanghai。这也是导致开头说的问题的源头所在。要实现这个效果很简单,userconfiguration换一个名字就可以了。

但是,为什么换个名字就可以了呢,不同spring配置文件相同bean id的bean为什么不会分别创建呢?原因就在于spring 对具有相同bean id的实例做了覆盖处理。你可以理解为一个map,key是bean id,value就是class,那么当两次put相同id的bean的时候自然就被覆盖了。

我们先回忆下bean的生命周期:

  1. 实例化
  2. 填充属性
  3. 调用beannameaware的setbeanname方法
  4. 调用beanfactoryaware的setbeanfactory方法
  5. 调用applicationcontextaware的setapplicationcontext方法
  6. 调用beanpostprocessor的预初始化方法
  7. 调用initializingbean的afterpropertiesset方法
  8. 调用自定义的初始化方法
  9. 调用beanpostprocessor的初始化方法
  10. 实例化完毕

问题出在注册bean定义的时候,我们可以控制台看到以下输出

overriding bean definition for bean 'userconfiguration' with a 
different definition: replacing [generic bean: class 
[com.rhwayfun.springboot.starter.rest.userconfiguration]; scope=; 
abstract=false; lazyinit=false; autowiremode=0; 
dependencycheck=0; autowirecandidate=true; primary=false; 
factorybeanname=null; factorymethodname=null; initmethodname=null; 
destroymethodname=null; 
defined in file [/users/chubin/ideaprojects/spring-boot-learning-examples/
spring-boot-starter-rest/target/classes/beans.xml]] with 
[generic bean: class [com.rhwayfun.springboot.starter.rest.userconfiguration]; 
scope=; abstract=false; lazyinit=false; autowiremode=0; dependencycheck=0; 
autowirecandidate=true; primary=false; factorybeanname=null; 
factorymethodname=null; initmethodname=null; destroymethodname=null; defined in 
file [/users/chubin/ideaprojects/spring-boot-learning-examples
/spring-boot-starter-rest/target/classes/beans2.xml]]

就是说beans.xml中配置的userconfiguration被beans2.xml配置的userconfiguration实例覆盖了。那么自然我们得到的结果是shanghai了。

spring bean覆盖

经过上面的分析,我们已经知道是因为被覆盖的导致的,那么怎么体现的呢?遇到解决不了的问题,看源码往往能得到答案:

spring中bean id相同引发故障的分析与解决

spring中bean id相同引发故障的分析与解决

这段代码的逻辑就是,如果不允许具有相同bean id的实例存在就抛出异常,而这个值默认是true,也就是允许存在相同的bean id定义。

@autowired注解实现机制

bean覆盖的问题解决了,那么还有一个问题,为什么使用@autowired注入userclient没有报错呢,明明配置了两个类型的bean啊。@autowired不是按照bytype注入的吗。

你确定吗?不完全正确。

因为@autowired是spring提供的注解,我们可以看到是如何注入的代码,在autowiredannotationbeanpostprocessor.autowiredmethodelement.inject()方法中。

1.解析依赖

spring中bean id相同引发故障的分析与解决

2.获取候选bean、决定最终被被注入的最优bean

spring中bean id相同引发故障的分析与解决

3.最优bean的决策过程:1)判断时候有@primary注解;2)如果没有,得到最高优先级的bean,也就是是否有实现了org.springframework.core.ordered接口的bean(优先级比较,可以通过注解@order(0)指定,数字越小,优先级越高);3)如果仍然没有,则根据属性名装配

spring中bean id相同引发故障的分析与解决

优先级定义:

/**
  * useful constant for the highest precedence value.
  * @see java.lang.integer#min_value
  */
 int highest_precedence = integer.min_value;

 /**
  * useful constant for the lowest precedence value.
  * @see java.lang.integer#max_value
  */
 int lowest_precedence = integer.max_value;

至此,我们就能理解为什么@autowired能够通过属性名注入不同的bean了。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。