【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)
Spring 中提供了@Value注解,用来绑定配置,可以实现从配置文件中,读取对应的配置并赋值给成员变量;某些时候,我们的配置可能并不是在配置文件中,如存在 db/redis/其他文件/第三方配置服务,本文将手把手教你实现一个自定义的配置加载器,并支持@Value的使用姿势
I. 环境 & 方案设计 1. 环境SpringBoot 2.2.1.RELEASE
IDEA + JDK8
2. 方案设计自定义的配置加载,有两个核心的角色
配置容器 MetaValHolder:与具体的配置打交道并提供配置
配置绑定 @MetaVal:类似@Value注解,用于绑定类属性与具体的配置,并实现配置初始化与配置变更时的刷新
上面@MetaVal提到了两点,一个是初始化,一个是配置的刷新,接下来可以看一下如何支持这两点
a. 初始化初始化的前提是需要获取到所有修饰有这个注解的成员,然后借助MetaValHolder来获取对应的配置,并初始化
为了实现上面这一点,最好的切入点是在 Bean 对象创建之后,获取 bean 的所有属性,查看是否标有这个注解,可以借助InstantiationAwareBeanPostProcessorAdapter来实现
b. 刷新当配置发生变更时,我们也希望绑定的属性也会随之改变,因此我们需要保存配置与bean属性之间的绑定关系
配置变更 与 bean属性的刷新 这两个操作,我们可以借助 Spring 的事件机制来解耦,当配置变更时,抛出一个MetaChangeEvent事件,我们默认提供一个事件处理器,用于更新通过@MetaVal注解绑定的 bean 属性
使用事件除了解耦之外,另一个好处是更加灵活,如支持用户对配置使用的扩展
II. 实现 1. MetaVal 注解提供配置与 bean 属性的绑定关系,我们这里仅提供一个根据配置名获取配置的基础功能,有兴趣的小伙伴可以自行扩展支持 SPEL
@Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface MetaVal { /** * 获取配置的规则 * * @return */ String value() default ""; /** * meta value转换目标对象;目前提供基本数据类型支持 * * @return */ MetaParser parser() default MetaParser.STRING_PARSER; }请注意上面的实现,除了 value 之外,还有一个 parser,因为我们的配置 value 可能是 String,当然也可能是其他的基本类型如 int,boolean;所以提供了一个基本的类型转换器
public interface IMetaParser<T> { T parse(String val); } public enum MetaParser implements IMetaParser { STRING_PARSER { @Override public String parse(String val) { return val; } }, SHORT_PARSER { @Override public Short parse(String val) { return Short.valueOf(val); } }, INT_PARSER { @Override public Integer parse(String val) { return Integer.valueOf(val); } }, LONG_PARSER { @Override public Long parse(String val) { return Long.valueOf(val); } }, FLOAT_PARSER { @Override public Object parse(String val) { return null; } }, DOUBLE_PARSER { @Override public Object parse(String val) { return Double.valueOf(val); } }, BYTE_PARSER { @Override public Byte parse(String val) { if (val == null) { return null; } return Byte.valueOf(val); } }, CHARACTER_PARSER { @Override public Character parse(String val) { if (val == null) { return null; } return val.charAt(0); } }, BOOLEAN_PARSER { @Override public Boolean parse(String val) { return Boolean.valueOf(val); } }; } 2. MetaValHolder提供配置的核心类,我们这里只定义了一个接口,具体的配置获取与业务需求相关
public interface MetaValHolder { /** * 获取配置 * * @param key * @return */ String getProperty(String key); }为了支持配置刷新,我们提供一个基于 Spring 事件通知机制的抽象类
public abstract class AbstractMetaValHolder implements MetaValHolder, ApplicationContextAware { protected ApplicationContext applicationContext; public void updateProperty(String key, String value) { String old = this.doUpdateProperty(key, value); this.applicationContext.publishEvent(new MetaChangeEvent(this, key, old, value)); } /** * 更新配置 * * @param key * @param value * @return */ public abstract String doUpdateProperty(String key, String value); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } 3. MetaValueRegister 配置绑定与初始化这个类,主要提供扫描所有的 bean,并获取到@MetaVal修饰的属性,并初始化
public class MetaValueRegister extends InstantiationAwareBeanPostProcessorAdapter { private MetaContainer metaContainer; public MetaValueRegister(MetaContainer metaContainer) { this.metaContainer = metaContainer; } @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { processMetaValue(bean); return super.postProcessAfterInstantiation(bean, beanName); } /** * 扫描bean的所有属性,并获取@MetaVal修饰的属性 * @param bean */ private void processMetaValue(Object bean) { try { Class clz = bean.getClass(); MetaVal metaVal; for (Field field : clz.getDeclaredFields()) { metaVal = field.getAnnotation(MetaVal.class); if (metaVal != null) { // 缓存配置与Field的绑定关系,并初始化 metaContainer.addInvokeCell(metaVal, bean, field); } } } catch (Exception e) { e.printStackTrace(); System.exit(-1); } } }请注意,上面核心点在metaContainer.addInvokeCell(metaVal, bean, field);这一行
4. MetaContainer