SpringBoot基础篇Bean之动态注册
Spring中的Bean除了前面提到的几种JavaConfig或者@Component
等注解标识之外,也是可以动态的向Spring容器注册的,本篇博文将主要介绍
- 如何向Spring容器注册Bean
- 如何引用主动注册的Bean
- 注册的Bean中,如果依赖其他的Bean,怎么操作
<!-- more -->
I. 手动注册Bean方式
1. 核心实现类
以前也写过关于动态注册Bean的博文,如 180804-Spring之动态注册bean
我们的实现方式和上面也没什么区别,依然是借助BeanDefinition
来创建Bean定义并注册到BeanFactory中,具体实现的核心代码如下
public class ManualRegistBeanUtil {
/**
* 主动向Spring容器中注册bean
*
* @param applicationContext Spring容器
* @param name BeanName
* @param clazz 注册的bean的类性
* @param args 构造方法的必要参数,顺序和类型要求和clazz中定义的一致
* @param <T>
* @return 返回注册到容器中的bean对象
*/
public static <T> T registerBean(ConfigurableApplicationContext applicationContext, String name, Class<T> clazz,
Object... args) {
if(applicationContext.containsBean(name)) {
Object bean = applicationContext.getBean(name);
if (bean.getClass().isAssignableFrom(clazz)) {
return (T) bean;
} else {
throw new RuntimeException("BeanName 重复 " + name);
}
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
for (Object arg : args) {
beanDefinitionBuilder.addConstructorArgValue(arg);
}
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
beanFactory.registerBeanDefinition(name, beanDefinition);
return applicationContext.getBean(name, clazz);
}
}
上面唯一的方法中,接收四个参数,源码中也有说明,稍微需要注意下的是Spring容器中不允许出现同名的Bean
2. 测试用例
动态创建Bean,并不是塞入容器之中就完结了,塞进去之后,是为了后续的使用,自然而然的就会有下面几种情形
a. 无其他Bean依赖
即不依赖其他的Bean, 单纯的供其他地方使用,这种情况下,主要需要测试的就是别人可以通过什么方式来使用它
@Slf4j
public class ManualBean {
private int id;
public ManualBean() {
Random random = new Random();
id = random.nextInt(100);
}
public String print(String msg) {
return "[ManualBean] print : " + msg + " id: " + id;
}
}
b. 依赖其他Bean
和前面一个不同,这个Bean内部需要注入其他的Bean,因此我们主动注册Bean时,能否将依赖的Bean也注入进去呢?
定义一个测试Bean
@Slf4j
public class ManualDIBean {
private int id;
@Autowired
private OriginBean originBean;
private String name;
public ManualDIBean(String name) {
Random random = new Random();
this.id = random.nextInt(100);
this.name = name;
}
public String print(String msg) {
String o = originBean.print(" call by ManualDIBean! ");
return "[ManualDIBean] print: " + msg + " id: " + id + " name: " + name + " originBean print:" + o;
}
}
其依赖的普通Bean定义如下
@Slf4j
@Component
public class OriginBean {
private LocalDateTime time;
public OriginBean() {
time = LocalDateTime.now();
}
public String print(String msg) {
return "[OriginBean] print msg: " + msg + ", time: " + time;
}
}
c. 普通Bean依赖主动注册的Bean
这个其实就是使用case了,主动注册的Bean也是被人使用的,那可以怎么使用呢?传统的Autowired
可否?
@Slf4j
@Component
public class AnoOriginBean {
// 希望可以注入 主动注册的Bean
@Autowired
private ManualBean manualBean;
public AnoOriginBean() {
System.out.println("AnoOriginBean init: " + System.currentTimeMillis());
}
public String print() {
return "[AnoOriginBean] print!!! manualBean == null ? " + (manualBean == null);
}
}
d. Bean注册实现
前面定义了两个需要手动注册的bean,所以就需要选择一个合适的地方来处理主动注册的逻辑,我们把这段逻辑放在AutoConfig中,用于测试演示
@Configuration
public class BeanRegisterAutoConf {
public BeanRegisterAutoConf(ApplicationContext applicationContext) {
System.out.println("BeanRegisterAutoConf init: " + System.currentTimeMillis());
registerManualBean((ConfigurableApplicationContext) applicationContext);
}
/**
* 手动注册自定义地bean
* @param applicationContext
*/
private void registerManualBean(ConfigurableApplicationContext applicationContext) {
// 主动注册一个没什么依赖的Bean
ManualBean manualBean = ManualRegistBeanUtil.registerBean(applicationContext, "manualBean", ManualBean.class);
manualBean.print("test print manualBean");
// manualDIBean 内部,依赖由Spring容器创建的OriginBean
ManualDIBean manualDIBean = ManualRegistBeanUtil.registerBean(applicationContext, "manualDIBean",
ManualDIBean.class, "依赖OriginBean的自定义Bean");
manualDIBean.print("test print manualDIBean");
}
}
3. 实测演示
前面的测试case都准备好了,接着就需要实际的跑一下看看效果了,选择Rest服务来演示,创建一个简单的Controller
@RestController
public class ShowController {
@Autowired
private ManualBean manualBean;
@Autowired
private ManualDIBean manualDIBean;
@Autowired
private AnoOriginBean anoOriginBean;
public ShowController() {
System.out.println("ShowController init: " + System.currentTimeMillis());
}
@GetMapping(path = "show")
public String show(String msg) {
Map<String, String> result = new HashMap<>(8);
result.put("manualBean", manualBean == null ? "null" : manualBean.print(msg));
result.put("manualDIBean", manualDIBean == null ? "null" : manualDIBean.print(msg));
result.put("anoOriginBean",anoOriginBean.print());
return JSONObject.toJSONString(result);
}
}
上面就使用了三个Bean,两个主动注册的外加一个依赖了主动注册Bean的anoOriginBean
(其实Controller本身也是一个使用主动注册Bean的Bean)
先预测一下结果:
- 如果 manualBean, manualDIBean 为空,表示不能直接通过
@Autowired
注解的方式引入手动注册的Bean;此时会抛npe - 如果没有npe,且 AnoOriginBean内部依赖的manualBean也不是null,则表示直接用
@Autowired
来注入没啥毛病(是否绝对呢?) - manualDIBean 内部依赖了
originBean
,也是通过注解方式注入,如果正常返回,表示手动注册的也可以这么引用其他的Bean;否则不行
执行结果如上图,简单来说,就是手动注册的Bean,和我们一般使用的Bean也没什么两样,原来可以怎么用,现在依然可以这么用
II. BeanDefinitionRegistryPostProcessor扩展方式
前面这种手动注入的方式有个不好的地方就是主动注册的这个逻辑,感觉写在什么地方都不太优雅,在Spring项目的源码中通过实现BeanDefinitionRegistryPostProcessor扩展方式
接口的方式比较多,比如org.springframework.cloud.autoconfigure.RefreshAutoConfiguration
依葫芦画瓢实现一个
1. 实现类
@Slf4j
@Configuration
public class AutoBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 注册Bean定义,容器根据定义返回bean
//构造bean定义
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(AutoBean.class);
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
//注册bean定义
registry.registerBeanDefinition("autoBean", beanDefinition);
// AutoDIBean 的注入方式
beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(AutoDIBean.class);
beanDefinitionBuilder.addConstructorArgValue("自动注入依赖Bean");
beanDefinition = beanDefinitionBuilder.getBeanDefinition();
registry.registerBeanDefinition("autoDiBean", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
// 注册Bean实例,使用supply接口, 可以创建一个实例,并主动注入一些依赖的Bean;当这个实例对象是通过动态代理这种框架生成时,就比较有用了
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(AutoFacDIBean.class, () -> {
AutoFacDIBean autoFacDIBean = new AutoFacDIBean("autoFac");
autoFacDIBean.setAutoBean(factory.getBean("autoBean", AutoBean.class));
autoFacDIBean.setOriginBean(factory.getBean("originBean", OriginBean.class));
return autoFacDIBean;
});
BeanDefinition beanDefinition = builder.getRawBeanDefinition();
((DefaultListableBeanFactory) factory).registerBeanDefinition("autoFacDIBean", beanDefinition);
}
}
接口的实现中,Bean的注册方式和前面的其实是一样的,这个接口提供了两个方法,通常实现第一个方法来做Bean的注册;两者从根本上也没太大的区别,上面只是给出了一种使用演示
2. 测试用例
测试的思路基本上和前面一样,定义了三个需要我们注册的Bean,一个没有外部依赖的AutoBean
public class AutoBean {
public String print() {
return "[AutoBean] " + System.currentTimeMillis();
}
}
一个依赖外部Bean的AutoDIBean
public class AutoDIBean {
private String name;
@Autowired
private OriginBean originBean;
public AutoDIBean(String name) {
this.name = name;
}
public String print() {
return "[AutoDIBean] " + name + " originBean == null ? " + (originBean == null);
}
}
一个用于主动创建和设置依赖的AutoFacDIBean
(用于前面的实现类中的第二个方法的注册方式)
public class AutoFacDIBean {
private String name;
@Setter
private OriginBean originBean;
@Setter
private AutoBean autoBean;
public AutoFacDIBean(String name) {
this.name = name;
}
public String print() {
return "[AutoDIBean] " + name + " originBean == null ? " + (originBean == null) + " | autoBean==null ? " +
(autoBean == null);
}
}
一个依赖了主动注册AutoBean的 AnoAutoOriginBean
@Component
public class AnoAutoOriginBean {
@Autowired
private AutoBean autoBean;
public AnoAutoOriginBean() {
System.out.println("AnoAutoOriginBean init: " + System.currentTimeMillis());
}
public String print() {
return "[AnoAutoOriginBean] print!!! autoBean == null ? " + (autoBean == null);
}
}
3. 实测演示
同样写一个RestApi进行演示,通过实际的演示结果发现和前面没什么太大的区别
@Autowired
private AutoBean autoBean;
@Autowired
private AutoDIBean autoDIBean;
@Autowired
private AutoFacDIBean autoFacDIBean;
@Autowired
private AnoAutoOriginBean anoAutoOriginBean;
@GetMapping(path = "auto")
public String autoShow() {
Map<String, String> result = new HashMap<>(8);
result.put("autoBean", autoBean == null ? "null" : autoBean.print());
result.put("manualDIBean", autoDIBean == null ? "null" : autoDIBean.print());
result.put("autoFacDIBean",autoFacDIBean == null ? "null" : autoFacDIBean.print());
result.put("anoAutoOriginBean",anoAutoOriginBean.print());
return JSONObject.toJSONString(result);
}
III. 其他
0. 相关
a. 文档
b. 源码
- 工程:spring-boot-demo
- model: 006-dynamicbean
1. 一灰灰Blog
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
3. 扫描关注
一灰灰blog
- 异步加载的基本逻辑与浏览器抓包一般流程
- 左手用R右手Python系列之——表格数据抓取之道
- XML/HTML/JSON——数据抓取过程中不得不知的几个概念
- R语言网络数据抓取的又一个难题,终于攻破了!
- R语言数据清洗实战——高效list解析方案
- 左手用R右手Python系列——循环中的错误异常规避
- SpringBoot2.x开发案例之整合Quartz任务管理系统
- 给出一组非负整数,重新排序组成最大的数
- [机智的机器在学习] TensorFlow实现Kmeans聚类
- [机智的机器在学习] 利用TensorFlow实现多元线性回归分类器
- [数据结构和算法]《算法导论》动态规划笔记(1)
- [数据结构和算法]《算法导论》动态规划笔记(2)
- [算法与数据结构] 《算法导论》堆排序笔记
- [数据结构与算法] 链表的其他类型
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Android通过XListView实现上拉加载下拉刷新功能
- Android自定义View实现圆形切图效果
- Android仿iOS侧滑退出当前界面功能
- android仿支付宝密码输入框效果
- TextInputLayout输入框控件的悬浮标签
- android使用SwipeRefreshLayout实现ListView下拉刷新上拉加载
- Android自定义ScrollView使用自定义监听
- Android开发实现拨打电话与发送信息的方法分析
- Android 3.0引入的异步加载机制Loader
- 自定义注解1-实现spel表达式
- 自定义注解2-动态修改注解的属性值
- 自定义注解3-组合注解
- swagger增加接口版本管理
- 数据安全02-Base64 算法原理
- leetcode之整理字符串