在系统运行过程中,可能由于一些配置项的简单变动需要重新打包启停项目,这对于在运行中的项目会造成数据丢失,客户操作无响应等情况发生,针对这类情况对开发框架进行升级提供yml文件实时修改更新功能
项目基于的是2.0.0.RELEASE版本,所以snakeyaml需要单独引入,高版本已包含在内
- <dependency>
- <groupId>org.yamlgroupId>
- <artifactId>snakeyamlartifactId>
- <version>1.23version>
- dependency>
网上大多数方法是引入spring-cloud-context配置组件调用ContextRefresher的refresh方法达到同样的效果,考虑以下两点未使用
读取resource文件下的文件需要使用ClassPathResource获取InputStream
- public String getTotalYamlFileContent() throws Exception {
- String fileName = "application.yml";
- return getYamlFileContent(fileName);
- }
- public String getYamlFileContent(String fileName) throws Exception {
- ClassPathResource classPathResource = new ClassPathResource(fileName);
- return onvertStreamToString(classPathResource.getInputStream());
- }
- public static String convertStreamToString(InputStream inputStream) throws Exception{
- return IOUtils.toString(inputStream, "utf-8");
- }
我们获取到yml文件内容后可视化显示到前台进行展示修改,将修改后的内容通过yaml.load方法转换成Map结构,再使用yaml.dumpAsMap转换为流写入到文件
- public void updateTotalYamlFileContent(String content) throws Exception {
- String fileName = "application.yml";
- updateYamlFileContent(fileName, content);
- }
- public void updateYamlFileContent(String fileName, String content) throws Exception {
- Yaml template = new Yaml();
- Map
yamlMap = template.load(content); -
- ClassPathResource classPathResource = new ClassPathResource(fileName);
-
- Yaml yaml = new Yaml();
- //字符输出
- FileWriter fileWriter = new FileWriter(classPathResource.getFile());
- //用yaml方法把map结构格式化为yaml文件结构
- fileWriter.write(yaml.dumpAsMap(yamlMap));
- //刷新
- fileWriter.flush();
- //关闭流
- fileWriter.close();
- }
yml属性在程序中读取使用一般有三种
- @Value("${system.systemName}")
- private String systemName;
- @Autowired
- private Environment environment;
-
- environment.getProperty("system.systemName")
- @Component
- @ConfigurationProperties(prefix = "system")
- public class SystemConfig {
- private String systemName;
- }
我们通过environment.getProperty方法读取的配置集合实际是存储在PropertySources中的,我们只需要把键值对全部取出存储在propertyMap中,将更新后的yml文件内容转换成相同格式的ymlMap,两个Map进行合并,调用PropertySources的replace方法进行整体替换即可
但是yaml.load后的ymlMap和PropertySources取出的propertyMap两者数据解构是不同的,需要进行手动转换
propertyMap集合就是单纯的key,value键值对,key是properties形式的名称,例如system.systemName=>xxxxx集团管理系统
ymlMap集合是key,LinkedHashMap的嵌套层次结构,例如system=>(systemName=>xxxxx集团管理系统)
- public HashMap
convertYmlMapToPropertyMap(Map yamlMap) { - HashMap
propertyMap = new HashMap(); - for (String key : yamlMap.keySet()) {
- String keyName = key;
- Object value = yamlMap.get(key);
- if (value != null && value.getClass() == LinkedHashMap.class) {
- convertYmlMapToPropertyMapSub(keyName, ((LinkedHashMap
) value), propertyMap); - } else {
- propertyMap.put(keyName, value);
- }
- }
- return propertyMap;
- }
-
- private void convertYmlMapToPropertyMapSub(String keyName, LinkedHashMap
submMap, Map propertyMap) { - for (String key : submMap.keySet()) {
- String newKey = keyName + "." + key;
- Object value = submMap.get(key);
- if (value != null && value.getClass() == LinkedHashMap.class) {
- convertYmlMapToPropertyMapSub(newKey, ((LinkedHashMap
) value), propertyMap); - } else {
- propertyMap.put(newKey, value);
- }
- }
- }
- String name = "applicationConfig: [classpath:/" + fileName + "]";
- MapPropertySource propertySource = (MapPropertySource) environment.getPropertySources().get(name);
- Map
source = propertySource.getSource(); - Map
map = new HashMap<>(source.size()); - map.putAll(source);
-
- Map
propertyMap = convertYmlMapToPropertyMap(yamlMap); -
- for (String key : propertyMap.keySet()) {
- Object value = propertyMap.get(key);
- map.put(key, value);
- }
- environment.getPropertySources().replace(name, new MapPropertySource(name, map));
不论是Value注解还是ConfigurationProperties注解,实际都是通过注入Bean对象的属性方法使用的,我们先自定注解RefreshValue来修饰属性所在Bean的class
通过实现
InstantiationAwareBeanPostProcessorAdapter接口在系统启动时过滤筛选对应的Bean存储下来,在更新yml文件时通过spring的event通知更新对应
bean的属性即可
- @EventListener
- public void updateConfig(ConfigUpdateEvent configUpdateEvent) {
- if(mapper.containsKey(configUpdateEvent.key)){
- List
fieldPairList = mapper.get(configUpdateEvent.key); - if(fieldPairList.size()>0){
- for (FieldPair fieldPair:fieldPairList) {
- fieldPair.updateValue(environment);
- }
- }
- }
- }
- @Autowired
- private ApplicationContext applicationContext;
-
- for (String key : propertyMap.keySet()) {
- applicationContext.publishEvent(new YamlConfigRefreshPostProcessor.ConfigUpdateEvent(this, key));
- }
YamlConfigRefreshPostProcessor的完整代码如下
- @Component
- public class YamlConfigRefreshPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements EnvironmentAware {
- private Map
> mapper = new HashMap<>(); - private Environment environment;
-
- @Override
- public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
- processMetaValue(bean);
- return super.postProcessAfterInstantiation(bean, beanName);
- }
-
- @Override
- public void setEnvironment(Environment environment) {
- this.environment = environment;
- }
-
- private void processMetaValue(Object bean) {
- Class clz = bean.getClass();
- if (!clz.isAnnotationPresent(RefreshValue.class)) {
- return;
- }
-
- if (clz.isAnnotationPresent(ConfigurationProperties.class)) {
- //@ConfigurationProperties注解
- ConfigurationProperties config = (ConfigurationProperties) clz.getAnnotation(ConfigurationProperties.class);
- for (Field field : clz.getDeclaredFields()) {
- String key = config.prefix() + "." + field.getName();
- if(mapper.containsKey(key)){
- mapper.get(key).add(new FieldPair(bean, field, key));
- }else{
- List
fieldPairList = new ArrayList<>(); - fieldPairList.add(new FieldPair(bean, field, key));
- mapper.put(key, fieldPairList);
- }
- }
- } else {
- //@Valuez注解
- try {
- for (Field field : clz.getDeclaredFields()) {
- if (field.isAnnotationPresent(Value.class)) {
- Value val = field.getAnnotation(Value.class);
- String key = val.value().replace("${", "").replace("}", "");
- if(mapper.containsKey(key)){
- mapper.get(key).add(new FieldPair(bean, field, key));
- }else{
- List
fieldPairList = new ArrayList<>(); - fieldPairList.add(new FieldPair(bean, field, key));
- mapper.put(key, fieldPairList);
- }
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- System.exit(-1);
- }
- }
- }
-
- public static class FieldPair {
- private static PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}",
- ":", true);
- private Object bean;
- private Field field;
- private String value;
-
- public FieldPair(Object bean, Field field, String value) {
- this.bean = bean;
- this.field = field;
- this.value = value;
- }
-
- public void updateValue(Environment environment) {
- boolean access = field.isAccessible();
- if (!access) {
- field.setAccessible(true);
- }
- try {
- if (field.getType() == String.class) {
- String updateVal = environment.getProperty(value);
- field.set(bean, updateVal);
- }
- else if (field.getType() == Integer.class) {
- Integer updateVal = environment.getProperty(value,Integer.class);
- field.set(bean, updateVal);
- }
- else if (field.getType() == int.class) {
- int updateVal = environment.getProperty(value,int.class);
- field.set(bean, updateVal);
- }
- else if (field.getType() == Boolean.class) {
- Boolean updateVal = environment.getProperty(value,Boolean.class);
- field.set(bean, updateVal);
- }
- else if (field.getType() == boolean.class) {
- boolean updateVal = environment.getProperty(value,boolean.class);
- field.set(bean, updateVal);
- }
- else {
- String updateVal = environment.getProperty(value);
- field.set(bean, JSONObject.parseObject(updateVal, field.getType()));
- }
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- field.setAccessible(access);
- }
-
- public Object getBean() {
- return bean;
- }
-
- public void setBean(Object bean) {
- this.bean = bean;
- }
-
- public Field getField() {
- return field;
- }
-
- public void setField(Field field) {
- this.field = field;
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
- }
-
- public static class ConfigUpdateEvent extends ApplicationEvent {
- String key;
-
- public ConfigUpdateEvent(Object source, String key) {
- super(source);
- this.key = key;
- }
- }
-
- @EventListener
- public void updateConfig(ConfigUpdateEvent configUpdateEvent) {
- if(mapper.containsKey(configUpdateEvent.key)){
- List
fieldPairList = mapper.get(configUpdateEvent.key); - if(fieldPairList.size()>0){
- for (FieldPair fieldPair:fieldPairList) {
- fieldPair.updateValue(environment);
- }
- }
- }
- }
- }