Spring动态从配置文件定义bean

原理参考ImportBeanDefinitionRegistrar+SPI简化Spring开发

有时候需要从配置文件(.yml或.json)反序列化成Object并注入到context中,例如我喜欢用一个yaml文件描述caffeine缓存定义:

- {cacheName: s30, spec: "initialCapacity=100,maximumSize=500,expireAfterWrite=30s"}
- {cacheName: m1, spec: "initialCapacity=100,maximumSize=500,expireAfterWrite=1m"}
- {cacheName: m2, spec: "initialCapacity=100,maximumSize=200,expireAfterWrite=2m"}

希望能自动扫描到该配置文件并装配成CaffeineCache.

首先我们定义一个Factory实现自动定义bean到context,支持注解方式以及手动指定方式。

定义bean的描述

@Data
@Accessors(chain = true)
public class DefineBeanConfig {

    private DefineType type;
    private String beanName;
    private String srcLocation;

}

定义注入的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface BeanDefine {

    /**
     * 定义类型
     */
    DefineType type() default DefineType.YAML;

    /**
     * bean名
     */
    String name() default "";

    /**
     * 源文件
     */
    String src() default "";

}

那么在一个class上添加了该注解,会被Factory扫描到并从指定的源文件反序列化出来成一个bean注入到context中。

Factory源码,重点是根据定义或扫描得到Multimap<Class<?>, DefineBeanConfig>,然后在InitializingBean时读取对应的配置文件,反序列化后注入到context。

现在我们对其进一步简化,使spring在初始阶段自动处理。
定义支持自动处理的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@EnableAutoRegistrar
@Repeatable(EnableAutoDefineBeans.class)
public @interface EnableAutoDefineBean {

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@EnableAutoRegistrar
public @interface EnableAutoDefineBeans {

    EnableAutoDefineBean[] value();

}

按照ImportBeanDefinitionRegistrar+SPI简化Spring开发中定义的方法,我们定义对应的SPI的Handler

public class EnableAutoDefineBeanHandler implements ConfigurationRegisterHandler {

    private static final AtomicInteger COUNT = new AtomicInteger(0);

    @Override
    public void registerBeanDefinitions(RegisterBeanDefinitionContext context) {
        Set<AnnotationAttributes> annotationAttributes = SpringAnnotationConfigUtils.attributesForRepeatable(
                context.getImportingClassMetadata(), EnableAutoDefineBeans.class, EnableAutoDefineBean.class);
        if (CollectionUtils.isEmpty(annotationAttributes)) {
            return;
        }
        for (AnnotationAttributes attribute : annotationAttributes) {
            final String[] basePackages = attribute.getStringArray("basePackages");
            if (basePackages != null && basePackages.length > 0) {
                registerBean(context.getRegistry(), new DefineBeanFactory(basePackages));
            }
            final Class<?>[] basePackageClasses = attribute.getClassArray("basePackageClasses");
            if (basePackageClasses != null && basePackageClasses.length > 0) {
                registerBean(context.getRegistry(), new DefineBeanFactory(basePackageClasses));
            }
        }
    }

    private void registerBean(BeanDefinitionRegistry registry, DefineBeanFactory factory) {
        String beanName = DefineBeanFactory.class.getName() + "_AUTO_" + COUNT.incrementAndGet();
        BeanDefinitionBuilder beanDefinitionBuilder =
                BeanDefinitionBuilder.genericBeanDefinition(DefineBeanFactory.class);
        beanDefinitionBuilder.addPropertyValue("beanDefineMap", factory.getBeanDefineMap());
        registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());
    }

    @Override
    public int getOrder() {
        return 0;
    }

}

在EnableAutoDefineBeanHandler中我们完成DefineBeanFactory的自动注入,借助DefineBeanFactory实现那些@BeanDefine的class自动实例化并注入bean

@Configuration
@EnableAutoDefineBean(basePackages = {"com.alpha.coding.example"})
public class EnableAutoDefineBeanConfiguration {
}
@Data
@Accessors(chain = true)
@BeanDefine(type = DefineType.YAML, name = "CaffeineCacheConfig", src = "caffeine.yml")
public class CaffeineCacheConfig {

    private String cacheName;
    private String loaderName;
    private boolean allowNullValues = true;
    private String spec;

}

CaffeineCacheConfig上有@BeanDefine注解,在@EnableAutoDefineBean配置的扫描路径类,会被自动处理。由于其配置文件是数组,会实例化成多个bean并注入。
至于如何根据CaffeineCacheConfig的bean自动注册CaffeineCache实例小伙伴们可自行研究哈,GitHub上也有对应的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值