网站首页 > 编程文章 正文
遇舟 大淘宝技术
2024年07月22日 18:18 浙江
最近在某个项目的开发过程中,遇到了一个bean注入不生效的问题,本文主要针对该问题进行展开,欢迎大家共同探讨。
背景
该项目涉及到两个应用,其中一个应用A需要给另一个应用B打一个胖客户端(Fat Client),该胖客户端的代码在两个应用中都有调用。胖客户端内需要配置一个tair bean,但是该bean配置在A中生效,但是在B中却没有生效。
相关知识点
还记得Spring是怎么配置bean的吗?在Spring中总体来看可以通过三种方式来配置对象:
- 使用XML文件配置
- 使用注解来配置
- 使用JavaConfig来配置
注解自动装配
- @Resource和@Autowire
@Autowired: 用于构造器、方法、参数或字段上,表明需要自动注入一个Bean。Spring会自动装配匹配的Bean。
@Qualifier: 与@Autowired一起使用时,指定要注入的Bean的名称,以避免与其他Bean的混淆。
@Resource: 来自JDK,类似于@Autowired,但默认是按名称装配,也可以混合使用。
@Inject: 来自javax.inject包,类似于@Autowired,属于JSR-330标准的一部分。
@Resource和@Autowired注解有什么区别呢?
@Autowired 是Spring框架提供的注解,主要用于根据类型自动装配依赖项。
行为和特性:
- 按类型装配:默认情况下,@Autowired按类型自动装配Bean。
- 可选依赖:如果你的依赖是可选的,可以使用required=false设置:
- 构造器、方法或字段:可以用在构造器,属性字段或Setter方法上。
- 结合@Qualifier:可以和@Qualifier结合使用以实现按名称装配。
- 作为Spring特有的注解,它更深度地集成在Spring的生态系统中,更适合与其他Spring注解一起使用。
@Resource 是JDK提供的注解,属于Java依赖注入规范(JSR-250)的一部分。
行为和特性:
- 按名称装配:默认情况下,@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代码来配置bean呢?
- 编写一个java类,使用@Configuration修饰该类
- 被@Configuration修饰的类就是配置类
以本次用到的bean配置为例:
@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;
}
@Bean
public TairAccessor tmgXxxTairAccessor(@Qualifier("tmgXxxTairManager") 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/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://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>
问题描述
为什么我的bean配置没生效?
最早,在胖客户端中,是通过Java配置类配置的tair的bean。最早的代码如下:
@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;
}
@Bean
public 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依赖注入优先级
原来是TairManager注入的不是我们在配置类中配置的tmgXxxTairManager?!
可是tmgXxxTairManager也是一个bean,不应该也是唯一的吗,难道这里不是按照bean名字注入的吗?
那这里依赖注入的优先级是什么呢?
- @Resource
在使用 @Resource 注解进行依赖注入时,优先级规则如下:
明确指定名称:
- 如果通过 @Resource(name="beanName") 明确指定了 Bean 的名称,那么 Spring 会首先按照名称匹配进行注入。
- 在这种情况下,@Primary 注解不会影响注入结果。
按字段或属性名称匹配:
- 如果没有通过 name 属性指定 Bean 的名称,Spring 会尝试按照字段或属性的名称进行匹配。
- 在这种情况下,@Primary 注解也不会影响注入结果。
按类型匹配:
- 如果按名称匹配失败(包括明确指定名称和按字段名称匹配都没有找到合适的 Bean),Spring 会按类型匹配。
- 在这种情况下,如果存在多个同类型的 Bean,则 @Primary 注解会起作用,标记为 @Primary 的 Bean 将被优先注入。
- @Autowired
按类型匹配:
- 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方法中的参数
Spring 框架在处理 @Bean 方法中的参数时,默认的行为与 @Autowired 注解的工作方式是一致的。
例如本文中涉及到的tmgXxxTairManager参数,注入的并不一定是上面定义的tmgXxxTairManager bean
@Bean
public TairAccessor tmgXxxTairAccessor(TairManager tmgXxxTairManager) {
TairAccessorImpl tairAccessor = new TairAccessorImpl();
tairAccessor.setTairManager(tmgXxxTairManager);
tairAccessor.setNamespace(namespace);
return tairAccessor;
}
解决方案
xml配置bean
在使用 XML 配置 Bean 时,ref 元素通常是用来引用其他已经定义的 Bean,并且是通过 Bean 的 id 来进行引用和注入的。这种方法使得在 XML 配置的 Spring 应用程序中可以灵活地管理和注入依赖。
这也解释了为什么中间改成用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/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://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;
}
@Bean
public TairAccessor tmgXxxTairAccessor(@Qualifier("tmgXxxTairManager") TairManager tmgXxxTairManager) {
TairAccessorImpl tairAccessor = new TairAccessorImpl();
tairAccessor.setTairManager(tmgXxxTairManager);
tairAccessor.setNamespace(namespace);
return tairAccessor;
}
}
结语
在使用配置类配置 Bean 时,@Bean 方法的参数,或者用@Autowired配置 Bean 时,最好使用@Qualifier 指定注入的bean,避免注入的bean不符合预期。@Resource则通常不存在这种烦恼。
团队介绍
我们是天猫国际前台技术团队,致力于通过技术能力解决人、货、场之间的高效匹配问题,持续为消费者打造优秀的进口商品购物体验。我们始终关注用户的真实需求和反馈,不断探索和应用新技术,和各个团队紧密合作,为用户提供更加智能、便捷的购物体验,将天猫国际打造成为全球消费者信赖的跨境购物平台。
猜你喜欢
- 2025-05-09 Spring Boot3 RESTful 接口参数校验,这篇吃透就够了!
- 2025-05-09 《Spring6》第02节:基于XML方式搭建Spring6框架开发环境
- 2025-05-09 MapStruct架构设计(mapstruct @mapping)
- 2025-05-09 分布式微服务架构组件(分布式微服务架构设计)
- 2025-05-09 Java Swing组件下的JButton实例(java swing 组件)
- 2025-05-09 java基础都在这了,小主们拿去吧(java基础是指什么)
- 2025-05-09 AOP的实现落地(拦截过滤),一切都要从Servlet说起
- 2025-05-09 【Spring Boot】WebSocket 的 6 种集成方式
- 2025-05-09 Java 中五种最常见加密算法:原理、应用与代码实现
- 2025-05-09 用注解进行参数校验,spring validation介绍、使用、实现原理分析
你 发表评论:
欢迎- 05-09Spring Boot3 RESTful 接口参数校验,这篇吃透就够了!
- 05-09《Spring6》第02节:基于XML方式搭建Spring6框架开发环境
- 05-09MapStruct架构设计(mapstruct @mapping)
- 05-09分布式微服务架构组件(分布式微服务架构设计)
- 05-09Java Swing组件下的JButton实例(java swing 组件)
- 05-09java基础都在这了,小主们拿去吧(java基础是指什么)
- 05-09AOP的实现落地(拦截过滤),一切都要从Servlet说起
- 05-09【Spring Boot】WebSocket 的 6 种集成方式
- 最近发表
-
- Spring Boot3 RESTful 接口参数校验,这篇吃透就够了!
- 《Spring6》第02节:基于XML方式搭建Spring6框架开发环境
- MapStruct架构设计(mapstruct @mapping)
- 分布式微服务架构组件(分布式微服务架构设计)
- Java Swing组件下的JButton实例(java swing 组件)
- java基础都在这了,小主们拿去吧(java基础是指什么)
- AOP的实现落地(拦截过滤),一切都要从Servlet说起
- 【Spring Boot】WebSocket 的 6 种集成方式
- Java 中五种最常见加密算法:原理、应用与代码实现
- 用注解进行参数校验,spring validation介绍、使用、实现原理分析
- 标签列表
-
- spire.doc (59)
- system.data.oracleclient (61)
- 按键小精灵源码提取 (66)
- pyqt5designer教程 (65)
- 联想刷bios工具 (66)
- c#源码 (64)
- graphics.h头文件 (62)
- mysqldump下载 (66)
- sqljdbc4.jar下载 (56)
- libmp3lame (60)
- maven3.3.9 (63)
- 二调符号库 (57)
- 苹果ios字体下载 (56)
- git.exe下载 (68)
- diskgenius_winpe (72)
- pythoncrc16 (57)
- solidworks宏文件下载 (59)
- qt帮助文档中文版 (73)
- satacontroller (66)
- hgcad (64)
- bootimg.exe (69)
- android-gif-drawable (62)
- axure9元件库免费下载 (57)
- libmysqlclient.so.18 (58)
- springbootdemo (64)
本文暂时没有评论,来添加一个吧(●'◡'●)