摘要
- 本文內(nèi)容基于springboot2.2.6
- SpringBoot可以通過
@PropertySource(value = "file:demo.properties")
的方式加載外部配置文件朱盐,這樣打好jar包后只要將這個(gè)屬性文件放到相同路徑即可 - 如果能夠在不重啟服務(wù)的情況下就可以重新加載這個(gè)屬性文件绷蹲,就可以很方便的實(shí)現(xiàn)動(dòng)態(tài)更新或渤,那么要怎么做呢?
- github:https://github.com/hanqunfeng/springbootchapter/tree/master/chapter27
思路
SpringCloud可以通過config組件實(shí)現(xiàn)配置的動(dòng)態(tài)加載唾戚,我們也可以將數(shù)據(jù)存在數(shù)據(jù)庫或者緩存中卿操,可是如果只是一個(gè)小項(xiàng)目警检,不想依賴任何中間件,那么就可以通過如下的方式實(shí)現(xiàn)害淤。
- 獲取所有注解了
@PropertySource
的對(duì)象扇雕,并且獲取其value屬性數(shù)組中是以file:
開頭的文件路徑 - 判斷是否同時(shí)注解了
@ConfigurationProperties
,并且獲取其prefix
的值 - 對(duì)每個(gè)屬性文件進(jìn)行遍歷窥摄,通過反射找到對(duì)象的field名稱(去除
prefix
后的名字)洼裤,并將屬性值賦值給該field
代碼
這個(gè)類要注冊(cè)到spring上下文,并在需要的地方調(diào)用該對(duì)象的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>動(dòng)態(tài)加載外部屬性處理類</p>
*/
@Slf4j
@Component
public class ExternalPropertiesRefresh {
@Autowired
private ConfigurableListableBeanFactory configurableListableBeanFactory;
/**
* <p>根據(jù)屬性名獲取屬性值</p>
*
* @param fieldName bean的屬性名稱
* @param object bean對(duì)象
* @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>根據(jù)屬性名設(shè)置屬性值</p>
*
* @param fieldName bean的屬性名稱
* @param object bean對(duì)象
* @param paramTypes set方法參數(shù)類型
* @param params set方法參數(shù)值
* @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對(duì)象</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的注冊(cè)名稱,默認(rèn)類名稱首字母小寫
*/
@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) {
//將屬性綁定到對(duì)象
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請(qǐng)求刷新配置文件
啟動(dòng)服務(wù)器后移国,任意修改屬性文件的值,然后請(qǐng)求/refresh道伟,即可重新加載全部屬性文件迹缀,然后請(qǐng)求/demo查看是否生效,也可以請(qǐng)求/propertiesDemo/refresh蜜徽,指定要刷新的對(duì)象祝懂。
@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]=阿爾卑斯山