Spring编程常见错误50例-Spring Bean依赖注入常见错误(下)

news2024/9/24 3:29:35

@Value没有注入预期的值

问题

对于@Value可以装配多种类型的数据:

  • 装配对象:
@Value("#{student}")
private Student student;

@Bean
public Student student(){
    Student student = createStudent(1, "xie");
    return student;
}
  • 装配字符串:
@Value("我是字符串")
private String text;
  • 注入系统参数、环境变量或者配置文件中的值:
@Value("${ip}")
private String ip
  • 注入其他Bean属性:
@Value("#{student.name}")  // student是bean的ID
private String name;

但是使用该注解时遇到以下场景会出现问题:在控制器类中引用配置类中的属性时部分值返回错误

username=admin
password=pass
@RestController
@Slf4j
public class ValueTestController {
    @Value("${username}")
    private String username;

    @Value("${password}")
    private String password;

    @RequestMapping(path = "user", method = RequestMethod.GET)
    public String getUser(){
       return username + ","  + ", " + password;  // username返回的是运行这段程序的计算机用户名,password能正确返回
    };

}

原因

从下面代码中可以看到@Value的工作分为三个核心步骤:

// DefaultListableBeanFactory#doResolveDependency
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
                                  @Nullable Set<String> autowiredBeanNames, 
                                  @Nullable TypeConverter typeConverter) throws BeansException {
    // ...
        // ①寻找@Value
        Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
        if (value != null) {
            // ②解析value值
            if (value instanceof String) {
                String strVal = resolveEmbeddedValue((String) value);
                BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
                value = evaluateBeanDefinitionString(strVal, bd);
            }
            // ③转化Value解析的结果到装配的类型
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            try {
                return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
            }
            catch (UnsupportedOperationException ex) {
                ...
            }
        }

    // ...
}
  • 寻找@Value:判断属性字段是否标记为@Value
// QualifierAnnotationAutowireCandidateResolver#findValue
@Nullable
protected Object findValue(Annotation[] annotationsToSearch) {
    if (annotationsToSearch.length > 0) {   // qualifier annotations have to be local
        AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
            // valueAnnotationType即为@Value
            AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
        if (attr != null) {
            return extractValue(attr);
        }
    }
    return null;
}
  • 解析@Value的字符串值:如果字段标记了@Value,则可拿到对应的字符串值,然后就可以根据字符串值去做解析,解析结果可能是字符串,也可能是对象

  • 将解析结果转化为要装配的对象的类型:

分析完对应的步骤后,可以定位到问题原因在解析@Value指定字符串过程中,对${xxx}的查找不局限在application.properties,而是针对多个源,这些源在启动时被有序固定,所以在查找时也是按序查找的。当查找到systemEnvironment时发有个username和配置文件中的重合
在这里插入图片描述

// PropertySourcesPropertyResolver#getProperty
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        for (PropertySource<?> propertySource : this.propertySources) {
            if (logger.isTraceEnabled()) {
                logger.trace("Searching for key '" + key + "' in PropertySource '" +
                             propertySource.getName() + "'");
            }
            Object value = propertySource.getProperty(key);
            if (value != null) {
                if (resolveNestedPlaceholders && value instanceof String) {
                    value = resolveNestedPlaceholders((String) value);
                }
                logKeyFound(key, propertySource, value);
                return convertValueIfNecessary(value, targetValueType);
            }
        }
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Could not find key '" + key + "' in any property source");
    }
    return null;
}

解决方式

避免存在与系统或环境变量有同名的配置

myname=admin
password=pass

错乱的注入集合

问题

假设存在多个学生Bean,需找出来并存储到List里并在控制器类中输出:

// 可以理解为收集方式
@Bean
public Student student1(){
    return createStudent(1, "psj1");
}

@Bean
public Student student2(){
    return createStudent(2, "psj2");
}

private Student createStudent(int id, String name) {
    Student student = new Student();
    student.setId(id);
    student.setName(name);
    return student;
}
private List<Student> students;

public StudentController(List<Student> students){
    this.students = students;
}

@RequestMapping(path = "students", method = RequestMethod.GET)
public String listStudents(){
    return students.toString();
};

此时需要再增加学生Bean,换了一种方式注入集合类型:

// 可以理解为直接装配方式
@Bean
public List<Student> students(){
    Student student3 = createStudent(3, "psj3");
    Student student4 = createStudent(4, "psj4");
    return Arrays.asList(student3, student4);
}

但上述两种方式都存在,只会输出前面两个学生

原因

  • 进行第一种装配方式时(即收集方式),主要分为以下过程:
// DefaultListableBeanFactory#resolveMultipleBeans
@Nullable
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
                                    @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
    final Class<?> type = descriptor.getDependencyType();

    if (descriptor instanceof StreamDependencyDescriptor) {
        ...
        // 装配stream
        return stream;
    }
    else if (type.isArray()) {
        ...
        // 装配数组
        return result;
    }
    else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
        // 装配集合
        // 获取集合的元素类型
        Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
        if (elementType == null) {
            return null;
        }
        // 根据元素类型查找所有的bean
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
                                                                   new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        // 转化查到的所有bean放置到集合并返回
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        ...
        return result;
    }
    else if (Map.class == type) {
        ...
        // 解析map
        return matchingBeans;
    }
    else {
        return null;
    }
}
  • 进行第二种装配方式时(即直接装配方式),具体过程在DefaultListableBeanFactory#findAutowireCandidates
  • 当同时满足这两种装配方式时,从下面代码中可以看出它们是不能共存的:
// DefaultListableBeanFactory#doResolveDependency
// 采用收集方式
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
    return multipleBeans;
}
// 上述方式不执行才会执行直接装配方式
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);

解决方式

对于同一个集合对象的注入不要混合多种注入方式

参考

极客时间-Spring 编程常见错误 50 例

https://github.com/jiafu1115/springissue/tree/master/src/main/java/com/spring/puzzle/class3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1019773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Nuxt3】Vue3 + Element-plus 打包后报错 @popperjs/core

问题&#xff1a; 更新 Element-plus 后&#xff0c;运行时需要安装 popperjs/core 依赖。 如果正常执行 npm install popperjs/core &#xff0c;那么&#xff0c;打包时&#xff0c;就会出现下面报错。 Named export ‘placements’ not found. The requested module ‘poppe…

Python | 为FastAPI后端服务添加API Key认证(分别基于路径传参和header两种方式且swagger文档友好支持)

文章目录 01 前言02 路径传参方式添加API Key2.1 完整代码2.2 请求示例2.3 swagger文档测试 03 请求头Header方式传入API Key&#xff08;推荐&#xff09;3.1 完整代码3.2 请求示例3.3 swagger文档测试 01 前言 FastAPI&#xff0c;如其名所示&#xff0c;是一个极为高效的框…

云计算的未来:云原生架构和自动化运维的崭露头角

文章目录 云计算的演进云原生架构1. 容器化2. 微服务3. 自动化部署和扩展4. 故障恢复 自动化运维1. 基础设施即代码&#xff08;IaC&#xff09;2. 运维自动化示例&#xff1a;使用Ansible自动化配置管理 3. 自动化监控和报警 未来展望1. 更多的自动化2. 多云混合云3. 边缘计算…

华为+苹果的“科技春晚”背后,“自主创新+实在技术”遥遥领先!

过去的24小时里&#xff0c;科技春晚迎来“双主角”&#xff1a;12日下午&#xff0c;华为发布会&#xff0c;13日凌晨&#xff0c;苹果发布会。 尽管苹果一向以其创新和高端的形象而闻名&#xff0c;但与昔日苹果发布会后有关新品的词条霸屏微博热搜不同&#xff0c;当天在发…

初步了解华为的MTL(市场到线索)流程的基本概念和来龙去脉

前两天&#xff0c;有读者给华研荟发私信&#xff0c;说在学习华为资料的时候看到华为有一个MTL流程&#xff0c;想了解下这个MTL流程和LTC流程有什么区别&#xff1f;既然有了LTC流程&#xff0c;为什么还要MTL流程呢&#xff1f; 为此&#xff0c;今天华研荟给大家简要介绍华…

让电子制造厂提高生产率的方法,学会受用终生!

在现代工业领域&#xff0c;工厂的生产运营离不开复杂的设备和关键的基础设施。然而&#xff0c;工厂在日常运营中常常面临着各种风险&#xff0c;其中之一就是水浸事件。 水浸监控不仅仅是一种反应性的措施&#xff0c;更是一种预防性的安全策略。通过使用高度先进的传感技术和…

前端使用H5中draggable实现拖拽排序效果 两种实现效果

文章目录 一、实现效果①1、实现代码2、效果演示 二、实现效果②1.实现代码2.效果演示 一、实现效果① 将一个节点拖到另一个节点之前或之后 1、实现代码 <!DOCTYPE html> <style>* {padding: 0;margin: 0;}body {display: flex;width: 100%;height: 100vh;just…

Matlab中关于 : 的使用

设&#xff0c;mat 这个矩阵的规格是 n*m&#xff0c;temp mat( i , j ) 矩阵的行和列的下标从1开始 在这个矩阵中&#xff0c;a:b 代表的含义是范围是从 a--b 则&#xff0c;当 a 和 b 被省略时&#xff0c;代表的范围就是最大范围&#xff08;1--n&#xff09; or &#…

卡奥斯第二届1024程序员节重磅预告!

一场属于程序员的狂欢! 第二届卡奥斯1024程序员节重磅来袭。 提前做好活动攻略&#xff0c;欢欢喜喜大奖抱回家&#xff01; 本次活动设置4个活动分会场: 低代码分会场、开源分会场、知识分会场和产品分会场&#xff0c;共12个奖项&#xff0c;1100多个奖品&#xff0c;雷神9…

IP归属地在金融行业的应用场景

IP归属地查询在各大行业当中的利用率可以说非常的高了&#xff0c;提供了各种的保障&#xff0c;比如安全保障、数据保障、性能保障等等。今天我就来详细说一说IP归属地在金融行业的应用场景有哪些&#xff1f; 用途一&#xff1a;通过解析用户IP地址所处的区县位置与表单填写位…

Re-Learn Linux Part1

1. Linux的目录结构 在Linux文件系统中有两个特殊的目录&#xff1a; 一个用户所在的工作目录&#xff0c;也叫当前目录&#xff0c;可以使用一个点 . 来表示&#xff1b;另一个是当前目录的上一级目录&#xff0c;也叫父目录&#xff0c;可以使用两个点 .. 来表示。 . &#…

关于激光探测器光斑质心算法在FPGA硬件的设计

目录 0引言 1CCD采集图像质心算法 2基于FPGA的图像质心算法 3仿真结果与分析 4结论 0引言 在一些姿态检测的实际应用中&#xff0c;需要在被测对象上安装激光探测器[1]&#xff0c;利用CCD相机捕捉激光光斑来检测观测对象的实际情况&#xff0c;光斑图像质心坐标的提取是图…

机器学习——SVM(支持向量机)

0、前言&#xff1a; SVM应用&#xff1a;主要针对小样本数据进行学习、分类和回归&#xff08;预测&#xff09;&#xff0c;能解决神经网络不能解决的过学习问题&#xff0c;有很好的泛化能力。&#xff08;注意&#xff1a;SVM算法的数学原理涉及知识点比较多&#xff0c;所…

grep多行匹配以及一些问题

测试文本, a.txt 123 456789这里是简单的文本 使用grep多行匹配 grep -Pzo "123\s456" a.txt-P: 启用Perl正则表达式模式。 -z: 允许多行匹配&#xff0c;即使匹配跨越了换行符的行。 -o: 只输出匹配的部分。 这里能匹配到 123 456但是有的时候也匹配不到&#…

表演复读生的王炸班型——薪火表演·独角兽班开课介绍

我们拥有同样的目标——大院名校 薪火独角兽班计划 最懂复读生的地方 不想上大课&#xff0c;只想1对1? 录制费用高&#xff0c;不愿增加父母经济压力? 稿件烂大街? 专业没人管? 这些都不是问题! 一站式解决复读生难题 选薪火独角兽班&#xff01; ---------ifire.ar…

利用前端和后端技术,海豚物流实现高效物流管理系统

随着信息技术的快速发展&#xff0c;前端和后端技术在物流行业中扮演着越来越重要的角色。海豚物流充分利用前后端技术&#xff0c;实现了物流管理的无缝协作&#xff0c;大大提升了运输效率和客户满意度。 前端技术在物流管理中扮演着用户界面的角色。通过优化用户界面&#x…

【产品运营】你真的懂B端大客户吗?来试试这8个棘手的需求问题

在与B端客户交流的过程中&#xff0c;有很多需要注意的问题&#xff0c;在产品的不同风格阶段&#xff0c;客户都会提出很多需求&#xff0c;而对于客户的需求产品经理需要有判断以及解决的能力&#xff1b; 本文主要讨论做需求时的棘手问题&#xff0c;在职责上与项目经理有些…

crypto++下载、安装(VS2017)及加解密使用

crpto 下载按个人喜好下载&#xff0c;我使用了图中框选的8.8.0 Release.解压 安装打开修改以适应本机配置整理至标准库 调用加解密使用 Crypto&#xff08;也称为Crypto Library或Crypto STL&#xff09;是一个C密码学库&#xff0c;它提供了各种密码学算法和安全编程工具&…

20230917后台面经总结

1.ping底层原理 Ping 是 ICMP 的一个重要应用&#xff0c;主要用来测试两台主机之间的连通性。Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文&#xff0c;目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。 基…

vue项目 高德地图搜索带关键字效果demo(整理)

<!-- 高德地图引入 --> <script type"text/javascript">window._AMapSecurityConfig {securityJsCode: be00dfb4bcd4b18dd7760486c40aa1ed, //秘钥} </script> <!-- <script type"text/javascript" src"./qrcode.js"&g…