1. 问题描述
Client
实例。
实现方法:通过 @Configuration
注解来生命配置类,然后用 @Bean("clientx")
标记的方法来返回实例,每个返回 bean 的方法都通过 @Value
注解获取具体参数。
但是即使申明了不同的 name
,自动注入的时候还是传入了同一个值。
2. 代码实例
首先构造一个 Client
类:
package com.avaloninc.domain;
import lombok.Data;
public class Client {
private String endPoint;
private String accessKey;
private String accessSecret;
private String region;
}
然后给定我们的配置类:
package com.avaloninc.factory;
import com.avaloninc.domain.Client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class ClientConfiguration {
"client1")
( public Client getClient( ("${endPoint.beijing}") String endPoint) {
log.info("client1 with one parameter is called.");
Client client = new Client();
client.setEndPoint(endPoint);
return client;
}
"client2")
( public Client getClient( ("${endPoint.shanghai}") String endPoint,
"${region}") String region) {
( log.info("client2 with two parameters is called.");
Client client = new Client();
client.setEndPoint(endPoint);
client.setRegion(region);
return client;
}
}
然后单元测试:
package com.avaloninc.factory;
import com.avaloninc.domain.Client;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
SpringRunner.class)
(
public class BeanFactoryTest extends TestCase {
"client1")
( private Client client1;
"client2")
( private Client client2;
public void test() {
assertEquals(client1, client2);
}
}
单元测试完成运行我们得到日志:
2018-03-18 00:35:36.611 INFO 90857 --- [ main] c.a.factory.ClientConfigurationTest : Starting ClientConfigurationTest on MacBookPro.lan with PID 90857 (started by wuzhiyu in /Users/wuzhiyu/Projects/manuscripts/spring-bean-factory-overload) 2018-03-18 00:35:36.612 INFO 90857 --- [ main] c.a.factory.ClientConfigurationTest : No active profile set, falling back to default profiles: default 2018-03-18 00:35:36.692 INFO 90857 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@561b6512: startup date [Sun Mar 18 00:35:36 CST 2018]; root of context hierarchy 2018-03-18 00:35:37.520 INFO 90857 --- [ main] c.avaloninc.factory.ClientConfiguration : client2 with two parameters is called. 2018-03-18 00:35:37.523 INFO 90857 --- [ main] c.avaloninc.factory.ClientConfiguration : client2 with two parameters is called. 2018-03-18 00:35:38.011 INFO 90857 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext@561b6512: startup date [Sun Mar 18 00:35:36 CST 2018]; root of context hierarchy
可以看到两个参数的方法被调用了两次,所以实际上两个不同名字的 bean 却拥有完全相同的内容。
有同事建议我试试 @Resource
注解来注入 bean。他的理由是 @Autowired
一般是通过类型来匹配 bean。所以加了如下代码:
name = "client3")
( public Client getClient( ("${endPoint.hangzhou}") String endPoint,
"${accessKey}") String accessKey,
( "${accessSecret}") String accessSecret) {
( log.info("client3 with three parameters is called.");
Client client = new Client();
client.setEndPoint(endPoint);
client.setAccessKey(accessKey);
client.setAccessSecret(accessSecret);
return client;
}
以及修改单元测试:
name = "client3")
( private Client client3;
结果依然类似:
2018-03-18 00:45:07.757 INFO 90937 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@385e9564: startup date [Sun Mar 18 00:45:07 CST 2018]; root of context hierarchy 2018-03-18 00:45:08.585 INFO 90937 --- [ main] c.avaloninc.factory.ClientConfiguration : client3 with three parameters is called. 2018-03-18 00:45:08.589 INFO 90937 --- [ main] c.avaloninc.factory.ClientConfiguration : client3 with three parameters is called. 2018-03-18 00:45:08.590 INFO 90937 --- [ main] c.avaloninc.factory.ClientConfiguration : client3 with three parameters is called. 2018-03-18 00:45:09.078 INFO 90937 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext@385e9564: startup date [Sun Mar 18 00:45:07 CST 2018]; root of context hierarchy
3. 转机
但是通过日志发现,两个场景每个 bean 在构造的时候都采用了最后一个方法。在这里做了一个假设:Spring
在构造 bean 的时候采用了反射的方式,而且可能因为某些原因对于重载函数只使用最后一个同名的函数。
假设之后小心求证一下,再次修改一下代码。首先增加新的构造方法:
"client4")
( public Client getClientFour( ("${endPoint.shanghai}") String endPoint,
"${region}") String region,
( "${accessKey}") String accessKey,
( "${accessSecret}") String accessSecret) {
( log.info("client4 with four parameters is called.");
Client client = new Client();
client.setEndPoint(endPoint);
client.setRegion(region);
client.setAccessKey(accessKey);
client.setAccessSecret(accessSecret);
return client;
}
然后在单元测试中注入:
name = "client4")
( private Client client4;
通过日志我们发现新的构造方法构造的实例与之前的发生了区别:
2018-03-18 00:57:52.552 INFO 91033 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@8c3b9d: startup date [Sun Mar 18 00:57:52 CST 2018]; root of context hierarchy 2018-03-18 00:57:53.369 INFO 91033 --- [ main] c.avaloninc.factory.ClientConfiguration : client3 with three parameters is called. 2018-03-18 00:57:53.372 INFO 91033 --- [ main] c.avaloninc.factory.ClientConfiguration : client3 with three parameters is called. 2018-03-18 00:57:53.373 INFO 91033 --- [ main] c.avaloninc.factory.ClientConfiguration : client3 with three parameters is called. 2018-03-18 00:57:53.374 INFO 91033 --- [ main] c.avaloninc.factory.ClientConfiguration : client4 with four parameters is called. 2018-03-18 00:57:53.811 INFO 91033 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext@8c3b9d: startup date [Sun Mar 18 00:57:52 CST 2018]; root of context hierarchy
4. 结论
虽然没有阅读 Spring
的源码,但是大致可以想见 Spring
在构造实例时对于返回不同 name 的 bean 的重载方法处理的时候只用最后一个同名方法。也就是说只用了函数的名字而不是完整的函数签名。
因此,下次在配置类中返回相同类型不同名字的实例时还是避免使用相同的函数名!