美文网首页JAVA随笔码农日历
【SpringBoot 基础系列】实现一个自定义配置加载器(应用

【SpringBoot 基础系列】实现一个自定义配置加载器(应用

作者: 一灰灰blog | 来源:发表于2020-05-07 09:13 被阅读0次
    image

    【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

    配置容器,保存配置与 field 映射关系,提供配置的基本操作

    @Slf4j
    public class MetaContainer {
        private MetaValHolder metaValHolder;
    
        // 保存配置与Field之间的绑定关系
        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

    @ToString
    @EqualsAndHashCode
    public class MetaChangeEvent extends ApplicationEvent {
        private static final long serialVersionUID = -9100039605582210577L;
        private String key;
    
        private String oldVal;
    
        private String newVal;
    
    
        /**
         * Create a new {@code ApplicationEvent}.
         *
         * @param source the object on which the event initially occurred or with
         *               which the event is associated (never {@code null})
         */
        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 绑定的配置

    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 的声明

    @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(常规套路了)

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.git.hui.boot.dynamic.config.DynamicConfig
    

    6. 测试

    上面完成基本功能,接下来进入测试环节,自定义一个配置加载

    @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

    @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 服务,用于查看/更新配置

    @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!";
        }
    }
    

    启动类

    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class);
        }
    }
    

    动图演示配置获取和刷新过程

    image

    配置刷新时,会有日志输出,如下

    image

    II. 其他

    0. 项目

    工程源码

    推荐博文

    1. 一灰灰 Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    一灰灰blog

    相关文章

      网友评论

        本文标题:【SpringBoot 基础系列】实现一个自定义配置加载器(应用

        本文链接:https://www.haomeiwen.com/subject/pthoghtx.html