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. 实现 提供配置与bean属性的绑定关系,我们这里仅提供一个根据配置名获取配置的基础功能,有兴趣的小伙伴可以自行扩展支持SPEL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Target ({ElementType.FIELD, ElementType.TYPE})@Retention (RetentionPolicy.RUNTIME)@Inherited @Documented public @interface MetaVal { String value () default "" ; MetaParser parser () default MetaParser.STRING_PARSER ; }
请注意上面的实现,除了value之外,还有一个parser,因为我们的配置value可能是String,当然也可能是其他的基本类型如int,boolean;所以提供了一个基本的类型转换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 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); } }; }
提供配置的核心类,我们这里只定义了一个接口,具体的配置获取与业务需求相关
1 2 3 4 5 6 7 8 9 public interface MetaValHolder { String getProperty (String key) ; }
为了支持配置刷新,我们提供一个基于Spring事件通知机制的抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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)); } public abstract String doUpdateProperty (String key, String value) ; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } }
这个类,主要提供扫描所有的bean,并获取到@MetaVal
修饰的属性,并初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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); } 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 ) { metaContainer.addInvokeCell(metaVal, bean, field); } } } catch (Exception e) { e.printStackTrace(); System.exit(-1 ); } } }
请注意,上面核心点在metaContainer.addInvokeCell(metaVal, bean, field);
这一行
配置容器,保存配置与field映射关系,提供配置的基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 @Slf 4jpublic class MetaContainer { private MetaValHolder metaValHolder; private Map<String, Set<InvokeCell>> metaCache = new ConcurrentHashMap<>(); public MetaContainer (MetaValHolder metaValHolder) { this .metaValHolder = metaValHolder; } public String getProperty (String key) { return metaValHolder.getProperty(key); } public void addInvokeCell (MetaVal metaVal, Object target, Field field) throws IllegalAccessException { String metaKey = metaVal.value(); if (!metaCache.containsKey(metaKey)) { synchronized (this ) { if (!metaCache.containsKey(metaKey)) { metaCache.put(metaKey, new HashSet<>()); } } } metaCache.get(metaKey).add(new InvokeCell(metaVal, target, field, getProperty(metaKey))); } public void updateMetaVal (String metaKey, String oldVal, String newVal) { Set<InvokeCell> cacheSet = metaCache.get(metaKey); if (CollectionUtils.isEmpty(cacheSet)) { return ; } cacheSet.forEach(s -> { try { s.update(newVal); log.info("update {} from {} to {}" , s.getSignature(), oldVal, newVal); } catch (IllegalAccessException e) { e.printStackTrace(); } }); } @Data public static class InvokeCell { private MetaVal metaVal; private Object target; private Field field; private String signature; private Object value; public InvokeCell (MetaVal metaVal, Object target, Field field, String value) throws IllegalAccessException { this .metaVal = metaVal; this .target = target; this .field = field; field.setAccessible(true ); signature = target.getClass().getName() + "." + field.getName(); this .update(value); } public void update (String value) throws IllegalAccessException { this .value = this .metaVal.parser().parse(value); field.set(target, this .value); } } }
5. Event/Listener 接下来就是事件通知机制的支持了
MetaChangeEvent配置变更事件,提供基本的三个信息,配置key,原value,新value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @ToString @EqualsAndHashCode public class MetaChangeEvent extends ApplicationEvent { private static final long serialVersionUID = -9100039605582210577L ; private String key; private String oldVal; private String newVal; public MetaChangeEvent (Object source) { super (source); } public MetaChangeEvent (Object source, String key, String oldVal, String newVal) { super (source); this .key = key; this .oldVal = oldVal; this .newVal = newVal; } public String getKey () { return key; } public String getOldVal () { return oldVal; } public String getNewVal () { return newVal; } }
MetaChangeListener事件处理器,刷新@MetaVal绑定的配置
1 2 3 4 5 6 7 8 9 10 11 12 public class MetaChangeListener implements ApplicationListener <MetaChangeEvent > { private MetaContainer metaContainer; public MetaChangeListener (MetaContainer metaContainer) { this .metaContainer = metaContainer; } @Override public void onApplicationEvent (MetaChangeEvent event) { metaContainer.updateMetaVal(event.getKey(), event.getOldVal(), event.getNewVal()); } }
6. bean配置 上面五步,一个自定义的配置加载器基本上就完成了,剩下的就是bean的声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Configuration public class DynamicConfig { @Bean @ConditionalOnMissingBean (MetaValHolder.class ) public MetaValHolder metaValHolder () { return key -> null ; } @Bean public MetaContainer metaContainer (MetaValHolder metaValHolder) { return new MetaContainer(metaValHolder); } @Bean public MetaValueRegister metaValueRegister (MetaContainer metaContainer) { return new MetaValueRegister(metaContainer); } @Bean public MetaChangeListener metaChangeListener (MetaContainer metaContainer) { return new MetaChangeListener(metaContainer); } }
以二方工具包方式提供外部使用,所以需要在资源目录下,新建文件META-INF/spring.factories
(常规套路了)
1 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.git.hui.boot.dynamic.config.DynamicConfig
6. 测试 上面完成基本功能,接下来进入测试环节,自定义一个配置加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Component public class MetaPropertyHolder extends AbstractMetaValHolder { public Map<String, String> metas = new HashMap<>(8 ); { metas.put("name" , "一灰灰" ); metas.put("blog" , "https://blog.hhui.top" ); metas.put("age" , "18" ); } @Override public String getProperty (String key) { return metas.getOrDefault(key, "" ); } @Override public String doUpdateProperty (String key, String value) { return metas.put(key, value); } }
一个使用MetaVal
的demoBean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component public class DemoBean { @MetaVal ("name" ) private String name; @MetaVal ("blog" ) private String blog; @MetaVal (value = "age" , parser = MetaParser.INT_PARSER) private Integer age; public String sayHello () { return "欢迎关注 [" + name + "] 博客:" + blog + " | " + age; } }
一个简单的REST服务,用于查看/更新配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @RestController public class DemoAction { @Autowired private DemoBean demoBean; @Autowired private MetaPropertyHolder metaPropertyHolder; @GetMapping (path = "hello" ) public String hello () { return demoBean.sayHello(); } @GetMapping (path = "update" ) public String updateBlog (@RequestParam(name = "key" ) String key, @RequestParam (name = "val" ) String val, HttpServletResponse response) throws IOException { metaPropertyHolder.updateProperty(key, val); response.sendRedirect("/hello" ); return "over!" ; } }
启动类
1 2 3 4 5 6 7 @SpringBootApplication public class Application { public static void main (String[] args) { SpringApplication.run(Application.class ) ; } }
动图演示配置获取和刷新过程
配置刷新时,会有日志输出,如下
II. 其他 0. 项目 工程源码
推荐博文
1. 一灰灰Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
一灰灰blog
Be the first person to leave a comment!