需求场景:
如果现在需要实现一个这样的需求:
根据配置信息动态控制是否创建任意Bean
通常我们会定义这样的配置:
application.yml: 实现业务需求:根据enbaled
控制下面bean-class
是否创建
com:tuling:bean:enbaled: true #业务需求:根据enbaled控制下面bean-class是否创建bean-class: com.tuling.beans.TestComponentproperties:id: 1name: 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启用@Datapublic class BeanProperties {private Boolean enbaled;private Class<?> beanClass;private Map<String,Object> properties;}
3、EnvironmentAware
Spring提供很多XXXAware接口、其中EnvironmentAware接口就可以通过其提供的Environment动态获取。
第一步:实现EnvironmentAware接口
@Componentpublic class TestEnvironmentAware implements EnvironmentAware {@Overridepublic void setEnvironment(Environment environment) {// Todo 绑定配置信息...}}
● 第二步:获取/绑定配置,提供两种方式:
获取方式一:单个获取public void setEnvironment(Environment environment) {environment.getProperty("com.tuling.bean.bean-class");// ToDo: 一个个获取更多配置信息..}
获取方式二:通过Binder绑定到properties对象
@Overridepublic 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容器的加载过程,可以学习我讲的视频:这里就不过多阐述它的创建过程/video/BV1pL4y1p7Fa?spm_id_from=333.999.0.0
如果想动态注册Bean,我们需要找到“定义态”的扩展接口可以通过先动态注册BeanDefintion即可,Spring提供了动态注册BeanDefinition的接口:
1、ImportBeanDefinitionRegistrar
第一步:创建实现ImportBeanDefinitionRegistrar接口的类, 演示了一个DeanDefintion的注册
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic 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 {@Overridepublic 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)
@Componentpublic class MyBeanDefinitionRegistryPostProcessor implementsBeanDefinitionRegistryPostProcessor,EnvironmentAware {BeanProperties beanProperties;// 根据配置信息进行逻辑控制 动态注册BeanDefintion从而创建Bean@Overridepublic 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);}// 绑定配置信息@Overridepublic void setEnvironment(Environment environment) {BindResult<BeanProperties> bindResult = Binder.get(environment).bind("com.tuling.bean", BeanProperties.class);beanProperties= bindResult.get();}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}}
测试
@Beanpublic CommandLineRunner runner(@Autowired(required = false)TestComponent testComponent){return new CommandLineRunner() {@Overridepublic void run(String... args) throws Exception {System.out.println(testComponent);}};}
● 当com.tuling.bean.enbaled
为true
○ 输出
● 当com.tuling.bean.enbaled
为false
○ 输出
通过这种方式就可以实现根据配置信息自由启用、禁用某些业务的实现 。 当然我这里只是抛砖引玉,实际开发中可以非常灵活:
可以通过配置创建多个bean、通过更复杂的业务逻辑进行控制等等… 学会的同学给个赞吧。
如果觉得《SpringBoot根据配置文件动态创建Bean》对你有帮助,请点赞、收藏,并留下你的观点哦!