全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

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

前言

最近因为同事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覆盖

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

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

@Autowired注解实现机制

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

你确定吗?不完全正确。

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

1.解析依赖

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

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

优先级定义:

/**
  * 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了。

总结

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


# spring  # bean  # id  # 重复  # 注解  # 使用监听器对Spring bean id进行唯一校验过程解析  # Spring实战之容器中的工程Bean用法示例  # Spring实战之抽象Bean和子Bean定义与用法示例  # Spring实战之调用实例工厂方法创建Bean操作示例  # Spring实战之使用静态工厂方法创建Bean操作示例  # Spring如何使用注解的方式创建bean  # Spring实战之注入嵌套Bean操作示例  # Java类获取Spring中bean的5种方式  # Spring的自动装配Bean的三种方式  # Spring实战之获得Bean本身的id操作示例  # 最优  # 配置文件  # 就可以  # 的是  # 都是  # 应该是  # 是因为  # 你可以  # 就能  # 两次  # 这段  # 我们可以  # 可以通过  # 很简单  # 如果没有  # 可以看到  # 还有一个  # 自定义  # 这篇文章  # 不完全 


相关文章: 网站制作新手教程,新手建设一个网站需要注意些什么?  网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?  详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)  如何快速查询网站的真实建站时间?  三星网站视频制作教程下载,三星w23网页如何全屏?  成都网站制作价格表,现在成都广电的单独网络宽带有多少的,资费是什么情况呢?  贸易公司网站制作流程,出口贸易网站设计怎么做?  如何通过IIS搭建网站并配置访问权限?  重庆市网站制作公司,重庆招聘网站哪个好?  如何快速搭建响应式可视化网站?  娃派WAP自助建站:免费模板+移动优化,快速打造专业网站  如何在Golang中实现微服务服务拆分_Golang微服务拆分与接口管理方法  香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化  如何用腾讯建站主机快速创建免费网站?  如何基于云服务器快速搭建个人网站?  如何正确选择百度移动适配建站域名?  微信h5制作网站有哪些,免费微信H5页面制作工具?  SAX解析器是什么,它与DOM在处理大型XML文件时有何不同?  无锡制作网站公司有哪些,无锡优八网络科技有限公司介绍?  ,sp开头的版面叫什么?  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  如何使用Golang table-driven基准测试_多组数据测量函数效率  制作门户网站的参考文献在哪,小说网站怎么建立?  孙琪峥织梦建站教程如何优化数据库安全?  简历在线制作网站免费,免费下载个人简历的网站是哪些?  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  岳西云建站教程与模板下载_一站式快速建站系统操作指南  如何用西部建站助手快速创建专业网站?  建站之星后台密码遗忘如何找回?  如何确保FTP站点访问权限与数据传输安全?  已有域名和空间,如何快速搭建网站?  专业企业网站设计制作公司,如何理解商贸企业的统一配送和分销网络建设?  网页设计与网站制作内容,怎样注册网站?  php json中文编码为null的解决办法  如何选择高效可靠的多用户建站源码资源?  魔毅自助建站系统:模板定制与SEO优化一键生成指南  php条件判断怎么写_ifelse和switchcase的使用区别【对比】  赚钱网站制作软件,建一个网站怎样才能赚钱?是如何盈利的?  ,南京靠谱的征婚网站?  建站之星如何助力企业快速打造五合一网站?  视频网站app制作软件,有什么好的视频聊天网站或者软件?  打鱼网站制作软件,波克捕鱼官方号怎么注册?  如何通过可视化优化提升建站效果?  如何通过虚拟机搭建网站?详细步骤解析  胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?  济南企业网站制作公司,济南社保单位网上缴费步骤?  html制作网站的步骤有哪些,iapp如何添加网页?  红河网站制作公司,红河事业单位身份证如何上传?  专业公司网站制作公司,用什么语言做企业网站比较好?  网站专业制作公司有哪些,做一个公司网站要多少钱? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。