前言
最近因为同事bean配置的问题导致生产环境往错误的redis实例写入大量的数据,差点搞挂redis。经过快速的问题定位,发现是同事新增一个redis配置文件,并且配置的redissentinelconfiguration的id是一样的,然后在使用@autowired注入bean的时候因为spring bean覆盖的机制导致读取的redis配置不是原来的。
总结起来,有两点问题:
- 为什么相同bean id的bean会被覆盖
- @autowired注解不是按照bytype的方式进行注入的吗
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class userclient {
private userconfiguration configuration;
public userclient(userconfiguration configuration) {
this .configuration = configuration;
}
public string getcity() {
return configuration.getcity();
}
}
|
beans.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?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:
1
2
3
4
5
6
7
|
user1.id= 1
user1.name=bean1
user1.city=hangzhou
user2.id= 2
user2.name=bean2
user2.city=shanghai
|
applition:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@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的生命周期:
- 实例化
- 填充属性
- 调用beannameaware的setbeanname方法
- 调用beanfactoryaware的setbeanfactory方法
- 调用applicationcontextaware的setapplicationcontext方法
- 调用beanpostprocessor的预初始化方法
- 调用initializingbean的afterpropertiesset方法
- 调用自定义的初始化方法
- 调用beanpostprocessor的初始化方法
- 实例化完毕
问题出在注册bean定义的时候,我们可以控制台看到以下输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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)如果仍然没有,则根据属性名装配
优先级定义:
1
2
3
4
5
6
7
8
9
10
11
|
/**
* 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了。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://blog.csdn.net/u011116672/article/details/78074246