美文网首页
SpringBoot动态更新外部属性文件

SpringBoot动态更新外部属性文件

作者: 飘逸峰 | 来源:发表于2020-04-18 00:22 被阅读0次

    摘要

    • 本文内容基于springboot2.2.6
    • SpringBoot可以通过@PropertySource(value = "file:demo.properties")的方式加载外部配置文件,这样打好jar包后只要将这个属性文件放到相同路径即可
    • 如果能够在不重启服务的情况下就可以重新加载这个属性文件,就可以很方便的实现动态更新,那么要怎么做呢?
    • github:https://github.com/hanqunfeng/springbootchapter/tree/master/chapter27

    思路

    SpringCloud可以通过config组件实现配置的动态加载,我们也可以将数据存在数据库或者缓存中,可是如果只是一个小项目,不想依赖任何中间件,那么就可以通过如下的方式实现。

    • 获取所有注解了@PropertySource的对象,并且获取其value属性数组中是以file:开头的文件路径
    • 判断是否同时注解了@ConfigurationProperties,并且获取其prefix的值
    • 对每个属性文件进行遍历,通过反射找到对象的field名称(去除prefix后的名字),并将属性值赋值给该field

    代码

    这个类要注册到spring上下文,并在需要的地方调用该对象的refresh方法即可重新加载所有外部属性文件。

    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.core.io.FileSystemResource;
    import org.springframework.core.io.support.PropertiesLoaderUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.util.*;
    
    /**
     * <p>动态加载外部属性处理类</p>
     */
    @Slf4j
    @Component
    public class ExternalPropertiesRefresh {
    
        @Autowired
        private ConfigurableListableBeanFactory configurableListableBeanFactory;
    
        /**
         * <p>根据属性名获取属性值</p>
         *
         * @param fieldName bean的属性名称
         * @param object    bean对象
         * @return java.lang.Object get方法返回值
         * @author hanqf
         */
        private Object getFieldValueByName(String fieldName, Object object) {
            try {
                String firstLetter = fieldName.substring(0, 1).toUpperCase();
                String getter = "get" + firstLetter + fieldName.substring(1);
                Method method = object.getClass().getMethod(getter, new Class[]{});
                Object value = method.invoke(object, new Object[]{});
                return value;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                return null;
            }
        }
    
    
        /**
         * <p>根据属性名设置属性值</p>
         *
         * @param fieldName  bean的属性名称
         * @param object     bean对象
         * @param paramTypes set方法参数类型
         * @param params     set方法参数值
         * @author hanqf
         */
        private void setFieldValueByName(String fieldName, Object object, Class[] paramTypes, Object[] params) {
            try {
                String firstLetter = fieldName.substring(0, 1).toUpperCase();
                String setter = "set" + firstLetter + fieldName.substring(1);
                Method method = object.getClass().getMethod(setter, paramTypes);
                method.invoke(object, params);
    
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    
    
        /**
         * <p>获取属性名称,去除前缀</p>
         *
         * @param key   属性key
         * @param prefix 属性key前缀
         * @return java.lang.String
         * @author hanqf
         */
        private String fieldName(String key, String prefix) {
            if (StringUtils.hasText(prefix)) {
                return key.replace(prefix + ".", "");
            }
            return key;
        }
    
        /**
         * <p>将属性文件值绑定到bean对象</p>
         *
         * @param bean
         * @param properties
         * @param prefix
         * @author hanqf
         */
        private Object bind(Object bean, Properties[] properties, String prefix) {
            String fieldName = "";//属性名称
            String pValue = "";//属性值
            String[] sp = null; //map属性分割key和value
            for (Properties pro : properties) {
                Map<String, Map<String, String>> fidleMap = new HashMap<>();
                Map<String, Set<String>> fidleSet = new HashMap<>();
                Map<String, List<String>> fidleList = new HashMap<>();
                //遍历属性
                for (Object key : pro.keySet()) {
                    pValue = (String) (pro.get(key));
                    fieldName = fieldName((String) key, prefix);
    
                    //map
                    sp = fieldName.split("\\.");
                    if (sp.length == 2) {
                        fieldName = sp[0];
                    }
    
                    //list&&set
                    if (fieldName.indexOf("[") > 0) {
                        fieldName = fieldName.substring(0, fieldName.indexOf("["));
                    }
    
                    //属性类型
                    Object object = getFieldValueByName(fieldName, bean);
    
                    //类型匹配
                    if (object instanceof Map) {
                        if (fidleMap.get(fieldName) != null) {
                            object = fidleMap.get(fieldName);
                        } else {
                            object = new HashMap<String, String>();
                        }
                        if (sp.length == 2) {
                            ((Map) object).put(sp[1], pValue);
                            fidleMap.put(fieldName, (Map<String, String>) object);
                        }
                    } else if (object instanceof Set) {
                        if (fidleSet.get(fieldName) != null) {
                            object = fidleSet.get(fieldName);
                        } else {
                            object = new HashSet<String>();
                        }
                        ((Set) object).add(pValue);
                        fidleSet.put(fieldName, (Set<String>) object);
                    } else if (object instanceof List) {
                        if (fidleList.get(fieldName) != null) {
                            object = fidleList.get(fieldName);
                        } else {
                            object = new ArrayList<String>();
                        }
                        ((List) object).add(pValue);
                        fidleList.put(fieldName, (List<String>) object);
                    } else if (object instanceof String) {
                        setFieldValueByName(fieldName, bean, new Class[]{String.class}, new Object[]{pValue});
                    } else if (object instanceof Integer) {
                        setFieldValueByName(fieldName, bean, new Class[]{Integer.class}, new Object[]{Integer.valueOf(pValue)});
                    } else if (object instanceof Long) {
                        setFieldValueByName(fieldName, bean, new Class[]{Long.class}, new Object[]{Long.valueOf(pValue)});
                    } else if (object instanceof Double) {
                        setFieldValueByName(fieldName, bean, new Class[]{Double.class}, new Object[]{Double.valueOf(pValue)});
                    } else if (object instanceof Float) {
                        setFieldValueByName(fieldName, bean, new Class[]{Float.class}, new Object[]{Float.valueOf(pValue)});
                    }
                }
    
                //map类型赋值
                if (fidleMap.size() > 0) {
                    for (String fname : fidleMap.keySet()) {
                        setFieldValueByName(fname, bean, new Class[]{Map.class}, new Object[]{fidleMap.get(fname)});
                    }
                }
    
                //set类型赋值
                if (fidleSet.size() > 0) {
                    for (String fname : fidleSet.keySet()) {
                        setFieldValueByName(fname, bean, new Class[]{Set.class}, new Object[]{fidleSet.get(fname)});
                    }
                }
    
                //list类型赋值
                if (fidleList.size() > 0) {
                    for (String fname : fidleList.keySet()) {
                        setFieldValueByName(fname, bean, new Class[]{List.class}, new Object[]{fidleList.get(fname)});
                    }
                }
    
            }
    
            return bean;
        }
    
    
        /**
         * <p>刷新指定属性类</p>
         * @author hanqf
         * @param beanName bean的注册名称,默认类名称首字母小写
         */
        @SneakyThrows
        public void refresh(String beanName){
            Class<?> cls = configurableListableBeanFactory.getType(beanName);
            Object bean = configurableListableBeanFactory.getBean(cls);
            Properties[] propertiesArray = null;
            String prefix = "";
            if (cls.getAnnotations() != null && cls.getAnnotations().length > 0) {
                for (Annotation annotation : cls.getAnnotations()) {
    
                    if (annotation instanceof PropertySource) {
                        PropertySource propertySource = (PropertySource) annotation;
                        String[] values = propertySource.value();
                        if (values.length > 0) {
                            propertiesArray = new Properties[values.length];
                            for (int i = 0; i < values.length; i++) {
                                //如果引用的是外部文件,则重新加载
                                if (values[i].startsWith("file:")) {
                                    String path = values[i].replace("file:", "");
                                    Properties properties = PropertiesLoaderUtils.loadProperties(new FileSystemResource(path));
                                    propertiesArray[i] = properties;
                                }
                            }
                        }
                    }
    
                    if (annotation instanceof ConfigurationProperties) {
                        ConfigurationProperties configurationProperties = (ConfigurationProperties) annotation;
                        prefix = configurationProperties.prefix();
                    }
    
                }
            }
    
            if (propertiesArray != null && propertiesArray.length > 0) {
                //将属性绑定到对象
                bind(bean, propertiesArray, prefix);
    
            }
        }
    
    
        /**
         * <p>重新加载属性文件</p>
         *
         * @author hanqf
         */
        @SneakyThrows
        public void refresh() {
            String[] ary = configurableListableBeanFactory.getBeanNamesForAnnotation(PropertySource.class);
            if (ary != null && ary.length > 0) {
                for (String beanName : ary) {
                    //通过Spring的beanName获取bean的类型
                    refresh(beanName);
                }
            }
        }
    }
    
    

    下面通过http请求刷新配置文件

    启动服务器后,任意修改属性文件的值,然后请求/refresh,即可重新加载全部属性文件,然后请求/demo查看是否生效,也可以请求/propertiesDemo/refresh,指定要刷新的对象。

    @RestController
    public class DemoController {
        @Autowired
        private ExternalPropertiesRefresh externalPropertiesRefresh;
    
        @Autowired
        private PropertiesDemo propertiesDemo;
    
    
        @RequestMapping("/refresh")
        public String refreshpro() {
            externalPropertiesRefresh.refresh();
            return "refresh properties success";
        }
    
        @RequestMapping("/{beanName}/refresh")
        public String refreshProByBeanName(@PathVariable String beanName) {
            externalPropertiesRefresh.refresh(beanName);
            return "refresh properties success for " + beanName;
        }
    
        @RequestMapping("/demo")
        public PropertiesDemo demo() {
            return propertiesDemo;
        }
    
    }
    

    PropertiesDemo.java

    @Component
    @PropertySource(value = "file:demo.properties",encoding = "utf-8")
    @ConfigurationProperties(prefix = "demo.data")
    @Data
    public class PropertiesDemo {
        private Map<String, String> map = new HashMap<>();
        private Map<String, String> map2 = new HashMap<>();
        private Set<String> set = new HashSet<>();
        private Set<String> set2 = new HashSet<>();
        private List<String> list = new ArrayList<>();
        private List<String> list2 = new ArrayList<>();
    
        private String name;
        private Integer age;
        private Double salary;
    }
    
    

    demo.properties

    demo.data.map.client=client
    demo.data.map.token=token
    
    demo.data.map2.client=client
    demo.data.map2.token=token
    
    demo.data.name=张三
    demo.data.age=20
    demo.data.salary=12345.67
    
    demo.data.set[0]=beijing
    demo.data.set[1]=shanghai
    demo.data.set[2]=tianjin
    
    demo.data.set2[0]=guangzhou
    demo.data.set2[1]=shenzheng
    demo.data.set2[2]=hangzhou
    
    
    demo.data.list[0]=南极
    demo.data.list[1]=北极
    demo.data.list[2]=赤道
    
    demo.data.list2[0]=喜马拉雅山
    demo.data.list2[1]=噶麦斯山
    demo.data.list2[2]=阿尔卑斯山
    

    相关文章

      网友评论

          本文标题:SpringBoot动态更新外部属性文件

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