需求场景:

如果现在需要实现一个这样的需求:

根据配置信息动态控制是否创建任意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 绑定配置信息...
    }
}
● 第二步:获取/绑定配置,提供两种方式:
  1. 获取方式一:单个获取
public void setEnvironment(Environment environment) {
         environment.getProperty("com.tuling.bean.bean-class");
         // ToDo: 一个个获取更多配置信息..
}
  1. 获取方式二:通过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也会了, 结合即可:
怎么结合 ? 想想乛◡乛

可以通过这两种方式:

  1. EnvironmentAware(获取配置)+ImportBeanDefinitionRegistrar(创建Bean)
  2. 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.enbaledtrue
○ 输出在这里插入图片描述

● 当com.tuling.bean.enbaledfalse
○ 输出在这里插入图片描述

通过这种方式就可以实现根据配置信息自由启用、禁用某些业务的实现 。 当然我这里只是抛砖引玉,实际开发中可以非常灵活:
可以通过配置创建多个bean、通过更复杂的业务逻辑进行控制等等… 学会的同学给个赞吧。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐