SpringBoot动态更新外部属性文件

摘要

  • 本文内容基于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.*;

/**
 * 

动态加载外部属性处理类

*/ @Slf4j @Component public class ExternalPropertiesRefresh { @Autowired private ConfigurableListableBeanFactory configurableListableBeanFactory; /** *

根据属性名获取属性值

* * @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; } } /** *

根据属性名设置属性值

* * @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); } } /** *

获取属性名称,去除前缀

* * @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; } /** *

将属性文件值绑定到bean对象

* * @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> fidleMap = new HashMap<>(); Map> fidleSet = new HashMap<>(); Map> 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(); } if (sp.length == 2) { ((Map) object).put(sp[1], pValue); fidleMap.put(fieldName, (Map) object); } } else if (object instanceof Set) { if (fidleSet.get(fieldName) != null) { object = fidleSet.get(fieldName); } else { object = new HashSet(); } ((Set) object).add(pValue); fidleSet.put(fieldName, (Set) object); } else if (object instanceof List) { if (fidleList.get(fieldName) != null) { object = fidleList.get(fieldName); } else { object = new ArrayList(); } ((List) object).add(pValue); fidleList.put(fieldName, (List) 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; } /** *

刷新指定属性类

* @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); } } /** *

重新加载属性文件

* * @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 map = new HashMap<>();
    private Map map2 = new HashMap<>();
    private Set set = new HashSet<>();
    private Set set2 = new HashSet<>();
    private List list = new ArrayList<>();
    private List 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动态更新外部属性文件)