从零开始 Spring Boot 45:FactoryBean

news2024/11/25 14:48:06

从零开始 Spring Boot 45:FactoryBean

spring boot

图源:简书 (jianshu.com)

在前文中我介绍过 FactoryBean,本篇文章会更深入的介绍相关内容。

依赖注入

从一个简单示例开始,我们看使用FactoryBean定义的 Spring Bean 如何注入。

假设我们有以下的几个类:

public class Clock {
    private LocalDateTime time;
    private int num;
    private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;

    public Clock(LocalDateTime time, int num) {
        this.time = time;
        this.num = num;
    }

    @Override
    public String toString() {
        return "Clock#%d(%s)".formatted(num, dateTimeFormatter.format(time));
    }
}

public class ClockFactory implements FactoryBean<Clock> {
    private static int num = 0;

    @Override
    public Clock getObject() throws Exception {
        return new Clock(LocalDateTime.now(), ++num);
    }

    @Override
    public Class<?> getObjectType() {
        return Clock.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

    @Override
    public String toString() {
        return "ClockFactory(num=%d)".formatted(num);
    }
}

Clock是一个POJO类,ClockFactoryClock的“工厂类”,并且实现了FactoryBean<Clock>接口。

需要注意的是,这里的ClockFacotry.isSingleton方法返回的是false,并且每次请求ClockFactory.getObject也会返回一个新的Clock实例。

使用 @Bean 方法向上下文添加 ClockFacotry bean:

@Configuration
public class WebConfig {
    @Bean("clock")
    public ClockFactory clockFactory(){
        return new ClockFactory();
    }
}

注意,这里只添加了ClockFactory bean,并没有添加Clock bean,但因为前者实现了FactoryBean接口,因此 Spring 会将其看作一个工厂 bean,如果向上下文请求Clock类型的 bean,Spring 就会利用ClockFacotry bean 的getObject方法返回一个Clock对象。

我们可以通过下面的测试验证这一点:

@SpringJUnitConfig(classes = {FactoryBeanApplication.class})
public class ClockFactoryTests {
    @Autowired
    private ClockFactory clockFactory1;
    @Autowired
    private ClockFactory clockFactory2;
    @Autowired
    private Clock clock1;
    @Autowired
    private Clock clock2;

    @Test
    void testClockFactoryInject() {
        Assertions.assertSame(clockFactory1, clockFactory2);
        Assertions.assertNotSame(clock1, clock2);
    }
}

这里通过属性注入,可以正常获取到4个bean,两个ClockFactory bean是同一个,因为通过@Bean方法添加的 bean 的作用域是单例。两个Clock bean 是不同的,这是因为ClockFactory返回的是新的Clock对象。

当然,也可以使用@Resource进行自动连接,不过要注意的是,使用工厂类(ClockFactory)的 bean 名称(clock)匹配到的 bean 是工厂类返回的类型(Clock),如果要匹配到工厂类本身,就需要使用&符号(&clock),比如:

@SpringJUnitConfig(classes = {FactoryBeanApplication.class})
public class ClockFactoryTests2 {
    @Resource(name = "&clock")
    private ClockFactory clockFactory1;
    @Resource(name = "&clock")
    private ClockFactory clockFactory2;
    @Resource(name = "clock")
    private Clock clock1;
    @Resource(name = "clock")
    private Clock clock2;
    // ...
}

可以修改示例,让ClockFactory返回的Clock是单例:

public class ClockFactory2 implements FactoryBean<Clock> {
    private static int num = 0;
    private static Clock clock = new Clock(LocalDateTime.now(), num);

    @Override
    public Clock getObject() throws Exception {
        return clock;
    }

    @Override
    public Class<?> getObjectType() {
        return Clock.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public String toString() {
        return "ClockFactory(num=%d)".formatted(num);
    }
}

编写测试用例:

@SpringJUnitConfig
public class ClockFactory2Tests {
    @Configuration
    static class Config {
        @Bean
        public ClockFactory2 clockFactory2() {
            return new ClockFactory2();
        }
    }

    @Autowired
    private ClockFactory2 clockFactory1;
    @Autowired
    private ClockFactory2 clockFactory2;
    @Autowired
    private Clock clock1;
    @Autowired
    private Clock clock2;

    @Test
    void testInject(){
        Assertions.assertSame(clockFactory1, clockFactory2);
        Assertions.assertSame(clock1, clock2);
    }
}

这个测试用例并没有导入入口类,而是通过内嵌类提供 test 配置,因此这里注入Clock的 bean 时不会发生冲突。

初始化

通常我们使用FactoryBean来创建某些复杂的 bean,因此可能需要在FactoryBean实例创建后,调用FactoryBean.getObject获取对象前对工厂对象进行处理,比如检查属性是否合法。

我们可以通过 Spring Bean 的生命周期回调来实现这点。

在这篇文章中,我详细介绍了生命周期回调。

看这个例子:

@Data
public class Tank {
    public enum Status {PREPAREDNESS, MAINTENANCE, TRAINING}

    public enum Model {T99A, T96, T95, T88, T69}

    private final Model model;
    private final String factory;
    private final int motorizedHours;
    private final Status status;
}

public class TankFactory implements FactoryBean<Tank> {
    private Map<Tank.Model, Integer> motorizedHours = new HashMap<>();

    {
        motorizedHours.put(Tank.Model.T99A, 100);
        motorizedHours.put(Tank.Model.T95, 300);
        motorizedHours.put(Tank.Model.T88, 400);
        motorizedHours.put(Tank.Model.T69, 500);
    }

    private final String factoryName;
    private final Tank.Model model;

    public TankFactory(String factoryName, Tank.Model model) {
        this.factoryName = factoryName;
        this.model = model;
    }

    @PostConstruct
    public void checkFactory() {
        if (ObjectUtils.isEmpty(factoryName) || model == null) {
            throw new RuntimeException("工厂名称或坦克型号不能为空");
        }
        if (!motorizedHours.containsKey(model)) {
            throw new RuntimeException("缺少型号%s的摩托化小时数据".formatted(model));
        }
    }

    @Override
    public Tank getObject() throws Exception {
        Integer motorizedHours = this.motorizedHours.get(model);
        if (motorizedHours == null) {
            throw new RuntimeException("缺少型号%s对应的摩托化小时数据".formatted(model));
        }
        return new Tank(model, factoryName, motorizedHours, Tank.Status.TRAINING);
    }

    @Override
    public Class<?> getObjectType() {
        return Tank.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

@Configuration
public class WebConfig {
	// ...
    @Bean
    public TankFactory tankFactory() {
        return new TankFactory("红旗机械厂", Tank.Model.T99A);
    }
}

在工厂类TankFactory中我们用@PostConstruct注解添加了一个 bean 生命周期回调checkFactory,这个方法会在TankFactory的 bean 被 ApplicationContext 初始化后调用。

为了检验checkFactory会正常调用,可以使用以下测试用例:

@SpringJUnitConfig
public class TankFactoryTests2 {
    @Configuration
    static class Config{
        @Bean
        public TankFactory tankFactory(){
            return new TankFactory("", null);
        }
    }
    @Autowired
    private Tank tank1;
    @Autowired
    private Tank tank2;

    @Test
    void testInject(){
        Assertions.assertNotSame(tank1, tank2);
    }
}

运行这个测试会产生一个错误:无法正常创建 Spring 的上下文。因为TankFactory bean 创建后,执行回调checkFactory会抛出一个异常。

当然,在这个示例中我们可以将检查工厂属性是否正确设置的代码放在工厂类的构造器中,但是使用 bean 的生命周期回调会让这种检查行为更灵活,比如下面这个示例:

@Setter
@Accessors(chain = true)
public class TankFactory2 implements FactoryBean<Tank> {
    @Setter(AccessLevel.NONE)
    private Map<Tank.Model, Integer> motorizedHours = new HashMap<>();
	// ...

    @PostConstruct
    public void checkFactory() {
		// ...
    }
	// ...
}

这里将设置工厂属性的方式从构造器改为 Setter,且可以级联调用(@Accessors)。

Lombok 的相关注解(@Setter@Accessors等)的用法见我的这篇文章。

但此时使用回调检测工厂类属性是否正确设置依然是可行的:

@SpringJUnitConfig
public class TankFactory2Tests {
    @Configuration
    static class Config{
        @Bean
        public TankFactory2 tankFactory2(){
            return new TankFactory2()
                    .setFactoryName("")
                    .setModel(null);
        }
    }

    @Autowired
    private Tank tank1;
    @Autowired
    private Tank tank2;
    @Test
    void testInject(){
        Assertions.assertNotSame(tank1, tank2);
    }
}

这里同样会因为工厂类属性设置不对导致上下文加载出错。

AbstractFactoryBean

Spring 提供一个抽象基类AbstractFactoryBean,利用它我们可以更方便地创建“工厂类”:

public class ClockFactory3 extends AbstractFactoryBean<Clock> {
    private static int num = 0;

    public ClockFactory3() {
        super();
        setSingleton(true);
    }

    @Override
    public Class<?> getObjectType() {
        return Clock.class;
    }

    @Override
    protected Clock createInstance() throws Exception {
        return new Clock(LocalDateTime.now(), ++num);
    }
}

需要强制重写的只有两个方法:

  • getObjectType,返回工厂产出对象的类型。
  • createInstance,返回工厂产出的对象。

createInstance方法只是单纯地负责“生产产品”,不需要考虑产品是否是单例的问题。因为这些问题会在基类AbstractFactoryBeangetObject方法中考虑。对于是否产出单例产品,我们只需要在工厂类的构造器中通过setSingleton告诉基类即可。

比如,如果要生产非单例的Clock 对象,可以:

public class ClockFactory4 extends AbstractFactoryBean<Clock> {
    private static int num = 0;

    public ClockFactory4() {
        super();
        setSingleton(false);
    }
	// ...
}

测试用例与之前的类似,这里不再说明,感兴趣的可以阅读完整代码。

总结

使用FactoryBean是封装复杂的构造逻辑或在 Spring 中更容易配置高度可配置对象的良好实践。

The End,谢谢阅读。

本文的完整示例可以从这里获取。

参考资料

  • 从零开始 Spring Boot 27:IoC - 红茶的个人站点 (icexmoon.cn)
  • 从零开始 Spring Boot 35:Lombok - 红茶的个人站点 (icexmoon.cn)
  • How to Use the Spring FactoryBean? Baeldung

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

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

相关文章

mysql数据库备份和恢复和日志管理

数据库备份和恢复和日志管理 一&#xff1a;数据库备份备份和恢复1.备份分类从物理与逻辑的角度&#xff0c;备份可分为从数据库的备份策略角度&#xff0c;备份可分为 2.常见的备份方法3.MySQL完全备份和恢复分类一&#xff1a;物理冷备份与恢复关闭MySQL数据库使用tar命令直接…

DJ5-1 链路层概述

目录 一、链路层的术语 二、链路层的类比 三、链路层提供的服务 四、链路层实现的位置 1、网络适配器 2、网络适配器工作过程 一、链路层的术语 ① 节点 (nodes)&#xff1a;主机和路由器 ② 链路 (links)&#xff1a;沿着通信路径连接相邻节点的通信信道 有线链路 (w…

locust学习教程(2)- 性能测试虚拟环境配置

前言 写这个是因为&#xff0c;好多专职自动化测试都不知道怎么配置虚拟&#xff0c;可想而知萌新们整个人都是懵的 1、安装pthon&#xff08;略&#xff09; 2、安装pycharm&#xff08;略&#xff09; 3、配置性能测试的虚拟环境 桌面新建目录 demolocust pycharm中 open …

Grdle版本的不同导致的一些差异

gradle版本是不断迭代升级的&#xff0c;升级后对有些配置是有影响的&#xff0c;比如对kotlin配置、上传maven的方式&#xff0c;特此记录一下 对kotlin配置的影响 我们主项目的gradle版本是6.3&#xff0c;对项目进行koltin配置的语法了&#xff0c;官方文档教程是一样的 …

计算机图形学-坐标系(坐标总汇)

建议买本书《计算机图形学》第四版 1 坐标系 1.1 在我们谈论坐标变换之前&#xff0c;我们必须对我们的坐标系做一个正式的定义。DirectX 使用的默认坐标系是左手坐标系。OpenGL 使用的默认坐标系是右手坐标系。 我们可以通过使用手对坐标系进行一个简单的判断。 左手坐标…

LiangGaRy-学习笔记-Day24

1、web服务介绍 1.1、Linux常见的web服务 httpd nginx tomcat 1.2、LAMP架构 L&#xff1a;CentOS CentOS7RHEL7等等 A&#xff1a;Apache apache&#xff1a;网页服务器 httpd服务 M&#xff1a;MySQL MySQL&#xff1a;关系型数据库 AB公司–>08年被SUN公司收购…

基于.Net6使用YoloV8的分割模型

前言 在目标检测一文中&#xff0c;我们学习了如何处理Onnx模型&#xff0c;并的到目标检测结果&#xff0c;在此基础上&#xff0c;本文实现基于.Net平台的实例分割任务。 执行YoloV8的分割任务后可以得到分割.pt模型。由于Python基本不用于工业软件的部署&#xff0c;最终还…

1.1 渲染流水线

整体流程 应用阶段&#xff1a;粗粒度剔除、进行渲染设置、准备基本数据、输出到几何阶段 几何阶段&#xff1a;顶点着色器、曲面细分、几何着色器、顶点裁剪、屏幕映射 光栅化阶段&#xff1a;三角形&#xff08;点/线&#xff09;设置、三角形&#xff08;点/线&#xff09…

基于android studio开发的火车票购票系统app,android移动开发课设,毕业设计

基于android studio开发的火车票购票系统app 项目概述 基于android studio开发实现火车票购票系统app 适用于android移动开发学习项目&#xff0c;课程设计&#xff0c;毕业设计等 开发环境及工具 开发工具&#xff1a;android studio 或者intellij idea专业版操作系统&…

最新大学计算机专业实习心得报告

最新大学计算机专业实习心得报告&#xff08;篇1&#xff09; 一、实习目的 通过理论联系实际&#xff0c;巩固所学的知识&#xff0c;提高处理实际问题的能力&#xff0c;为顺利毕业进行做好充分的准备&#xff0c;并为自己能顺利与社会环境接轨做准备。通过这次实习&#xff…

软件测试入门篇

软件测试含义 在规定条件下对程序进行操作&#xff0c;发现软件错误&#xff0c;衡量软件质量&#xff0c;对其是否能满足设计要求进行评估的过程 开发不做测试原因&#xff1a;测试力度&#xff0c;思维方式&#xff0c;关注度 计算机定义 一种可以自动高效进行技术操作的…

Debian 版本代号与《玩具总动员》

作为最受欢迎的 Linux 发行版之一&#xff0c;Debian 是许多其他发行版的基础&#xff0c;许多非常受欢迎的 Linux 发行版&#xff0c;例如 Ubuntu、Knoppix、PureOS 、Tails、Armbian 以及 Raspbian&#xff0c;都基于 Debian。 经过近 20 个月的开发&#xff0c;2023 年 6 月…

基于SpringBoot的大学生成长管理系统的设计与实现

摘 要 大学生成长管理系统是记录大学生在大学期间的成长记录史。它是为了促进学生成长、提升学习兴趣及其质量。系统是集辅导员、学生和管理员为核心的一个综合平台。 本系统采用Java编程语言&#xff0c;完成了大学生成长管理系统。系统的使用角色分为三个&#xff1a;管理员…

鉴源实验室丨HSM技术浅述

作者 | 徐奕华 上海控安可信软件创新研究院汽车网络安全组 来源 | 鉴源实验室 引言&#xff1a;2023年5月初&#xff0c;工业和信息化部装备工业一司组织全国汽车标准化技术委员会开展《汽车整车信息安全技术要求》等四项强制性国家标准的制修订[1]。这意味着车辆信息安全管理…

CPU调优 iostat命令

目录 一、命令描述 二、命令作用 三、命令 1.用法 2.命令参数 四、使用实例 1.CPU属性值说明 五、压力测试stress 1.stress介绍 2.stress参数 3.安装stress 四、实验 五、总结 一、命令描述 Linux 中的 iostat 是I/O statistics&#xff08;输入/输出统计&#xf…

毕业设计心得总结10篇

毕业设计心得总结1 201_年5月30日上午&#xff0c;我们的毕业论文答辩圆满结束了。当刘老师给我们送上人生的祝语时&#xff0c;一种即将离别的难舍之情油然而生&#xff0c;我开始眷恋培养我大学四年的母校&#xff0c;看着一张张熟悉的面孔&#xff0c;过去的欢乐和不快都烟消…

JavaEE课程设计——校园招聘管理系统(vue框架分析)

目录 Vue架构 登录 Vue架构 前端执行命令 npm run serve 这是整个前端的目录结构 vue.config.js是对前端vue的一个配置&#xff0c; // var webpack require(webpack); const path require(path)function resolve(dir) {return path.join(__dirname, dir) }function pu…

每日学术速递6.13

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Tracking Everything Everywhere All at Once 标题&#xff1a;一次跟踪所有地方的一切 作者&#xff1a;Qianqian Wang, Yen-Yu Chang, Ruojin Cai, Zhengqi Li, Bharath Hariha…

数据结构 栈(C语言实现)

绪论 时间就是生命&#xff0c;时间就是速度&#xff0c;时间就是气力。——郭沫若&#xff1b;本章继续学习数据结构&#xff0c;本章主要讲了什么是栈以及栈的基本功能和实现方法。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&#xff…

专业科普:什么是单片机?

一、什么是单片机 单片机诞生于20世纪70年代末&#xff0c;它是指一个集成在一块芯片上的完整计算机系统。单片机具有一个完整计算机所需要的大部分部件&#xff1a;CPU、内存、内部和外部总线系统&#xff0c;目前大部分还会具有外存。同时集成诸如通讯接口、定时器&#xff…