原理参考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上也有对应的方法。