需求场景:
如果现在需要实现一个这样的需求:
根据配置信息动态控制是否创建任意Bean
通常我们会定义这样的配置:
application.yml: 实现业务需求:根据enbaled
控制下面bean-class
是否创建
com:
tuling:
bean:
enbaled: true #业务需求:根据enbaled控制下面bean-class是否创建
bean-class: com.tuling.beans.TestComponent
properties:
id: 1
name: xushu
那怎么根据配置动态创建呢:
解:
可以把这个需求拆成获取配置信息
和 动态创建Bean
这两步,逐个分析,最后组合即可:
获取配置信息的几种方式:
1、@Value
但是通过@Value单个获取;一个个设置,太麻烦
@Value("${com.tuling.bean.bean-class}")
private Class<?> beanClass;
// Todo... 一个个获取
2、@ConfigurationProperties
通过@ConfigurationProperties(prefix = “com.tuling”)可以批量获取,比较方便
@ConfigurationProperties("com.tuling.bean")
@Component // 如果是自动配置类 请通过@EnableConfigurationProperties启用
@Data
public class BeanProperties {
private Boolean enbaled;
private Class<?> beanClass;
private Map<String,Object> properties;
}
3、EnvironmentAware
Spring提供很多XXXAware接口、其中EnvironmentAware接口就可以通过其提供的Environment动态获取。
第一步:实现EnvironmentAware接口
@Component
public class TestEnvironmentAware implements EnvironmentAware {
@Override
public void setEnvironment(Environment environment) {
// Todo 绑定配置信息...
}
}
● 第二步:获取/绑定配置,提供两种方式:
- 获取方式一:单个获取
public void setEnvironment(Environment environment) {
environment.getProperty("com.tuling.bean.bean-class");
// ToDo: 一个个获取更多配置信息..
}
- 获取方式二:通过Binder绑定到properties对象
@Override
public void setEnvironment(Environment environment) {
BindResult<BeanProperties> bindResult = Binder.get(environment).bind("com.tuling.bean", BeanProperties.class);
BeanProperties beanProperties= bindResult.get();
}
ok. 3种 获取配置信息的几种方式,你用哪种? 不知道?那接着看吧
动态创建Bean的几种方式:
想想怎么创建bean? 什么?! 用@Bean ??? to simple!!!
注意!我们需要的是动态!动态!!是在运行过程中经过逻辑代码创建Bean, 不是通过配置、 @Component这种配置方式,这种方式不能自由控制业务逻辑。
想要动态创建Bean先了解Bean创建的大概过程(如果知道,可以跳过章节):
我把Bean的创建过程分为三步:
(出生—> 发育—> 成熟)
相信大家都用喜欢成熟的(徐庶老师也是)想啥呢!正经点:我们在spring应用中也都是用spring帮我们创建的最终的成熟的那个, 但是在这个需求中我们需要在spring发育之前创建,否则无法完成根据配置动态创建,因为你不能直接塞一个成熟的bean给spring容器,那叫早熟!这样bean不健康!!她需要有个过程。
如果想学习更多spring IOC容器的加载过程,可以学习我讲的视频:这里就不过多阐述它的创建过程https://www.bilibili.com/video/BV1pL4y1p7Fa?spm_id_from=333.999.0.0
如果想动态注册Bean,我们需要找到“定义态”的扩展接口可以通过先动态注册BeanDefintion即可,Spring提供了动态注册BeanDefinition的接口:
1、ImportBeanDefinitionRegistrar
第一步:创建实现ImportBeanDefinitionRegistrar接口的类, 演示了一个DeanDefintion的注册
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
GenericBeanDefinition beandefinition=new GenericBeanDefinition();
beandefinition.setBeanClassName("com.tuling.beans.TestComponent");
beandefinition.getPropertyValues().add("id",1);
beandefinition.getPropertyValues().add("name","图灵");
registry.registerBeanDefinition("testComponent",beandefinition);
}
}
第二步:结合@Import让它生效
@Import(MyImportBeanDefinitionRegistrar.class)
2、BeanDefinitionRegistryPostProcessor
创建实现BeanDefinitionRegistryPostProcessor接口的类, 演示一个DeanDefintion的注册
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
GenericBeanDefinition beandefinition=new GenericBeanDefinition();
beandefinition.setBeanClassName("com.tuling.beans.TestComponent");
beandefinition.getPropertyValues().add("id",1);
beandefinition.getPropertyValues().add("name","图灵");
registry.registerBeanDefinition("testComponent",beandefinition);
}
}
3、通过BeanFactoryPostProcessor
BeanFactoryPostProcessor也可以,但是BeanDefinitionRegistryPostProcessor的职责没有BeanFactoryPostProcessor这么明确,BeanFactoryPostProcessor就是是用来注册的,及其他方式就不演示了。
根据配置信息动态创建Bean:
OK. 现在读取信息会了, 动态创建Bean也会了, 结合即可:
怎么结合 ? 想想乛◡乛
可以通过这两种方式:
- EnvironmentAware(获取配置)+ImportBeanDefinitionRegistrar(创建Bean)
- EnvironmentAware(获取配置)+BeanDefinitionRegistryPostProcessor(创建Bean)
通过@Value 和@ConfigurationProperties 注解方式获取配置为什么不可以?Why?~
因为顺序原因
!这里就要清楚:
@Value 和@ConfigurationProperties注解依赖BeanPostProcessor解析
,要调用BeanPostProcessor就要先注册,而BeanPostProcessor的注册是在BeanDefinition的注册之后的。
所以在注册BeanDefinition时是获取不到注解绑定的配置信息的:
实现
EnvironmentAware(获取配置)+BeanDefinitionRegistryPostProcessor(创建Bean)
@Component
public class MyBeanDefinitionRegistryPostProcessor implements
BeanDefinitionRegistryPostProcessor,
EnvironmentAware {
BeanProperties beanProperties;
// 根据配置信息进行逻辑控制 动态注册BeanDefintion从而创建Bean
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
GenericBeanDefinition beandefinition=new GenericBeanDefinition();
beandefinition.setBeanClass(beanProperties.getBeanClass());
Map<String, Object> properties = beanProperties.getProperties();
for (String propertyName : properties.keySet()) {
beandefinition.getPropertyValues().add(propertyName,properties.get(propertyName));
beandefinition.getPropertyValues().add(propertyName,properties.get(propertyName));
}
registry.registerBeanDefinition(beandefinition.getBeanClass().getName(),beandefinition);
}
// 绑定配置信息
@Override
public void setEnvironment(Environment environment) {
BindResult<BeanProperties> bindResult = Binder.get(environment).bind("com.tuling.bean", BeanProperties.class);
beanProperties= bindResult.get();
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
测试
@Bean
public CommandLineRunner runner(
@Autowired(required = false)
TestComponent testComponent){
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
System.out.println(testComponent);
}
};
}
● 当com.tuling.bean.enbaled
为true
○ 输出
● 当com.tuling.bean.enbaled
为false
○ 输出
通过这种方式就可以实现根据配置信息自由启用、禁用某些业务的实现 。 当然我这里只是抛砖引玉,实际开发中可以非常灵活:
可以通过配置创建多个bean、通过更复杂的业务逻辑进行控制等等… 学会的同学给个赞吧。