文章目录
- 一,序言
- 二,准备工作
- 1. pom.xml引入组件
- 2. 配置文件示例
- 三,自定义配置项动态刷新编码实现
- 1. 定义自定义配置项对象
- 2. 添加注解实现启动时自动注入
- 3. 实现yml文件监听以及文件变化处理
- 四,yaml文件转换为java对象
- 1. 无法使用前缀绑定的处理
- 2. 实现yaml文件转换java对象
- 五、完整代码
- 1. 代码结构
- 2. 完整代码备份
- 3. 运行说明
一,序言
springboot 配置文件一般以yaml方式保存,除了系统配置项如spring、server等外,还有我们自定义的配置项,方便系统启动时自动注入。
自定义的配置项一般是动态配置项,在系统运行过程中,可能需要在线修改,来实现自定义的配置项不停服更新,也就是类似于spring-cloud-starter-config的动态刷新。
由于系统不重启,无法通过自动注入的方式自动更新自定义配置, 这儿便需要我们手动加载yaml文件,转换为java对象,将变化赋值到spring管理的对象中
二,准备工作
采用最常见的snakeyaml、YAMLMapper来实现yaml文件处理。
1. pom.xml引入组件
因 jackson-dataformat-yaml 已经包含snakeyaml ,只需引入前者。
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
2. 配置文件示例
sample.yml
spring:datasource:url: ${druid.url}username: ${druid.username}password: ${druid.password}driverClassName: ${druid.driverClassName}type: com.alibaba.druid.pool.DruidDataSourcesqlScriptEncoding: utf-8schema: classpath:sql/schema.sqlcontinue-on-error: truedruid:initial-size: 5 # 初始化大小min-idle: 10 # 最小连接数max-active: 20 # 最大连接数max-wait: 60000 # 获取连接时的最大等待时间min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间,单位是毫秒time-between-eviction-runs-millis: 60000 # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒validation-query: SELECT 1 # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效test-on-borrow: true # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能test-on-return: true # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能test-while-idle: true # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能devtools:restart:exclude: application-dev.yml,welcome.propertiesperson: name: qinjiangage: 18happy: falsebirth: 2000-01-01maps: {k1: v1,k2: v2}lists:- code- girl- musicdog:name: 旺财age: 1
三,自定义配置项动态刷新编码实现
1. 定义自定义配置项对象
import java.util.Date;
import java.util.List;
import java.util.Map;import lombok.Data;@Data
public class Person
{private String name;private Integer age;private Boolean happy;private Date birth;private Map<String, Object> maps;private List<Object> lists;private Dog dog;
}
import lombok.Data;@Data
public class Dog
{private String name;private Integer age;
}
2. 添加注解实现启动时自动注入
在Person类添加 @Component、@ConfigurationProperties(prefix = “person”) 实现自动注入,spring管理
@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person
{private String name;private Integer age;private Boolean happy;private Date birth;private Map<String, Object> maps;private List<Object> lists;private Dog dog;
}
3. 实现yml文件监听以及文件变化处理
/*** 监听文件变化(推荐)*/
@Slf4j
@Component
public class ReloadByFileAlterationMonitor
{@Autowiredprivate Welcome welcome;/*** thread-safe*/YAMLMapper yamlMapper = new YAMLMapper();/*** 初始化yml文件监听器*/@PostConstructpublic void initYamlMonitor(){try{URL url = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX);if (ResourceUtils.isFileURL(url)){FileAlterationObserver observer = new FileAlterationObserver(url.getPath(), FileFilterUtils.suffixFileFilter(".yml"));observer.addListener(new FileAlterationListenerAdaptor(){@Overridepublic void onFileChange(File file){log.info("★★★★★★★★ {} changed.", file.getName());if (StringUtils.equals("application-dev.yml", file.getName())){try{// yaml to JavaBeanString text = FileUtils.readFileToString(file, StandardCharsets.UTF_8);JavaBean javaBean = yamlMapper.readValue(text, JavaBean.class);if (javaBean != null && javaBean.getWelcome() != null){String value = javaBean.getWelcome().getMessage();log.info("#### autoRefresh to: {}", value);welcome.setMessage(value);}}catch (IOException e){log.error(e.getMessage(), e.getCause());}}}});long interval = TimeUnit.SECONDS.toMillis(10);FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);monitor.start();}}catch (Exception e){log.error(e.getMessage(), e.getCause());}}
}
四,yaml文件转换为java对象
1. 无法使用前缀绑定的处理
定义Result 使用Person person绑定yaml中前缀为person的数据,为了避免报错,同时定义了Map<String, Object> spring 来保存spring节点数据。
import lombok.Data;/*** 定义Result实体绑定Person<br>* 与下面的Spring配置等价<br>* @Component<br>* @ConfigurationProperties(prefix = "person")<br>* public class Person { ... }*/
@Data
public class Result
{private Person person;private Map<String, Object> spring;
}
2. 实现yaml文件转换java对象
注意: org.yaml.snakeyaml.Yaml 非线程安全
,建议使用 YAMLMapper
import java.io.IOException;
import java.nio.charset.StandardCharsets;import org.apache.commons.io.IOUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.fly.refresh.entity.Result;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SampleTest
{static String yamlText;YAMLMapper yamlMapper = new YAMLMapper();@BeforeClasspublic static void init(){try{yamlText = IOUtils.toString(new ClassPathResource("yaml/sample.yml").getURL(), StandardCharsets.UTF_8);log.info("yamlText => {}", yamlText);}catch (IOException e){log.error(e.getMessage(), e.getCause());}}/*** 解析带prefix的yaml*/@Testpublic void test()throws IOException{Result result = new Yaml().loadAs(yamlText, Result.class);log.info("snakeyaml toJavaBean: {}", result);result = yamlMapper.readValue(yamlText, Result.class);log.info("yamlMapper toJavaBean: {}", result);}
}
五、完整代码
1. 代码结构
2. 完整代码备份
如何使用下面的备份文件恢复成原始的项目代码,请移步查阅:神奇代码恢复工具
//goto docker\docker-compose.yml
version: '3'
services:hello:image: registry.cn-shanghai.aliyuncs.com/00fly/spring-config-refresh:1.0.0container_name: config-refreshdeploy:resources:limits:cpus: '1'memory: 300Mreservations:cpus: '0.05'memory: 200Mports:- 8080:8080environment:JAVA_OPTS: -server -Xms200m -Xmx200m -Djava.security.egd=file:/dev/./urandomrestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'//goto docker\restart.sh
#!/bin/bash
docker-compose down && docker system prune -f && docker-compose up -d && docker stats
//goto docker\stop.sh
#!/bin/bash
docker-compose down
//goto Dockerfile
FROM openjdk:8-jre-alpineRUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezoneCOPY target/*.jar /app.jarEXPOSE 8080CMD ["--server.port=8080"]ENTRYPOINT ["java","-jar","/app.jar"]
//goto pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.fly</groupId><artifactId>spring-config-refresh</artifactId><version>1.0.0</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.build.timestamp.format>yyyyMMdd-HH</maven.build.timestamp.format><docker.hub>registry.cn-shanghai.aliyuncs.com</docker.hub><java.version>1.8</java.version><skipTests>true</skipTests></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.4.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><exclusions><exclusion><groupId>org.apache.tomcat</groupId><artifactId>tomcat-jdbc</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.5</version></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>commons-configuration</groupId><artifactId>commons-configuration</artifactId><version>1.10</version></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-yaml</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-properties</artifactId></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!-- Test --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-configuration2</artifactId><version>2.8.0</version><scope>test</scope></dependency><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.4</version><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><finalName>${project.artifactId}-${project.version}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!-- 添加docker-maven插件 --><plugin><groupId>io.fabric8</groupId><artifactId>docker-maven-plugin</artifactId><version>0.41.0</version><executions><execution><phase>package</phase><goals><goal>build</goal><goal>push</goal><goal>remove</goal></goals></execution></executions><configuration><!-- 连接到带docker环境的linux服务器编译image --><!--<dockerHost>http://192.168.182.10:2375</dockerHost>--><!-- Docker 推送镜像仓库地址 --><pushRegistry>${docker.hub}</pushRegistry><images><image><!--推送到私有镜像仓库,镜像名需要添加仓库地址 --><name>${docker.hub}/00fly/${project.artifactId}:${project.version}-UTC-${maven.build.timestamp}</name><!--定义镜像构建行为 --><build><dockerFileDir>${project.basedir}</dockerFileDir></build></image><image><name>${docker.hub}/00fly/${project.artifactId}:${project.version}</name><build><dockerFileDir>${project.basedir}</dockerFileDir></build></image></images></configuration></plugin></plugins><resources><resource><directory>src/main/java</directory><excludes><exclude>**/*.java</exclude></excludes></resource><resource><directory>src/main/resources</directory><includes><include>**/**</include></includes></resource></resources></build>
</project>
//goto src\main\java\com\fly\BootApplication.javapackage com.fly;import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;import com.fly.core.utils.SpringContextUtils;import lombok.extern.slf4j.Slf4j;/*** * SpringBoot 启动入口* * @author 00fly* @version [版本号, 2018年7月20日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Slf4j
@EnableScheduling
@SpringBootApplication
@PropertySource("classpath:jdbc-h2.properties")
public class BootApplication
{public static void main(String[] args){// args = new String[] {"--noweb"};boolean web = !ArrayUtils.contains(args, "--noweb");log.info("############### with Web Configuration: {} #############", web);new SpringApplicationBuilder(BootApplication.class).web(web ? WebApplicationType.SERVLET : WebApplicationType.NONE).run(args);}@Bean@ConditionalOnWebApplicationCommandLineRunner init(){return args -> {if (SystemUtils.IS_OS_WINDOWS){log.info("★★★★★★★★ now open Browser ★★★★★★★★ ");String url = SpringContextUtils.getServerBaseURL();Runtime.getRuntime().exec("cmd /c start /min " + url + "/doc.html");Runtime.getRuntime().exec("cmd /c start /min " + url + "/h2-console");}};}
}
//goto src\main\java\com\fly\core\config\Knife4jConfig.java
package com.fly.core.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import io.swagger.annotations.ApiOperation;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;/*** knife4j** @author jack*/
@Configuration
@EnableSwagger2
public class Knife4jConfig
{@Value("${knife4j.enable: true}")private boolean enable;@BeanDocket api(){return new Docket(DocumentationType.SWAGGER_2).enable(enable).apiInfo(apiInfo()).groupName("Rest API").select().apis(RequestHandlerSelectors.basePackage("com.fly.refresh.web")).apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).paths(PathSelectors.any()).build();}private ApiInfo apiInfo(){return new ApiInfoBuilder().title("接口API").description("接口文档").termsOfServiceUrl("http://00fly.online/").version("1.0.0").build();}
}
//goto src\main\java\com\fly\core\config\ScheduleThreadPoolConfig.java
package com.fly.core.config;import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;/*** * Schedule线程池配置* * @author 00fly* @version [版本号, 2023年10月22日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Configuration
public class ScheduleThreadPoolConfig implements SchedulingConfigurer
{@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar){ScheduledExecutorService service = new ScheduledThreadPoolExecutor(8, new CustomizableThreadFactory("schedule-pool-"));taskRegistrar.setScheduler(service);}
}
//goto src\main\java\com\fly\core\config\SysDataBaseConfig.java
package com.fly.core.config;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;import javax.annotation.PostConstruct;import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;import lombok.Data;
import lombok.extern.slf4j.Slf4j;/*** * 数据库配置信息加载类* * @author 00fly* @version [版本号, 2021年10月24日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Slf4j
@Configuration
public class SysDataBaseConfig
{@AutowiredJdbcTemplate jdbcTemplate;@AutowiredConfigurableEnvironment environment;@PostConstructpublic void initDatabasePropertySource(){// 取配置信息列表并过滤空值List<SysConfig> data = jdbcTemplate.query("SELECT `key`, `value` FROM sys_config WHERE `status` = '1'", new BeanPropertyRowMapper<>(SysConfig.class));Map<String, Object> collect = data.stream().filter(p -> StringUtils.isNoneEmpty(p.getKey(), p.getValue())).collect(Collectors.toMap(SysConfig::getKey, SysConfig::getValue));log.info("====== init from database ===== {}", collect);// 追加配置到系统变量中,name取值随意environment.getPropertySources().addLast(new MapPropertySource("sys_config", collect));}
}/*** * 配置信息实体对象* * @author 00fly* @version [版本号, 2021年10月24日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Data
class SysConfig
{private String key;private String value;
}
//goto src\main\java\com\fly\core\JsonResult.java
package com.fly.core;import lombok.Data;/*** * 结果对象* * @author 00fly* @version [版本号, 2021年5月2日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Data
public class JsonResult<T>
{private T data;private boolean success;private String errorCode;private String message;public JsonResult(){super();}public static <T> JsonResult<T> success(T data){JsonResult<T> r = new JsonResult<>();r.setData(data);r.setSuccess(true);return r;}public static JsonResult<?> success(){JsonResult<Object> r = new JsonResult<>();r.setSuccess(true);return r;}public static JsonResult<Object> error(String code, String msg){JsonResult<Object> r = new JsonResult<>();r.setSuccess(false);r.setErrorCode(code);r.setMessage(msg);return r;}public static JsonResult<Object> error(String msg){return error("500", msg);}
}
//goto src\main\java\com\fly\core\utils\SpringContextUtils.java
package com.fly.core.utils;import java.net.InetAddress;
import java.net.UnknownHostException;import javax.servlet.ServletContext;import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;import lombok.extern.slf4j.Slf4j;/*** Spring Context 工具类* * @author 00fly**/
@Slf4j
@Component
public class SpringContextUtils implements ApplicationContextAware
{private static ApplicationContext applicationContext;/*** web服务器基准URL*/private static String SERVER_BASE_URL = null;@Overridepublic void setApplicationContext(ApplicationContext applicationContext)throws BeansException{log.info("###### execute setApplicationContext ######");SpringContextUtils.applicationContext = applicationContext;}public static ApplicationContext getApplicationContext(){return applicationContext;}public static <T> T getBean(Class<T> clazz){Assert.notNull(applicationContext, "applicationContext is null");return applicationContext.getBean(clazz);}/*** execute @PostConstruct May be SpringContextUtils not inited, throw NullPointerException* * @return*/public static String getActiveProfile(){Assert.notNull(applicationContext, "applicationContext is null");String[] profiles = applicationContext.getEnvironment().getActiveProfiles();return StringUtils.join(profiles, ",");}/*** can use in @PostConstruct* * @param context* @return*/public static String getActiveProfile(ApplicationContext context){Assert.notNull(context, "context is null");String[] profiles = context.getEnvironment().getActiveProfiles();return StringUtils.join(profiles, ",");}/*** get web服务基准地址,一般为 http://${ip}:${port}/${contentPath}* * @return* @throws UnknownHostException* @see [类、类#方法、类#成员]*/public static String getServerBaseURL()throws UnknownHostException{if (SERVER_BASE_URL == null){ServletContext servletContext = getBean(ServletContext.class);Assert.notNull(servletContext, "servletContext is null");String ip = InetAddress.getLocalHost().getHostAddress();SERVER_BASE_URL = "http://" + ip + ":" + getProperty("server.port") + servletContext.getContextPath();}return SERVER_BASE_URL;}/*** getProperty* * @param key eg:server.port* @return* @see [类、类#方法、类#成员]*/public static String getProperty(String key){return applicationContext.getEnvironment().getProperty(key, "");}
}
//goto src\main\java\com\fly\core\utils\YamlUtils.java
package com.fly.core.utils;import java.io.IOException;
import java.util.Map;
import java.util.Properties;import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;/*** * yaml转换工具* * @author 00fly* @version [版本号, 2023年4月25日]* @see [相关类/方法]* @since [产品/模块版本]*/
public final class YamlUtils
{private static YAMLMapper yamlMapper = new YAMLMapper();private static JavaPropsMapper javaPropsMapper = new JavaPropsMapper();/*** yaml转Json字符串* * @param yamlContent* @return* @throws IOException*/public static String yamlToJson(String yamlContent)throws IOException{JsonNode jsonNode = yamlMapper.readTree(yamlContent);return jsonNode.toPrettyString();}/*** yaml转Map<String, String>* * @param yamlContent* @return* @throws IOException*/public static Map<String, String> yamlToMap(String yamlContent)throws IOException{JsonNode jsonNode = yamlMapper.readTree(yamlContent);return javaPropsMapper.writeValueAsMap(jsonNode);}/*** yaml转properties* * @param yamlContent* @return* @throws IOException*/public static Properties yamlToProperties(String yamlContent)throws IOException{JsonNode jsonNode = yamlMapper.readTree(yamlContent);return javaPropsMapper.writeValueAsProperties(jsonNode);}/*** yaml转properties字符串* * @param yamlContent* @return* @throws IOException*/public static String yamlToPropText(String yamlContent)throws IOException{JsonNode jsonNode = yamlMapper.readTree(yamlContent);return javaPropsMapper.writeValueAsString(jsonNode);}private YamlUtils(){super();}
}
//goto src\main\java\com\fly\refresh\back\ReloadByDataBase.java
package com.fly.refresh.back;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;import com.fly.refresh.entity.Welcome;import lombok.extern.slf4j.Slf4j;/*** 数据库配置表手动刷新*/
@Slf4j
@Service
public class ReloadByDataBase
{@AutowiredWelcome welcome;@AutowiredJdbcTemplate jdbcTemplate;/*** 更新到数据库* * @param message* @return*/public int update(String message){int count = jdbcTemplate.update("UPDATE sys_config SET `value`=? WHERE `key` = 'welcome.message'", message);if (count > 0){log.info("#### autoRefresh to: {}", message);welcome.setMessage(message);}return count;}
}
//goto src\main\java\com\fly\refresh\back\ReloadByFileAlterationMonitor.java
package com.fly.refresh.back;import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.concurrent.TimeUnit;import javax.annotation.PostConstruct;import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.fly.refresh.entity.Person;
import com.fly.refresh.entity.Result;
import com.fly.refresh.entity.Welcome;import lombok.extern.slf4j.Slf4j;/*** 监听文件变化(推荐)*/
@Slf4j
@Component
public class ReloadByFileAlterationMonitor
{@Autowiredprivate Person person;@Autowiredprivate Welcome welcome;/*** thread-safe*/YAMLMapper yamlMapper = new YAMLMapper();/*** thread-safe*/JavaPropsMapper javaPropsMapper = new JavaPropsMapper();/*** 初始化yml文件监听器*/@PostConstructpublic void initYamlMonitor(){try{URL url = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX);if (ResourceUtils.isFileURL(url)){FileAlterationObserver observer = new FileAlterationObserver(url.getPath(), FileFilterUtils.suffixFileFilter(".yml"));observer.addListener(new FileAlterationListenerAdaptor(){@Overridepublic void onFileChange(File file){log.info("★★★★★★★★ {} changed.", file.getName());if (StringUtils.equals("application-dev.yml", file.getName())){try{// yaml to JavaBeanString text = FileUtils.readFileToString(file, StandardCharsets.UTF_8);Result javaBean = yamlMapper.readValue(text, Result.class);// Welcome属性拷贝if (javaBean != null && javaBean.getWelcome() != null){Welcome from = javaBean.getWelcome();BeanUtils.copyProperties(from, welcome);log.info("#### autoRefresh to: {}", welcome);}// Person属性拷贝if (javaBean != null && javaBean.getPerson() != null){Person from = javaBean.getPerson();BeanUtils.copyProperties(from, person);log.info("#### autoRefresh to: {}", person);}}catch (IOException e){log.error(e.getMessage(), e.getCause());}}}});long interval = TimeUnit.SECONDS.toMillis(10);FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);monitor.start();}}catch (Exception e){log.error(e.getMessage(), e.getCause());}}/*** 初始化Properties文件监听器*/@PostConstructpublic void initPropsMonitor(){try{URL url = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX);if (ResourceUtils.isFileURL(url)){FileAlterationObserver observer = new FileAlterationObserver(url.getPath(), FileFilterUtils.suffixFileFilter(".properties"));observer.addListener(new FileAlterationListenerAdaptor(){@Overridepublic void onFileChange(File file){log.info("★★★★★★★★ {} changed.", file.getName());if (StringUtils.equals("welcome.properties", file.getName())){try{// Properties to JavaBeanProperties prop = PropertiesLoaderUtils.loadProperties(new ClassPathResource(file.getName()));Result javaBean = javaPropsMapper.readPropertiesAs(prop, Result.class);if (javaBean != null && javaBean.getWelcome() != null){String value = javaBean.getWelcome().getMessage();log.info("#### autoRefresh to: {}", value);welcome.setMessage(value);}}catch (IOException e){log.error(e.getMessage(), e.getCause());}}}});long interval = TimeUnit.SECONDS.toMillis(10);FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);monitor.start();}}catch (Exception e){log.error(e.getMessage(), e.getCause());}}}
//goto src\main\java\com\fly\refresh\back\ReloadByReloadingStrategy.java
package com.fly.refresh.back;import javax.annotation.PostConstruct;import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.FileConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import com.fly.refresh.entity.Welcome;import lombok.extern.slf4j.Slf4j;/*** 文件重加载策略(不推荐)*/
@Slf4j
@Component
public class ReloadByReloadingStrategy
{String lastMsg;@AutowiredWelcome welcome;FileConfiguration propConfig;/*** 初始化properties文件重加载策略*/@PostConstructpublic void initReloadingStrategy(){try{// 只支持propertiespropConfig = new PropertiesConfiguration("welcome.properties");FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();strategy.setRefreshDelay(10000L);propConfig.setReloadingStrategy(strategy);lastMsg = propConfig.getString("welcome.message");}catch (ConfigurationException e){log.error(e.getMessage(), e.getCause());}}/*** 配置变更时刷新*/@Scheduled(initialDelay = 30000L, fixedRate = 10000L)public void autoRefresh(){// 是否变更,何时刷新逻辑实现String message = propConfig.getString("welcome.message");if (!StringUtils.equals(message, lastMsg)){log.info("#### autoRefresh to: {}, after properties Changed", message);welcome.setMessage(message);lastMsg = message;}}
}
//goto src\main\java\com\fly\refresh\entity\Dog.java
package com.fly.refresh.entity;import lombok.Data;@Data
public class Dog
{private String name;private Integer age;
}
//goto src\main\java\com\fly\refresh\entity\Person.java
package com.fly.refresh.entity;import java.util.Date;
import java.util.List;
import java.util.Map;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;import lombok.Data;@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person
{private String name;private Integer age;private Boolean happy;@DateTimeFormat(pattern = "yyyy-MM-dd")private Date birth;private Map<String, Object> maps;private List<Object> lists;private Dog dog;
}
//goto src\main\java\com\fly\refresh\entity\Result.java
package com.fly.refresh.entity;import java.util.Map;import lombok.Data;/*** 定义Result实体绑定Person、Welcome<br>* 与下面的Spring配置等价<br>* <br>* @Component<br>* @ConfigurationProperties(prefix = "person")<br>* public class Person { ... }<br>* <br>* @Component<br>* @ConfigurationProperties(prefix = "welcome")<br>* public class Welcome { ... }*/
@Data
public class Result
{private Person person;private Welcome welcome;private Map<String, Object> spring;
}
//goto src\main\java\com\fly\refresh\entity\Welcome.java
package com.fly.refresh.entity;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;import lombok.Data;/*** * Welcome配置文件实体<br>* 使用@Lazy待SysDataBaseConfig方法initDatabasePropertySource执行完再注入<br>* 否则仅使用数据库初始化时开发环境和Jar运行message值不一致* * @author 00fly* @version [版本号, 2023年11月3日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Data
@Lazy
@Component
@ConfigurationProperties(prefix = "welcome")
public class Welcome
{/*** message赋值方式:<br>* 1. Configuration注解在SysDataBaseConfig<br>* 2. spring.profiles.active指定dev即application-dev.yml<br>* 3. welcome.properties内容变更时触发<br>* 4. /show/refresh接口被调用时触发<br>* 方式1、2有竞争,不能严格区分先后*/private String message = "hello, 00fly in java!";
}
//goto src\main\java\com\fly\refresh\job\SimpleJob.java
package com.fly.refresh.job;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import com.fly.refresh.entity.Person;
import com.fly.refresh.entity.Welcome;import lombok.extern.slf4j.Slf4j;/*** * SimpleJob* * @author 00fly* @version [版本号, 2022年11月30日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Slf4j
@Component
public class SimpleJob
{@Autowiredprivate Person person;@Autowiredprivate Welcome welcome;/*** 不能实时刷新*/@Value("#{welcome.message}")private String message;@Scheduled(cron = "*/10 * * * * ?")public void run(){log.info("---- autoRefresh: {} | fixed: {}", welcome.getMessage(), message);log.info("**** {}, {}", welcome, person);}
}
//goto src\main\java\com\fly\refresh\ResourceReloadConfig.java
package com.fly.refresh;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.ResourceBundle;
import java.util.concurrent.ScheduledThreadPoolExecutor;import javax.annotation.PostConstruct;import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;import lombok.extern.slf4j.Slf4j;/*** 配置文件实时刷新* * @author 00fly* @version [版本号, 2017年4月25日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Slf4j
@Component
@ConditionalOnNotWebApplication
public class ResourceReloadConfig implements SchedulingConfigurer
{PropertiesConfiguration jobConfig;Resource cron = new ClassPathResource("test/cron.properties");ResourceBundle job = ResourceBundle.getBundle("test/job");@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar){// 配置公共Schedule线程池taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(8, new CustomizableThreadFactory("schedule-pool-")));// 配置TriggerTasktaskRegistrar.addTriggerTask(new Runnable(){@Overridepublic void run(){// 任务逻辑log.info("★★★★★★★ {} run ★★★★★★★", getClass().getName());}}, new Trigger(){@Overridepublic Date nextExecutionTime(TriggerContext triggerContext){String cron = readCronText();return new CronTrigger(cron).nextExecutionTime(triggerContext);}});}/*** 初始化*/@PostConstructpublic void init(){try{jobConfig = new PropertiesConfiguration("test/job.properties");FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();strategy.setRefreshDelay(60000L);// 刷新周期1分钟jobConfig.setReloadingStrategy(strategy);}catch (ConfigurationException e){log.error(e.getMessage(), e.getCause());}}/*** 3种方式读取CronText* * @return*/private String readCronText(){String cronText = "*/10 * * * * ?";Integer key = RandomUtils.nextInt(3);switch (key){case 0:cronText = jobConfig.getString("schedule.myjob.cron");break;case 1:try{cronText = IOUtils.toString(cron.getURL(), StandardCharsets.UTF_8);}catch (IOException e){log.error(e.getMessage(), e.getCause());}break;case 2:ResourceBundle.clearCache();cronText = job.getString("schedule.myjob.cron");break;default:break;}log.info("**** key: {} ==> {}", key, cronText);return cronText;}
}
//goto src\main\java\com\fly\refresh\web\ShowController.java
package com.fly.refresh.web;import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.fly.core.JsonResult;
import com.fly.refresh.back.ReloadByDataBase;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;@RestController
@Api(tags = "演示接口")
@RequestMapping("/show")
public class ShowController
{@AutowiredReloadByDataBase reloadByDataBase;@ApiOperation("刷新欢迎语")@PostMapping("/refresh")@ApiImplicitParam(name = "message", value = "欢迎语", example = "热烈欢迎活捉洪真英,生擒李知恩! ", required = true)public JsonResult<?> refresh(String message){if (StringUtils.isBlank(message)){return JsonResult.error("message不能为空");}boolean success = reloadByDataBase.update(message) > 0;return success ? JsonResult.success(message) : JsonResult.error("刷新欢迎语失败");}
}
//goto src\main\resources\application-dev.yml
person: name: qinjiangage: 18happy: falsebirth: 2000-01-01maps: {k1: v1,k2: v2}lists:- code- girl- musicdog:name: 旺财age: 1welcome:message: Hello 00fly in application-dev.yml
//goto src\main\resources\application-prod.yml
//goto src\main\resources\application-test.yml
//goto src\main\resources\application.yml
server:port: 8080servlet:context-path: /session:timeout: 1800
spring:datasource:url: ${druid.url}username: ${druid.username}password: ${druid.password}driverClassName: ${druid.driverClassName}type: com.alibaba.druid.pool.DruidDataSourcesqlScriptEncoding: utf-8schema: classpath:sql/schema.sqlcontinue-on-error: truedruid:initial-size: 5 # 初始化大小min-idle: 10 # 最小连接数max-active: 20 # 最大连接数max-wait: 60000 # 获取连接时的最大等待时间min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间,单位是毫秒time-between-eviction-runs-millis: 60000 # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒validation-query: SELECT 1 # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效test-on-borrow: true # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能test-on-return: true # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能test-while-idle: true # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能devtools:restart:exclude: application-dev.yml,welcome.propertiesh2:console:enabled: truepath: /h2-consolesettings:web-allow-others: trueprofiles:active:- dev
//goto src\main\resources\jdbc-h2.properties
druid.username=sa
druid.password=
druid.url=jdbc:h2:mem:reload;database_to_upper=false
druid.driverClassName=org.h2.Driver
//goto src\main\resources\sql\schema.sql
CREATE TABLE IF NOT EXISTS `sys_config` (`id` bigint NOT NULL AUTO_INCREMENT,`key` varchar(100),`value` varchar(200),`description` varchar(200),`status` varchar(20),`version` bigint,`creater` varchar(50),`create_time` datetime,`modifier` varchar(50),`modify_time` datetime,PRIMARY KEY (`id`)
);INSERT INTO `sys_config` VALUES ('1', 'welcome.message', CONCAT('hello from db, rand ' ,CAST(RAND()*65536 AS INT)), '系统提示语', '1', '0', 'admin', now(), 'admin', now());
//goto src\main\resources\test\cron.properties
*/5 * * * * ?
//goto src\main\resources\test\job.properties
schedule.myjob.cron = */5 * * * * ?
//goto src\main\resources\welcome.properties
welcome.message = Hello 00fly in welcome.properties
//goto src\test\java\com\fly\refresh\config2\ResourceReloadConfigTest.java
package com.fly.refresh.config2;import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.builder.fluent.PropertiesBuilderParameters;
import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.ClasspathLocationStrategy;
import org.apache.commons.configuration2.io.FileLocationStrategy;
import org.apache.commons.configuration2.reloading.PeriodicReloadingTrigger;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;import lombok.extern.slf4j.Slf4j;/*** Configuration2配置文件实时刷新 https://www.geek-share.com/detail/2727072209.html* * @author 00fly* @version [版本号, 2017年4月25日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Slf4j
public class ResourceReloadConfigTest
{ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder;/*** 初始化*/@Beforepublic void init(){// 文件扫描策略// FileLocationStrategy strategy = new CombinedLocationStrategy(Arrays.asList(new ClasspathLocationStrategy(), new FileSystemLocationStrategy()));FileLocationStrategy strategy = new ClasspathLocationStrategy();PropertiesBuilderParameters propertiesBuilderParameters = new Parameters().properties().setEncoding(StandardCharsets.UTF_8.name()).setPath(new ClassPathResource("job.properties").getPath()).setLocationStrategy(strategy).setListDelimiterHandler(new DefaultListDelimiterHandler(',')).setReloadingRefreshDelay(2000L).setThrowExceptionOnMissing(true);builder = new ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration>(PropertiesConfiguration.class).configure(propertiesBuilderParameters);PeriodicReloadingTrigger trigger = new PeriodicReloadingTrigger(builder.getReloadingController(), null, 60, TimeUnit.SECONDS);trigger.start();}@Testpublic void read()throws ConfigurationException{// 直接读取Configurations configs = new Configurations();FileBasedConfigurationBuilder.setDefaultEncoding(PropertiesConfiguration.class, StandardCharsets.UTF_8.name());PropertiesConfiguration propConfig = configs.properties(new ClassPathResource("job.properties").getPath());log.info("propConfig:{}", propConfig.getString("schedule.myjob.cron"));}/*** https://cloud.tencent.com/developer/article/1600688* * @throws ConfigurationException*/@Testpublic void test()throws ConfigurationException{PropertiesConfiguration configuration = builder.getConfiguration();log.info("{}", configuration.getString("schedule.myjob.cron"));}
}
//goto src\test\java\com\fly\refresh\prop\JavaPropsMapperTest.java
package com.fly.refresh.prop;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Properties;import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper;
import com.fly.core.utils.YamlUtils;
import com.fly.refresh.entity.Result;import lombok.extern.slf4j.Slf4j;@Slf4j
public class JavaPropsMapperTest
{/*** thread-safe*/JavaPropsMapper javaPropsMapper = new JavaPropsMapper();/*** Properties to Bean* * @throws IOException*/@Testpublic void testPropToBean()throws IOException{Properties complex = PropertiesLoaderUtils.loadProperties(new ClassPathResource("prop/complex.properties"));// 多层结构转换成了嵌套MapMap<?, ?> map = javaPropsMapper.readPropertiesAs(complex, Map.class);log.info("***** PropToBean:{} => {}", complex, map);Result javaBean = javaPropsMapper.readPropertiesAs(complex, Result.class);log.info("***** PropToBean:{} => {}", complex, javaBean);}/*** Properties to Bean* * @throws IOException*/@Testpublic void testPropToBean2()throws IOException{String text = IOUtils.toString(new ClassPathResource("yaml/sample.yml").getURL(), StandardCharsets.UTF_8);Properties props = YamlUtils.yamlToProperties(text);props.keySet().forEach(key -> {log.info("{} => {}", key, props.get(key));});Result result = javaPropsMapper.readPropertiesAs(props, Result.class);log.info("***** PropToBean:{}", result);}
}
//goto src\test\java\com\fly\refresh\yaml\SampleTest.java
package com.fly.refresh.yaml;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;import org.apache.commons.io.IOUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.fly.core.utils.YamlUtils;
import com.fly.refresh.entity.Result;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SampleTest
{static String yamlText;/*** thread-safe*/YAMLMapper yamlMapper = new YAMLMapper();@BeforeClasspublic static void init(){try{yamlText = IOUtils.toString(new ClassPathResource("yaml/sample.yml").getURL(), StandardCharsets.UTF_8);log.info("yamlText => {}", yamlText);}catch (IOException e){log.error(e.getMessage(), e.getCause());}}/*** 解析带prefix的yaml*/@Testpublic void test()throws IOException{Result result = new Yaml().loadAs(yamlText, Result.class);log.info("snakeyaml toJavaBean: {}", result);result = yamlMapper.readValue(yamlText, Result.class);log.info("yamlMapper toJavaBean: {}", result);}@Testpublic void test2()throws IOException{// TODO: yamlText截取person内容转换为Person对象Properties props = YamlUtils.yamlToProperties(yamlText);log.info("Properties: {}", props);Result result = new JavaPropsMapper().readPropertiesAs(props, Result.class);log.info("***** PropToBean:{}", result);}
}
//goto src\test\java\com\fly\refresh\yaml\SnakeYamlTest.java
package com.fly.refresh.yaml;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;import org.apache.commons.io.IOUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.yaml.snakeyaml.Yaml;import com.fly.core.utils.YamlUtils;
import com.fly.refresh.entity.Result;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SnakeYamlTest
{private static String text;@BeforeClasspublic static void init(){try{Resource resource = new ClassPathResource("yaml/complex.yml");text = IOUtils.toString(resource.getURL(), StandardCharsets.UTF_8);log.info("yamlText => {}", text);}catch (IOException e){log.error(e.getMessage(), e.getCause());}}@Testpublic void test(){Yaml yaml = new Yaml();Result javaBean = yaml.loadAs(text, Result.class);log.info("***** toJavaBean => {}", javaBean);}/*** 注意区别*/@Testpublic void testPk()throws IOException{Yaml yaml = new Yaml();Properties prop1 = yaml.loadAs(text, Properties.class);Properties prop2 = YamlUtils.yamlToProperties(text);log.info("** PK ** {} <=> {}", prop1, prop2);// {welcome={message=Hello 00fly in test2.yml}} <=> {welcome.message=Hello 00fly in test2.yml}}
}
//goto src\test\java\com\fly\refresh\yaml\YAMLMapperTest.java
package com.fly.refresh.yaml;import java.io.IOException;
import java.nio.charset.StandardCharsets;import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.fly.refresh.entity.Result;import lombok.extern.slf4j.Slf4j;@Slf4j
public class YAMLMapperTest
{/*** thread-safe*/YAMLMapper yamlMapper = new YAMLMapper();@Testpublic void test()throws IOException{Resource resource = new ClassPathResource("yaml/complex.yml");String text = IOUtils.toString(resource.getURL(), StandardCharsets.UTF_8);log.info("***** complex.yml yamlText => {}", text);Result javaBean = yamlMapper.readValue(text, Result.class);log.info("***** toJavaBean => {}", javaBean);// 报错com.fasterxml.jackson.databind.exc.MismatchedInputException// Properties prop = yamlMapper.readValue(text, Properties.class);// log.info("***** toJavaBean => {}", prop);}
}
//goto src\test\resources\job.properties
schedule.myjob.cron = */5 * * * * ?
//goto src\test\resources\log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="off" monitorInterval="0"><appenders><console name="Console" target="system_out"><patternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %c - %msg%n" /></console></appenders><loggers><root level="INFO"><appender-ref ref="Console" /></root></loggers>
</configuration>
//goto src\test\resources\prop\complex.properties
welcome.message=Hello 00fly in complex
//goto src\test\resources\yaml\complex.yml
welcome:message: Hello 00fly in test2.yml
//goto src\test\resources\yaml\sample.yml
spring:datasource:url: ${druid.url}username: ${druid.username}password: ${druid.password}driverClassName: ${druid.driverClassName}type: com.alibaba.druid.pool.DruidDataSourcesqlScriptEncoding: utf-8schema: classpath:sql/schema.sqlcontinue-on-error: truedruid:initial-size: 5 # 初始化大小min-idle: 10 # 最小连接数max-active: 20 # 最大连接数max-wait: 60000 # 获取连接时的最大等待时间min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间,单位是毫秒time-between-eviction-runs-millis: 60000 # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒validation-query: SELECT 1 # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效test-on-borrow: true # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能test-on-return: true # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能test-while-idle: true # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能devtools:restart:exclude: application-dev.yml,welcome.propertiesperson: name: qinjiangage: 18happy: falsebirth: 2000-01-01maps: {k1: v1,k2: v2}lists:- code- girl- musicdog:name: 旺财age: 1
3. 运行说明
- 系统启动后从内存数据库h2表sys_config加载配置,从application-dev.yml加载配置
- 修改application-dev.yml、welcome.properties可以看到配置被动态刷新
- /show/refresh 接口调用刷新配置
- 如需要测试数据库配置,请在application.yml设置spring.profiles.active为test或者prod
有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!
-over-