阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

定制Bean

251次阅读
没有评论

共计 5488 个字符,预计需要花费 14 分钟才能阅读完成。

Scope

对于 Spring 容器来说,当我们把一个 Bean 标记为 @Component 后,它就会自动为我们创建一个单例(Singleton),即容器初始化时创建 Bean,容器关闭前销毁 Bean。在容器运行期间,我们调用 getBean(Class) 获取到的 Bean 总是同一个实例。

还有一种 Bean,我们每次调用 getBean(Class),容器都返回一个新的实例,这种 Bean 称为 Prototype(原型),它的生命周期显然和 Singleton 不同。声明一个 Prototype 的 Bean 时,需要添加一个额外的@Scope 注解:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {...}

注入 List

有些时候,我们会有一系列接口相同,不同实现类的 Bean。例如,注册用户时,我们要对 email、password 和 name 这 3 个变量进行验证。为了便于扩展,我们先定义验证接口:

public interface Validator {void validate(String email, String password, String name);
}

然后,分别使用 3 个 Validator 对用户参数进行验证:

@Component
public class EmailValidator implements Validator {public void validate(String email, String password, String name) {if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {throw new IllegalArgumentException("invalid email:" + email);
        }
    }
}

@Component
public class PasswordValidator implements Validator {public void validate(String email, String password, String name) {if (!password.matches("^.{6,20}$")) {throw new IllegalArgumentException("invalid password");
        }
    }
}

@Component
public class NameValidator implements Validator {public void validate(String email, String password, String name) {if (name == null || name.isBlank() || name.length() > 20) {throw new IllegalArgumentException("invalid name:" + name);
        }
    }
}

最后,我们通过一个 Validators 作为入口进行验证:

@Component
public class Validators {@Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {for (var validator : this.validators) {validator.validate(email, password, name);
        }
    }
}

注意到 Validators 被注入了一个 List<Validator>,Spring 会自动把所有类型为Validator 的 Bean 装配为一个 List 注入进来,这样一来,我们每新增一个 Validator 类型,就自动被 Spring 装配到 Validators 中了,非常方便。

因为 Spring 是通过扫描 classpath 获取到所有的 Bean,而 List 是有序的,要指定 List 中 Bean 的顺序,可以加上 @Order 注解:

@Component
@Order(1)
public class EmailValidator implements Validator {...}

@Component
@Order(2)
public class PasswordValidator implements Validator {...}

@Component
@Order(3)
public class NameValidator implements Validator {...}

可选注入

默认情况下,当我们标记了一个 @Autowired 后,Spring 如果没有找到对应类型的 Bean,它会抛出 NoSuchBeanDefinitionException 异常。

可以给 @Autowired 增加一个 required = false 的参数:

@Component
public class MailService {@Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();
    ...
}

这个参数告诉 Spring 容器,如果找到一个类型为 ZoneId 的 Bean,就注入,如果找不到,就忽略。

这种方式非常适合有定义就使用定义,没有就使用默认值的情况。

创建第三方 Bean

如果一个 Bean 不在我们自己的 package 管理之内,例如ZoneId,如何创建它?

答案是我们自己在 @Configuration 类中编写一个 Java 方法创建并返回它,注意给方法标记一个 @Bean 注解:

@Configuration
@ComponentScan
public class AppConfig {// 创建一个 Bean:
    @Bean
    ZoneId createZoneId() {return ZoneId.of("Z");
    }
}

Spring 对标记为 @Bean 的方法只调用一次,因此返回的 Bean 仍然是单例。

初始化和销毁

有些时候,一个 Bean 在注入必要的依赖后,需要进行初始化(监听消息等)。在容器关闭时,有时候还需要清理资源(关闭连接池等)。我们通常会定义一个 init() 方法进行初始化,定义一个 shutdown() 方法进行清理,然后,引入 JSR-250 定义的 Annotation:

  • jakarta.annotation:jakarta.annotation-api:2.1.1

在 Bean 的初始化和清理方法上标记 @PostConstruct@PreDestroy

@Component
public class MailService {@Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();

    @PostConstruct
    public void init() {System.out.println("Init mail service with zoneId =" + this.zoneId);
    }

    @PreDestroy
    public void shutdown() {System.out.println("Shutdown mail service");
    }
}

Spring 容器会对上述 Bean 做如下初始化流程:

  • 调用构造方法创建 MailService 实例;
  • 根据 @Autowired 进行注入;
  • 调用标记有 @PostConstructinit()方法进行初始化。

而销毁时,容器会首先调用标记有 @PreDestroyshutdown()方法。

Spring 只根据 Annotation 查找 无参数 方法,对方法名不作要求。

使用别名

默认情况下,对一种类型的 Bean,容器只创建一个实例。但有些时候,我们需要对一种类型的 Bean 创建多个实例。例如,同时连接多个数据库,就必须创建多个 DataSource 实例。

如果我们在 @Configuration 类中创建了多个同类型的 Bean:

@Configuration
@ComponentScan
public class AppConfig {@Bean
    ZoneId createZoneOfZ() {return ZoneId.of("Z");
    }

    @Bean
    ZoneId createZoneOfUTC8() {return ZoneId.of("UTC+08:00");
    }
}

Spring 会报 NoUniqueBeanDefinitionException 异常,意思是出现了重复的 Bean 定义。

这个时候,需要给每个 Bean 添加不同的名字:

@Configuration
@ComponentScan
public class AppConfig {@Bean("z")
    ZoneId createZoneOfZ() {return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")
    ZoneId createZoneOfUTC8() {return ZoneId.of("UTC+08:00");
    }
}

可以用 @Bean("name") 指定别名,也可以用 @Bean+@Qualifier("name") 指定别名。

存在多个同类型的 Bean 时,注入 ZoneId 又会报错:

NoUniqueBeanDefinitionException: No qualifying bean of type 'java.time.ZoneId' available: expected single matching bean but found 2

意思是期待找到唯一的 ZoneId 类型 Bean,但是找到两。因此,注入时,要指定 Bean 的名称:

@Component
public class MailService {@Autowired(required = false)
	@Qualifier("z") // 指定注入名称为 "z" 的 ZoneId
	ZoneId zoneId = ZoneId.systemDefault();
    ...
}

还有一种方法是把其中某个 Bean 指定为@Primary

@Configuration
@ComponentScan
public class AppConfig {@Bean
    @Primary // 指定为主要 Bean
    @Qualifier("z")
    ZoneId createZoneOfZ() {return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")
    ZoneId createZoneOfUTC8() {return ZoneId.of("UTC+08:00");
    }
}

这样,在注入时,如果没有指出 Bean 的名字,Spring 会注入标记有 @Primary 的 Bean。这种方式也很常用。例如,对于主从两个数据源,通常将主数据源定义为@Primary

@Configuration
@ComponentScan
public class AppConfig {@Bean
    @Primary
    DataSource createMasterDataSource() {...}

    @Bean
    @Qualifier("slave")
    DataSource createSlaveDataSource() {...}
}

其他 Bean 默认注入的就是主数据源。如果要注入从数据源,那么只需要指定名称即可。

使用 FactoryBean

我们在设计模式的工厂方法中讲到,很多时候,可以通过工厂模式创建对象。Spring 也提供了工厂模式,允许定义一个工厂,然后由工厂创建真正的 Bean。

用工厂模式创建 Bean 需要实现 FactoryBean 接口。我们观察下面的代码:

@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {String zone = "Z";

    @Override
    public ZoneId getObject() throws Exception {return ZoneId.of(zone);
    }

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

当一个 Bean 实现了 FactoryBean 接口后,Spring 会先实例化这个工厂,然后调用 getObject() 创建真正的 Bean。getObjectType()可以指定创建的 Bean 的类型,因为指定类型不一定与实际类型一致,可以是接口或抽象类。

因此,如果定义了一个 FactoryBean,要注意 Spring 创建的 Bean 实际上是这个FactoryBeangetObject()方法返回的 Bean。为了和普通 Bean 区分,我们通常都以 XxxFactoryBean 命名。

由于可以用 @Bean 方法创建第三方 Bean,本质上 @Bean 方法就是工厂方法,所以,FactoryBean已经用得越来越少了。

练习

定制 Bean。

下载练习

小结

Spring 默认使用 Singleton 创建 Bean,也可指定 Scope 为 Prototype;

可将相同类型的 Bean 注入 List 或数组;

可用 @Autowired(required=false) 允许可选注入;

可用带 @Bean 标注的方法创建 Bean;

可使用 @PostConstruct@PreDestroy对 Bean 进行初始化和清理;

相同类型的 Bean 只能有一个指定为 @Primary,其他必须用@Qualifier("beanName") 指定别名;

注入时,可通过别名 @Qualifier("beanName") 指定某个 Bean;

可以定义 FactoryBean 来使用工厂模式创建 Bean。

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计5488字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19348
评论数
4
阅读量
7815500
文章搜索
热门文章
开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南 大家好,我是星哥。之前介绍了腾讯云的 Code...
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板 1Panel:小白轻松构建 Web 服务与面板加固指南 哈喽,我是星哥,经常有人问我不...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

  240 元左右!五盘位 NAS 主机,7 代 U 硬解 4K 稳如狗,拓展性碾压同价位 在 NA...
星哥带你玩飞牛NAS-7:手把手教你免费内网穿透-Cloudflare tunnel

星哥带你玩飞牛NAS-7:手把手教你免费内网穿透-Cloudflare tunnel

星哥带你玩飞牛 NAS-7:手把手教你免费内网穿透 -Cloudflare tunnel 前言 大家好,我是星...
飞牛NAS玩转Frpc并且配置,随时随地直连你的私有云

飞牛NAS玩转Frpc并且配置,随时随地直连你的私有云

飞牛 NAS 玩转 Frpc 并且配置,随时随地直连你的私有云 大家好,我是星哥,最近在玩飞牛 NAS。 在数...
如何免费使用强大的Nano Banana Pro?附赠邪修的用法

如何免费使用强大的Nano Banana Pro?附赠邪修的用法

如何免费使用强大的 Nano Banana Pro?附赠邪修的用法 前言 大家好,我是星哥,今天来介绍谷歌的 ...
再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见 zabbix!轻量级自建服务器监控神器在 Linux 的完整部署指南 在日常运维中,服务器监控是绕不开的...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
【开源神器】微信公众号内容单篇、批量下载软件

【开源神器】微信公众号内容单篇、批量下载软件

【开源神器】微信公众号内容单篇、批量下载软件 大家好,我是星哥,很多人都希望能高效地保存微信公众号的文章,用于...
仅2MB大小!开源硬件监控工具:Win11 无缝适配,CPU、GPU、网速全维度掌控

仅2MB大小!开源硬件监控工具:Win11 无缝适配,CPU、GPU、网速全维度掌控

还在忍受动辄数百兆的“全家桶”监控软件?后台偷占资源、界面杂乱冗余,想查个 CPU 温度都要层层点选? 今天给...
星哥带你玩飞牛NAS硬件02:某鱼6张左右就可拿下5盘位的飞牛圣体NAS

星哥带你玩飞牛NAS硬件02:某鱼6张左右就可拿下5盘位的飞牛圣体NAS

星哥带你玩飞牛 NAS 硬件 02:某鱼 6 张左右就可拿下 5 盘位的飞牛圣体 NAS 前言 大家好,我是星...
支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare 也瘫了连监控都挂,根因藏在哪? 最近两天的互联网堪称“故障...
把小米云笔记搬回家:飞牛 NAS 一键部署,小米云笔记自动同步到本地

把小米云笔记搬回家:飞牛 NAS 一键部署,小米云笔记自动同步到本地

把小米云笔记搬回家:飞牛 NAS 一键部署,小米云笔记自动同步到本地 大家好,我是星哥,今天教大家在飞牛 NA...