【基础系列】Bean之动态注册

文章目录
  1. I. 手动注册Bean方式
    1. 1. 核心实现类
    2. 2. 测试用例
      1. a. 无其他Bean依赖
      2. b. 依赖其他Bean
      3. c. 普通Bean依赖主动注册的Bean
      4. d. Bean注册实现
    3. 3. 实测演示
  2. II. BeanDefinitionRegistryPostProcessor扩展方式
    1. 1. 实现类
    2. 2. 测试用例
    3. 3. 实测演示
  3. III. 其他
    1. 0. 相关
      1. a. 文档
      2. b. 源码
    2. 1. 一灰灰Blog
    3. 2. 声明
    4. 3. 扫描关注

Spring中的Bean除了前面提到的几种JavaConfig或者@Component等注解标识之外,也是可以动态的向Spring容器注册的,本篇博文将主要介绍

  • 如何向Spring容器注册Bean
  • 如何引用主动注册的Bean
  • 注册的Bean中,如果依赖其他的Bean,怎么操作

I. 手动注册Bean方式

1. 核心实现类

以前也写过关于动态注册Bean的博文,如 180804-Spring之动态注册bean

我们的实现方式和上面也没什么区别,依然是借助BeanDefinition来创建Bean定义并注册到BeanFactory中,具体实现的核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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, 单纯的供其他地方使用,这种情况下,主要需要测试的就是别人可以通过什么方式来使用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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可否?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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中,用于测试演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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. 实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@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

1
2
3
4
5
6
public class AutoBean {

public String print() {
return "[AutoBean] " + System.currentTimeMillis();
}
}

一个依赖外部Bean的AutoDIBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 (用于前面的实现类中的第二个方法的注册方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
6
7
8
9
10
11
12
13
@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进行演示,通过实际的演示结果发现和前面没什么太大的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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. 源码


1. 一灰灰Blog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

一灰灰blog

QrCode

知识星球

goals


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到