文章目录
- 一,序言
- 二,准备工作
-
- 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.DruidDataSource sqlScriptEncoding: utf-8 schema: classpath:sql/schema.sql continue-on-error: true druid: 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.properties person: name: qinjiang age: 18 happy: false birth: 2000-01-01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: 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 { @Autowired private Welcome welcome; /** * thread-safe */ YAMLMapper yamlMapper = new YAMLMapper(); /** * 初始化yml文件监听器 */ @PostConstruct public 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() { @Override public void onFileChange(File file) { log.info(" {} changed.", file.getName()); if (StringUtils.equals("application-dev.yml", file.getName())) { try { // yaml to JavaBean String 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对象
注意:
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(); @BeforeClass public 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 */ @Test public 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 dockerdocker-compose.yml version: '3' services: hello: image: registry.cn-shanghai.aliyuncs.com/00fly/spring-config-refresh:1.0.0 container_name: config-refresh deploy: resources: limits: cpus: '1' memory: 300M reservations: cpus: '0.05' memory: 200M ports: - 8080:8080 environment: JAVA_OPTS: -server -Xms200m -Xmx200m -Djava.security.egd=file:/dev/./urandom restart: on-failure logging: driver: json-file options: max-size: 5m max-file: '1' //goto docker estart.sh #!/bin/bash docker-compose down && docker system prune -f && docker-compose up -d && docker stats //goto dockerstop.sh #!/bin/bash docker-compose down //goto Dockerfile FROM openjdk:8-jre-alpine RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone COPY target/*.jar /app.jar EXPOSE 8080 CMD ["--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 srcmainjavacomflyBootApplication.java package 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 @ConditionalOnWebApplication CommandLineRunner 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 srcmainjavacomflycoreconfigKnife4jConfig.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; @Bean Docket 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 srcmainjavacomflycoreconfigScheduleThreadPoolConfig.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 { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ScheduledExecutorService service = new ScheduledThreadPoolExecutor(8, new CustomizableThreadFactory("schedule-pool-")); taskRegistrar.setScheduler(service); } } //goto srcmainjavacomflycoreconfigSysDataBaseConfig.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 { @Autowired JdbcTemplate jdbcTemplate; @Autowired ConfigurableEnvironment environment; @PostConstruct public 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 srcmainjavacomflycoreJsonResult.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 srcmainjavacomflycoreutilsSpringContextUtils.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; @Override public 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 srcmainjavacomflycoreutilsYamlUtils.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 srcmainjavacomfly efreshackReloadByDataBase.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 { @Autowired Welcome welcome; @Autowired JdbcTemplate 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 srcmainjavacomfly efreshackReloadByFileAlterationMonitor.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 { @Autowired private Person person; @Autowired private Welcome welcome; /** * thread-safe */ YAMLMapper yamlMapper = new YAMLMapper(); /** * thread-safe */ JavaPropsMapper javaPropsMapper = new JavaPropsMapper(); /** * 初始化yml文件监听器 */ @PostConstruct public 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() { @Override public void onFileChange(File file) { log.info(" {} changed.", file.getName()); if (StringUtils.equals("application-dev.yml", file.getName())) { try { // yaml to JavaBean String 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文件监听器 */ @PostConstruct public 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() { @Override public void onFileChange(File file) { log.info(" {} changed.", file.getName()); if (StringUtils.equals("welcome.properties", file.getName())) { try { // Properties to JavaBean Properties 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 srcmainjavacomfly efreshackReloadByReloadingStrategy.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; @Autowired Welcome welcome; FileConfiguration propConfig; /** * 初始化properties文件重加载策略 */ @PostConstruct public void initReloadingStrategy() { try { // 只支持properties propConfig = 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 srcmainjavacomfly efreshentityDog.java package com.fly.refresh.entity; import lombok.Data; @Data public class Dog { private String name; private Integer age; } //goto srcmainjavacomfly efreshentityPerson.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 srcmainjavacomfly efreshentityResult.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 srcmainjavacomfly efreshentityWelcome.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 srcmainjavacomfly efreshjobSimpleJob.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 { @Autowired private Person person; @Autowired private 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 srcmainjavacomfly efreshResourceReloadConfig.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"); @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 配置公共Schedule线程池 taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(8, new CustomizableThreadFactory("schedule-pool-"))); // 配置TriggerTask taskRegistrar.addTriggerTask(new Runnable() { @Override public void run() { // 任务逻辑 log.info(" {} run ", getClass().getName()); } }, new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { String cron = readCronText(); return new CronTrigger(cron).nextExecutionTime(triggerContext); } }); } /** * 初始化 */ @PostConstruct public 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 srcmainjavacomfly efreshwebShowController.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 { @Autowired ReloadByDataBase 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 srcmain esourcesapplication-dev.yml person: name: qinjiang age: 18 happy: false birth: 2000-01-01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: name: 旺财 age: 1 welcome: message: Hello 00fly in application-dev.yml //goto srcmain esourcesapplication-prod.yml //goto srcmain esourcesapplication-test.yml //goto srcmain esourcesapplication.yml server: port: 8080 servlet: context-path: / session: timeout: 1800 spring: datasource: url: ${druid.url} username: ${druid.username} password: ${druid.password} driverClassName: ${druid.driverClassName} type: com.alibaba.druid.pool.DruidDataSource sqlScriptEncoding: utf-8 schema: classpath:sql/schema.sql continue-on-error: true druid: 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.properties h2: console: enabled: true path: /h2-console settings: web-allow-others: true profiles: active: - dev //goto srcmain esourcesjdbc-h2.properties druid.username=sa druid.password= druid.url=jdbc:h2:mem:reload;database_to_upper=false druid.driverClassName=org.h2.Driver //goto srcmain esourcessqlschema.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 srcmain esources estcron.properties */5 * * * * ? //goto srcmain esources estjob.properties schedule.myjob.cron = */5 * * * * ? //goto srcmain esourceswelcome.properties welcome.message = Hello 00fly in welcome.properties //goto src estjavacomfly efreshconfig2ResourceReloadConfigTest.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; /** * 初始化 */ @Before public 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(); } @Test public 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 */ @Test public void test() throws ConfigurationException { PropertiesConfiguration configuration = builder.getConfiguration(); log.info("{}", configuration.getString("schedule.myjob.cron")); } } //goto src estjavacomfly efreshpropJavaPropsMapperTest.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 */ @Test public void testPropToBean() throws IOException { Properties complex = PropertiesLoaderUtils.loadProperties(new ClassPathResource("prop/complex.properties")); // 多层结构转换成了嵌套Map Map<?, ?> 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 */ @Test public 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 estjavacomfly efreshyamlSampleTest.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(); @BeforeClass public 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 */ @Test public 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); } @Test public 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 estjavacomfly efreshyamlSnakeYamlTest.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; @BeforeClass public 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()); } } @Test public void test() { Yaml yaml = new Yaml(); Result javaBean = yaml.loadAs(text, Result.class); log.info("***** toJavaBean => {}", javaBean); } /** * 注意区别 */ @Test public 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 estjavacomfly efreshyamlYAMLMapperTest.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(); @Test public 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 est esourcesjob.properties schedule.myjob.cron = */5 * * * * ? //goto src est esourceslog4j2.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 est esourcespropcomplex.properties welcome.message=Hello 00fly in complex //goto src est esourcesyamlcomplex.yml welcome: message: Hello 00fly in test2.yml //goto src est esourcesyamlsample.yml spring: datasource: url: ${druid.url} username: ${druid.username} password: ${druid.password} driverClassName: ${druid.driverClassName} type: com.alibaba.druid.pool.DruidDataSource sqlScriptEncoding: utf-8 schema: classpath:sql/schema.sql continue-on-error: true druid: 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.properties person: name: qinjiang age: 18 happy: false birth: 2000-01-01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: 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-