从零开始 Spring Boot 36:注入集合

news2024/11/27 5:40:01

从零开始 Spring Boot 36:注入集合

spring boot

图源:简书 (jianshu.com)

在前面一篇文章从零开始 Spring Boot 27:IoC中,讨论过依赖注入集合(Java 容器)的内容,这里更深入地讨论注入集合的相关内容。

我们来看一个最基本的集合注入示例:

public record BookCategory(String name) {
}

@Configuration
public class WebConfig {
    //文学
    //文学理论
    @Bean
    BookCategory literaryTheory() {
        return new BookCategory("literary theory");
    }

    //外国文学
    @Bean
    BookCategory foreignLiterature() {
        return new BookCategory("foreign literature");
    }

    //中国文学
    @Bean
    BookCategory chineseLiterature() {
        return new BookCategory("chinese literature");
    }

    //历史
    //中国历史
    @Bean
    BookCategory chineseHistory() {
        return new BookCategory("chinese history");
    }

    //外国历史
    @Bean
    BookCategory foreignHistory() {
        return new BookCategory("foreign history");
    }
}

@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    List<BookCategory> bookCategories;

    @GetMapping("/category/list")
    public Result<Object> listBookCategories() {
        System.out.println(bookCategories);
        return Result.success();
    }
}

运行示例会输出:

[BookCategory[name=literary theory], BookCategory[name=foreign literature], BookCategory[name=chinese literature], BookCategory[name=chinese history], BookCategory[name=foreign history]]

在这个示例中,@Autowired标记的是一个Java容器类型(List),而 Spring “智能地”用容器元素的类型BookCategory填充了一个List对象,并最终进行注入。

默认值

如果实际上并不存在任何容器元素的bean,会发生什么事?

比如删除配置类中的所有 bean 的工厂方法:

@Configuration
public class WebConfig {
	
}

程序将无法通过编译,报如下错误:

Field bookCategories in com.example.dicollections.controller.BookController required a bean of type 'java.util.List' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)

错误提示告诉我们无法完成依赖注入,因为缺少相关类型的 bean。可以看到,注入容器的时候,默认情况下,如果一个容器元素类型的 bean 都没有,就不会完成容器对象组装,并注入失败。

我们可以通过以下方式让这种情况发生时不报错:

public class BookController {
    @Autowired(required = false)
    List<BookCategory> bookCategories;
	// ...
}

此时bookCategories实际上并没有被注入,因此其值是Java对象初始化后的null

如果我们需要为其指定一个其它默认值,比如空列表,可以:

public class BookController {
    @Autowired(required = false)
    List<BookCategory> bookCategories = new ArrayList<>();
    // ...
}

要注意的是,如果是通过构造器注入,结果会有所不同,比如:

public class BookController {
    List<BookCategory> bookCategories;

    public BookController(List<BookCategory> bookCategories){
        this.bookCategories = bookCategories;
    }
	// ...
}

即使Spring 容器中没有任何可用于注入的 bean,bookCategories属性也会被一个空列表初始化。

但如果是通过Setter注入,就会报错,比如:

public class BookController {
    List<BookCategory> bookCategories;
    
    @Autowired
    public void setBookCategories(List<BookCategory> bookCategories) {
        this.bookCategories = bookCategories;
    }
    // ...
}

和之前的处理类似,如果要不报错和指定默认值,可以:

public class BookController {
    List<BookCategory> bookCategories = Collections.emptyList();

    @Autowired(required = false)
    public void setBookCategories(List<BookCategory> bookCategories) {
        this.bookCategories = bookCategories;
    }
    // ...
}

Spring 对 Setter 注入的处理是——如果缺少可以被注入的 bean,Setter 就不会被调用。

多个备选项

只存在容器元素类型的 bean 或只存在容器类型的 bean,注入的结果都是明确的,但是如果两者都存在,注入的结果会是什么?

看下面的示例:

@Configuration
public class WebConfig {
    //文学
    //文学理论
    @Bean
    BookCategory literaryTheory() {
        return new BookCategory("literary theory");
    }

  	// ...

    @Bean
    List<BookCategory> defaultBookCategories(){
        return List.of(chineseHistory(), foreignHistory());
    }
}

public class BookController {
    @Autowired(required = false)
    List<BookCategory> bookCategories = new ArrayList<>();
	// ...
}

输出:

[BookCategory[name=literary theory], BookCategory[name=foreign literature], BookCategory[name=chinese literature], BookCategory[name=chinese history], BookCategory[name=foreign history]]

注入的结果是使用容器元素类型的 bean 组装容器对象后注入,这种注入方式优先于容器类型的 bean

如果要用容器类型的 bean 完成注入,要怎么做?

可以使用@Resource实现:

public class BookController {
    @Resource(name = "defaultBookCategories")
    List<BookCategory> bookCategories;
	// ...
}

关于@Autowired@Resource的区别,可以阅读这篇文章。

@Value

可以用@Value从配置文件中“注入”容器,比如:

public class BookController {
    @Value("#{${book.categories}}")
    private List<String> categories;
    // ...
}

对应的配置文件:

book.categories={'literary theory','foreign literature','chinese literature','chinese history','foreign history'}

默认情况下我们只能通过这种方式生成常见类型的元素组成的容器,比如StringInteger等。这是因为Spring 是通过转换器(Converter)来处理字符串到相应类型的转换,而默认只包含一些基本类型的转换器。

换言之,如果我们需要处理自定义类型,想要从配置文件中将字符串形式的信息读取并创建我们需要的集合,可以建立相应的转换器并实现:

public class StringToBookCategoryConverter implements Converter<String, BookCategory> {
    @Override
    public BookCategory convert(String source) {
        return new BookCategory(source);
    }
}

@Configuration
public class MVCConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        WebMvcConfigurer.super.addFormatters(registry);
        registry.addConverter(new StringToBookCategoryConverter());
    }
}

@RestController
@RequestMapping("/book")
public class BookController {
    @Value("#{${book.categories}}")
    private List<BookCategory> bookCategories;
	// ...
}

关于转换器的相关内容,可以阅读我的另一篇文章。

@ConfigurationProperties

当然,同样可以借助@ConfigurationProperties从配置文件中读取并创建容器:

@Configuration
@ConfigurationProperties(prefix = "my.book.categories")
@Getter
@Setter
public class BookCategories {
    private List<BookCategory> list;
}

@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    private BookCategories bookCategories2;
    // ...
    @GetMapping("/category/list")
    public Result<Object> listBookCategories() {
        // ...
        System.out.println(bookCategories2.getList());
        return Result.success();
    }
}

对应的配置文件:

my.book.categories.list[0]=literary theory
my.book.categories.list[1]=foreign literature
my.book.categories.list[2]=chinese literature
my.book.categories.list[3]=chinese history
my.book.categories.list[4]=foreign history

同样的,这里使用到了前面示例中提到的自定义转换器StringToBookCategoryConverter。除此之外,也可以不使用自定义转换器,而是通过在配置文件中通过结构化语法指定每个对象的每个属性,比如:

my.book.categories.list[0].name=literary theory
my.book.categories.list[1].name=foreign literature
# ...

The End,谢谢阅读。

本文的所有示例代码,可以从这里获取。

参考资料

  • 从零开始 Spring Boot 27:IoC - 红茶的个人站点 (icexmoon.cn)
  • Spring - Injecting Collections | Baeldung
  • Wiring in Spring: @Autowired, @Resource and @Inject | Baeldung
  • Spring Boot 教程3:在 Spring Boot 中使用 application.yml 与 application.properties - 红茶的个人站点 (icexmoon.cn)
  • Inject Arrays & Lists from Spring Property Files | Baeldung
  • 从零开始 Spring Boot 29:类型转换 - 红茶的个人站点 (icexmoon.cn)

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

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

相关文章

ThinkPHP5学生学术管理系统

有需要请私信或看评论链接哦 可远程调试 ThinkPHP5学生学术管理系统 一 介绍 此学生学术管理系统基于ThinkPHP5框架开发&#xff0c;数据库mysql&#xff0c;前端Amazeui。系统角色分为学生用户和管理员。学生可以对个人信息&#xff0c;发表论文&#xff0c;专利授权&#x…

chatgpt赋能python:Python快速建站的SEO(搜索引擎优化)指南

Python快速建站的SEO&#xff08;搜索引擎优化&#xff09;指南 在当今数字时代&#xff0c;任何企业都需要一个强大和有效的网站。随着多个开源和商业网站平台的出现&#xff0c;建立一个网站变得更加容易。其中一个让人充满激情的开源工具是Python&#xff0c;它是一种流行的…

06_ MySQL优化实战

1. 计算并指定索引长度 阿里开发手册&#xff1a; 强制】在 varchar 字段上建立索引时&#xff0c;必须指定索引长度&#xff0c;没必要对全字段建立索引&#xff0c;根据实际文本区分度决定索引长度。 说明&#xff1a;索引的长度与区分度是一对矛盾体&#xff0c;一般对字符…

2.4 网络设计与redis、memcached、nginx组件

目录 一、网络模块需要处理哪些事情二、reactor网络设计模型三、网络模块与业务的关系四、redis、memcached、nginx1、redis2、memcached3、ngnix4、总结 一、网络模块需要处理哪些事情 网络编程主要关注客户端与服务端交互的四个问题&#xff1a; 1、连接建立 2、消息到达 3、…

学历不代表能力,但学历不够就意味着没资格!

今年的高考报名人数再创历史新高。 据悉&#xff0c;2023年全国高考报名人数1291万人&#xff0c;比去年增加98万人。 那么&#xff0c;今年的高校毕业生人数呢&#xff1f; 据人社部统计,今年我国高校毕业生人数达到1158万&#xff0c;继2022年破千万后再创历史新高。 大家…

Vue路由到新的页面,页面的名称需要改变

如下图&#xff1a;在页面中点击“属性列表”和“参数列表”的时候&#xff0c;要路由到新的页面&#xff0c;之后页面的title不用路由中的名称&#xff0c;而是用新的名称。也就是要显示对应的按钮名称&#xff0c;这个路由地址的名称是动态的。 在旧的页面上加上&#xff1a;…

汇报演示领导都说好,只因用了Smartbi幻灯片这个功能

在日常工作中&#xff0c;定期以PPT的方式汇报工作是非常常见的需求。假设你是一位销售经理&#xff0c;每个月都要参加公司的销售会议。在会议上&#xff0c;你需要向团队和高层展示销售数据、市场趋势和业绩报告等信息。过去&#xff0c;你通常是PPT来制作演示文稿&#xff0…

链表及相关面试题

链表 单链表 特点&#xff1a; 逻辑上顺序存储&#xff0c;物理上无序存储头指针根据情况而定&#xff0c;不保存数据&#xff0c;很多操作需要头指针&#xff0c;比如原地反转链表。每个节点包含 data, Node next保存下个Node public class LinkList {public Node headern…

系统初始化加载动画逻辑以及隐藏

需求&#xff1a;进入系统默认有如下的加载界面&#xff0c;但是由于网页内嵌到了其他网页中&#xff0c;这种环境下进入时再加载就不合适&#xff0c;需要隐藏掉。 因此本文的内容逻辑为 文章目录 研究加载逻辑解决需求&#xff1a;在被内嵌时隐藏掉loading 研究加载逻辑 1.…

【SpinalHDL快速入门】3、Scala 快速入门

SpinalHDL本质上来讲是Scala语言的一个库&#xff0c;所以需要先学习Scala&#xff0c;才能在此基础上学习SpinalHDL。 文章目录 Scala 基础Scala 数据类型&#xff08;5种&#xff1a;Boolean、Int、Float、Double、String&#xff09;Scala VariablesScala FunctionsReturnRe…

Python自动化测试框架:unittest介绍

Unittest是Python中最常用的测试框架之一&#xff0c;它提供了丰富和强大的测试工具和方法&#xff0c;可以帮助开发者更好地保证代码质量和稳定性&#xff0c;本文就来介绍下Unittest单元测试框架。 1. 介绍 unittest是Python的单元测试框架&#xff0c;它提供了一套丰富的测…

2023软件测试卷出天际!!!性能测试为啥一枝独秀?

近十年是中国互联网发展最快的10年&#xff0c;互联网用户从4亿增长至10亿。面对用户量的暴增&#xff0c;用户体验就成为互联网产品最大的考验。而 影响用户体验的最重要因素就是性能。 流量为王的时代&#xff0c;性能测试是所有产品上线前必须通过的重要环节。 企业招聘性…

12米与30米TanDEM-X数字高程模型DEM数据的下载申请方法

本文介绍全球12米与30米高空间分辨率的数字高程模型&#xff08;DEM&#xff09;数据——TanDEM-X数据的下载申请方法。 Tandem-X卫星项目于2010年6月启动&#xff0c;并于2010年6月21日和2010年12月21日分别发射两颗卫星&#xff0c;即TerraSAR-X和TanDEM-X。Tandem-X卫星之间…

裸辞3个月,面试了25家公司,这难度真不一般····

上半年裁员&#xff0c;下半年裸辞&#xff0c;有不少人高呼裸辞后躺平真的好快乐&#xff01;但也有很多人&#xff0c;裸辞后的生活五味杂陈。 面试25次终于找到心仪工作 因为工作压力大、领导PUA等各种原因&#xff0c;今年2月下旬我从一家互联网小厂裸辞&#xff0c;没想到…

【Android】WMS(五)输入事件原理

输入事件原理 安卓输入事件整体流程 Android 系统是由事件驱动的&#xff0c;而 input 是最常见的事件之一&#xff0c;用户的点击、滑动、长按等操作&#xff0c;都属于 input 事件驱动&#xff0c;其中的核心就是 InputReader 和 InputDispatcher。 InputReader 和 InputD…

申请国家标准项目管理专业人员能力评级(CSPM)报名条件有哪些?

2021年10月&#xff0c;中共中央、国务院发布的《国家标准化发展纲要》明确提出构建多层次从业人员培养培训体系&#xff0c;开展专业人才培养培训和国家质量基础设施综合教育。建立健全人才的职业能力评价和激励机制。由中国标准化协会&#xff08;CAS&#xff09;组织开展的项…

3.JavaScript常用对象数组对象

3.1、数组对象 3.1.1、概述 目录 3.1、数组对象 3.1.1、概述 3.1.2、创建数组 3.1.2.1、使用对象创建 3.1.2.2、使用字面量创建 3.1.3、遍历数组 3.1.4、数组属性 3.1.5、数组方法 3.2、函数对象 3.2.1、call()和apply() 3.2.2、this指向 3.2.3、arguments参数 3…

JavaSE-06 [面向对象+封装]

JavaSE-06 [面向对象封装] 第一章 面向对象思想 1.1 面向过程和面向对象 面向过程&#xff1a; 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候一个一个依次调用就可以了面向对象&#xff1a; 面向对象是把构成…

PYTHON元素定位方式总结

一&#xff0c;常用的8种定位方式 id定位 driver.find_element_by_id("id 值")   driver.find_element(by "id", value "ID值" ) name定位 单个元素&#xff1a;     driver.find_element_by_name("name值")     drive…

总结:记一次docker调试镜像的问题

一、背景 同事让帮忙部署一个应用到QKE&#xff0c;给了我镜像地址与配置文件。 由于要将配置文件映射到容器中&#xff0c;我创建了configmap&#xff0c;然后应用中将configmap中key对应的内容映射到了容器中的配置文件中。 但是我遇到了一个问题&#xff1a; 容器频繁快…