又写bug了?我的bean配置居然没生效?
阿里妹导读
一、背景
二、相关知识点
使用XML文件配置 使用注解来配置 使用JavaConfig来配置
注解自动装配
@Resource和@Autowired
行为和特性:
按类型装配:默认情况下,@Autowired按类型自动装配Bean。
可选依赖:如果你的依赖是可选的,可以使用required=false设置:
构造器、方法或字段:可以用在构造器,属性字段或Setter方法上。
结合@Qualifier:可以和@Qualifier结合使用以实现按名称装配。
作为Spring特有的注解,它更深度地集成在Spring的生态系统中,更适合与其他Spring注解一起使用。
按名称装配:默认情况下,@Resource按名称装配。如果没有匹配到名称,再按类型装配。
不支持required属性:与@Autowired不同,@Resource不支持required属性。
可以用于字段和Setter方法:虽然也可以用于构造器,但不常见。通常用在字段或Setter方法上。
由于是Java EE规范的一部分,它可以与其他Java EE注解(如@PostConstruct和@PreDestroy)更好地配合使用。
@Component: 标注一个类为Spring管理的组件。类似的注解还有:
@Service: 表示服务层组件。
@Repository: 表示DAO(数据访问层)组件。
@Controller: 表示Spring MVC控制器组件。
@Primary: 当一个类型有多个Bean时,在不使用@Qualifier的情况下,Spring会优先选择标注了@Primary的Bean。
@Scope: 用于指定Bean的作用域,如singleton、prototype。
Java配置类
编写一个java类,使用@Configuration修饰该类
被@Configuration修饰的类就是配置类
@Configuration@Slf4j(topic = "config")public class XxxTairConfig {@Value("${spring.tmg.xxx.tair.username:默认值}")private String username;@Value("${spring.tmg.xxx.tair.namespace:默认值}")private Integer namespace;@Bean(initMethod = "init")public TairManager tmgXxxTairManager() {MultiClusterTairManager tairManager = new MultiClusterTairManager();tairManager.setUserName(username);tairManager.setDynamicConfig(true);return tairManager;}@Beanpublic TairAccessor tmgXxxTairAccessor(@Qualifier("tmgXxxTairManager") TairManager tmgXXXTairManager){TairAccessorImpl tairAccessor = new TairAccessorImpl();tairAccessor.setTairManager(tmgXxxTairManager);tairAccessor.setNamespace(namespace);return tairAccessor;}}
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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><bean id="tmgXxxTairManager" class="com.taobao.tair.impl.mc.MultiClusterTairManager" init-method="init"><property name="userName"><value>tair用户名参数</value></property><property name="timeout"><value>50</value></property></bean><bean id="tmgXxxTairAccessor" class="com.alibaba.tmallg.gcommon.tair.TairAccessorImpl"><property name="tairManager" ref="tmgXxxTairManager"/><property name="namespace" value="tair namespace参数"/></bean></beans>
三、问题描述
@Configuration@Slf4j(topic = "config")public class XxxTairConfig {@Value("${spring.tmg.xxx.tair.username:默认值}")private String username;@Value("${spring.tmg.xxx.tair.namespace:默认值}")private Integer namespace;@Bean(initMethod = "init")public TairManager tmgXxxTairManager() {MultiClusterTairManager tairManager = new MultiClusterTairManager();tairManager.setUserName(username);tairManager.setDynamicConfig(true);return tairManager;}@Beanpublic TairAccessor tmgXxxTairAccessor(TairManager tmgXxxTairManager){TairAccessorImpl tairAccessor = new TairAccessorImpl();tairAccessor.setTairManager(tmgXxxTairManager);tairAccessor.setNamespace(namespace);return tairAccessor;}}
在应用A中,该配置没有问题,tair也可以正常查询。
但是部署到应用B后,奇怪的问题出现了:同样的key,应用A可以查到,tair控制台也可以查到,但是应用B死活查不出来。
四、排查
排查过程
难道是因为username的@Value默认值没生效?因为debug可以看到namespace属性,符合预期,但是username属性看不到,我最早怀疑是username没配置成功。但namespace是生效的,理论上不应该部分不生效呀?抱着试试看的态度,遂尝试将username直接写死成目标值。重新部署后,依然不行,看来不是@Value的锅。
中间我尝试将java配置bean的方式,改成用xml配置,这样是生效的,可以解决问题。
但是为什么java配置bean的方式不生效呢?
还原后,继续debug,感觉依旧有些摸不着头脑。后来请团队同学帮忙看了下,一开始也觉得奇怪,tair各个单元都是有数据的,和单元化也没关系,后来点开tairManager属性值看到mdbcomm,问了句,我们的tair用的是mdb吗?最后打开控制台一看,是ldb……
Spring依赖注入优先级
明确指定名称:
如果通过@Resource(name="beanName") 明确指定了 Bean 的名称,那么 Spring 会首先按照名称匹配进行注入。
在这种情况下,@Primary 注解不会影响注入结果。
按字段或属性名称匹配:
如果没有通过 name 属性指定 Bean 的名称,Spring 会尝试按照字段或属性的名称进行匹配。
在这种情况下,@Primary 注解也不会影响注入结果。
按类型匹配:
如果按名称匹配失败(包括明确指定名称和按字段名称匹配都没有找到合适的 Bean),Spring 会按类型匹配。
在这种情况下,如果存在多个同类型的 Bean,则 @Primary 注解会起作用,标记为 @Primary 的 Bean 将被优先注入。
按类型匹配:
Spring 首先通过类型匹配找到所有符合要求的候选 Bean。如果只有一个候选 Bean,那么该 Bean 会被注入。
按名称匹配结合 @Qualifier:
如果有多个同类型的 Bean,可以使用 @Qualifier 注解来指定具体的 Bean。
@Qualifier 的值必须与一个候选 Bean 的名称匹配,匹配成功的 Bean 会被注入。
使用 @Primary:
如果仍存在多个符合要求的 Bean,并且其中一个 Bean 标记了 @Primary,Spring 会优先选择标记了 @Primary 的 Bean 进行注入。
按名称匹配字段或属性名称:
在没有使用 @Qualifier 时,如果存在多个候选 Bean,Spring 会尝试通过字段或属性名称进行匹配。
如果找到名称匹配的 Bean,则该 Bean 会被注入。
NoUniqueBeanDefinitionException:
如果存在多个候选Bean,但没有使用@Qualifier指定名称,且没有标记@Primary,会抛出NoUniqueBeanDefinitionException,表明有多个Bean类型匹配但无法确定注入哪个。
@Bean方法中的参数。
@Beanpublic TairAccessor tmgXxxTairAccessor(TairManager tmgXxxTairManager) {TairAccessorImpl tairAccessor = new TairAccessorImpl();tairAccessor.setTairManager(tmgXxxTairManager);tairAccessor.setNamespace(namespace);return tairAccessor;}
五、解决方案
xml配置bean
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><bean id="tmgXxxTairManager" class="com.taobao.tair.impl.mc.MultiClusterTairManager" init-method="init"><property name="userName"><value>tair用户名参数</value></property><property name="timeout"><value>50</value></property></bean><bean id="tmgXxxTairAccessor" class="com.alibaba.tmallg.gcommon.tair.TairAccessorImpl"><property name="tairManager" ref="tmgXxxTairManager"/><property name="namespace" value="tair namespace参数"/></bean></beans>
java配置bean
通过@Qualifier指定bean,避免受@Primary等因素的影响。
@Configuration@Slf4j(topic = "config")public class XxxTairConfig {@Value("${spring.tmg.xxx.tair.username:默认值}")private String username;@Value("${spring.tmg.xxx.tair.namespace:默认值}")private Integer namespace;@Bean(initMethod = "init")public TairManager tmgXxxTairManager() {MultiClusterTairManager tairManager = new MultiClusterTairManager();tairManager.setUserName(username);tairManager.setDynamicConfig(true);return tairManager;}@Beanpublic TairAccessor tmgXxxTairAccessor(@Qualifier("tmgXxxTairManager") TairManager tmgXxxTairManager) {TairAccessorImpl tairAccessor = new TairAccessorImpl();tairAccessor.setTairManager(tmgXxxTairManager);tairAccessor.setNamespace(namespace);return tairAccessor;}}
六、结语
团队介绍