前言
有时候我们需要在应用启动的时候根据某些规则动态创建一个Bean对象,我们应该要考虑一些事情。
- 对象是否在创建后才能使用,这样避免有些对象在定义的时候就由于Bean创建的顺序问题导致Bean都没有初始化给容器就被使用了,这样启动的时候出现一些null异常。
- 对象的属性或描述从第三方配置中心获取,这些配置是否能在Bean定义中就能获取到。
大概流程如图:
动态创建Bean的两种方式
1、ImportBeanDefinitionRegistrar
第一步:创建实现ImportBeanDefinitionRegistrar接口的类, 并实现registerBeanDefinitions方法DeanDefintion的注册即可交给Spring去初始化
@Configuration
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
GenericBeanDefinition beandefinition=new GenericBeanDefinition();
beandefinition.setBeanClassName("你的类全路径");
beandefinition.getPropertyValues().add("id",1);
beandefinition.getPropertyValues().add("name","cdg");
registry.registerBeanDefinition("testComponent",beandefinition);
}
}
第二步:结合@Import让它生效,两者搭配使用。
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
2、BeanDefinitionRegistryPostProcessor
此方式有两种定义方式可以玩,一种是构造函数,一种是不用构造函数。
@Configuration
public class MyBeanDefinitionRegistryPostProcessor implements EnvironmentAware, BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
// 构造函数使用方式
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(你需要创建的类.class);
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, tenants);
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
beanDefinitionRegistry.registerBeanDefinition("dynamicRedisTemplate", beanDefinition);
// 非构造函数使用方式
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(你需要创建的类.class);
beanDefinitionBuilder.setInitMethodName("init");
beanDefinitionBuilder.setDestroyMethodName("close");
beanDefinitionBuilder.addPropertyValue("url", config.getUrl());
beanDefinitionRegistry.registerBeanDefinition(config.getDataName(), beanDefinition);
}
}
以上的代码相信大家琢磨一下也能看的懂,我假设要实现一个DruidDataSource数据源Bean,该类里面有init和close方法需要我去指定,所以里面就写了这个例子。
配置文件加载
这点其实应该要提前说明的,这里有个坑要淌很久时间才能熟悉其门道。
我在做这个POC的时候用的配置文件是一个yml文件在classpath下,我用Spring的@Configuration定义了解析Bean,但是在这里就是得不到值,都是空的。后来我看了Spring的启动流程才发现,BeanDefinition是最早去创建的,@Configuration定义的配置类是在他之后才会被加载,用@Condition注解也没作用。
再后来发现只能去实现EnvironmentAware从环境变量中拿,然后又担心第三方Apollo是否比他还要滞后,但查看源码后发现Apollo实现的配置加载是在这个之前并且提交给了Spring的环境变量容器,所以我用 String json = environment.getProperty("dynamic.datasource.json");
是可以拿到第三方配置中心里面的配置。
所以我就定义了一个统一初始化配置文件转对象的类,但是要注意只转换一次,避免多线程使用的不安全行为,请看我的实现
public final class JsonInit {
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
private static List<Tenants> tenants;
public static List<Tenants> doInit(String json) {
if (!INITIALIZED.compareAndSet(false, true)) {
return tenants;
}
tenants = JSON.parseArray(json, Tenants.class);
if (tenants == null) {
log.warn("dynamic.datasource.json is null!");
tenants = new ArrayList<>();
}
return tenants;
}
}
到此就大概能完整了。