又写bug了?我的bean配置居然没生效?

简介: 最近在某个项目的开发过程中,遇到了一个bean注入不生效的问题,本文主要针对该问题进行展开,欢迎大家共同探讨。

一、背景

该项目涉及到两个应用,其中一个应用A需要给另一个应用B打一个胖客户端(Fat Client),该胖客户端的代码在两个应用中都有调用。胖客户端内需要配置一个tair bean,但是该bean配置在A中生效,但是在B中却没有生效。

image.png

二、相关知识点

还记得Spring是怎么配置bean的吗?在Spring中总体来看可以通过三种方式来配置对象:


  • 使用XML文件配置
  • 使用注解来配置
  • 使用JavaConfig来配置


注解自动装配


@Resource和@Autowired


@Autowired: 用于构造器、方法、参数或字段上,表明需要自动注入一个Bean。Spring会自动装配匹配的Bean。


@Qualifier: 与@Autowired一起使用时,指定要注入的Bean的名称,以避免与其他Bean的混淆。


@Resource: 来自JDK,类似于@Autowired,但默认是按名称装配,也可以混合使用。


@Inject: 来自javax.inject包,类似于@Autowired,属于JSR-330标准的一部分。


@Resource和@Autowired注解有什么区别呢?


@Autowired 是Spring框架提供的注解,主要用于根据类型自动装配依赖项。


行为和特性:

  1. 按类型装配:默认情况下,@Autowired按类型自动装配Bean。
  2. 可选依赖:如果你的依赖是可选的,可以使用required=false设置:
  3. 构造器、方法或字段:可以用在构造器,属性字段或Setter方法上。
  4. 结合@Qualifier:可以和@Qualifier结合使用以实现按名称装配。
  5. 作为Spring特有的注解,它更深度地集成在Spring的生态系统中,更适合与其他Spring注解一起使用。


@Resource 是JDK提供的注解,属于Java依赖注入规范(JSR-250)的一部分。行为和特性:

  1. 按名称装配:默认情况下,@Resource按名称装配。如果没有匹配到名称,再按类型装配。
  2. 不支持required属性:与@Autowired不同,@Resource不支持required属性。
  3. 可以用于字段和Setter方法:虽然也可以用于构造器,但不常见。通常用在字段或Setter方法上。
  4. 由于是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死活查不出来。

image.png


四、排查


排查过程

难道是因为username的@Value默认值没生效?因为debug可以看到namespace属性,符合预期,但是username属性看不到,我最早怀疑是username没配置成功。但namespace是生效的,理论上不应该部分不生效呀?抱着试试看的态度,遂尝试将username直接写死成目标值。重新部署后,依然不行,看来不是@Value的锅。


中间我尝试将java配置bean的方式,改成用xml配置,这样是生效的,可以解决问题。


但是为什么java配置bean的方式不生效呢?


还原后,继续debug,感觉依旧有些摸不着头脑。后来请团队同学帮忙看了下,一开始也觉得奇怪,tair各个单元都是有数据的,和单元化也没关系,后来点开tairManager属性值看到mdbcomm,问了句,我们的tair用的是mdb吗?最后打开控制台一看,是ldb……

image.png


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则通常不存在这种烦恼。


团队介绍


我们是天猫国际前台技术团队,致力于通过技术能力解决人、货、场之间的高效匹配问题,持续为消费者打造优秀的进口商品购物体验。我们始终关注用户的真实需求和反馈,不断探索和应用新技术,和各个团队紧密合作,为用户提供更加智能、便捷的购物体验,将天猫国际打造成为全球消费者信赖的跨境购物平台。







来源  |  阿里云开发者公众号

作者  | 遇舟

相关文章
|
数据采集 Java
自定义 ForkJoinPool 提升并行流 ParallelStream 执行速度
简介 在 java8 中 添加了流Stream,可以让你以一种声明的方式处理数据。使用起来非常简单优雅。ParallelStream 则是一个并行执行的流,采用 ForkJoinPool 并行执行任务,提高执行速度。
8579 1
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
6705 14
Spring Boot 3 集成 Spring Security + JWT
|
监控 Linux
Linux systemd 服务启动失败Main process exited, code=exited, status=203/EXEC
通过以上步骤,可以有效解决 systemd 服务启动失败并报错 `Main process exited, code=exited, status=203/EXEC` 的问题。关键在于仔细检查单元文件配置、验证可执行文件的有效性,并通过日志分析具体错误原因。确保可执行文件路径正确、文件具有执行权限,并且可以独立运行,将有助于快速定位和解决问题。
5640 7
|
11月前
|
安全 前端开发 Java
理解 Spring Security 的 HttpMethod 和路径匹配
本文深入解析了Spring Security中`HttpMethod`与路径匹配的使用方法及其常见问题。通过具体示例,阐明了如何针对不同HTTP方法(如GET、POST)和路径(如`/api/products`与`/products`)设置权限,避免用户绕过限制访问核心数据。同时,强调了配置顺序和角色前缀的重要性,并提供了最佳实践,帮助开发者精准控制应用的安全性。
320 1
|
XML JSON Java
springboot文件上传,单文件上传和多文件上传,以及数据遍历和回显
本文介绍了在Spring Boot中如何实现文件上传,包括单文件和多文件上传的实现,文件上传的表单页面创建,接收上传文件的Controller层代码编写,以及上传成功后如何在页面上遍历并显示上传的文件。同时,还涉及了`MultipartFile`类的使用和`@RequestPart`注解,以及在`application.properties`中配置文件上传的相关参数。
springboot文件上传,单文件上传和多文件上传,以及数据遍历和回显
|
消息中间件 JSON Java
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
32868 0
|
关系型数据库 MySQL 数据库
实时计算 Flink版操作报错之遇到报错org.postgresql.util.psqlexception: The connection attempt failed.,该怎么解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
Java UED Spring
Springboot通过SSE实现实时消息返回
通过Spring Boot实现SSE,可以简单高效地将实时消息推送给客户端。虽然SSE有其限制,但对于许多实时消息推送场景而言,它提供了一种简洁而强大的解决方案。在实际开发中,根据具体需求选择合适的技术,可以提高系统的性能和用户体验。希望本文能帮助你深入理解Spring Boot中SSE的实现和应用。
6850 1
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
本文介绍了如何在Spring Boot项目中集成Swagger 2.x和3.0版本,并提供了解决Swagger在Spring Boot中启动失败问题“Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerEx”的方法,包括配置yml文件和Spring Boot版本的降级。
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
|
Java Maven Spring
【SpringBug】lombok插件失效,但是没有报错信息,@Data不能生成get和set方法
解决写了@Data注解,但是在测试文件中生成的反编译target文件Us二Info中没有get和set方法
1161 16

热门文章

最新文章