springboot配置项动态刷新

文章目录

  • 一,序言
  • 二,准备工作
    • 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. 运行说明

  1. 系统启动后从内存数据库h2表sys_config加载配置,从application-dev.yml加载配置
  2. 修改application-dev.yml、welcome.properties可以看到配置被动态刷新
  3. /show/refresh 接口调用刷新配置
  4. 如需要测试数据库配置,请在application.yml设置spring.profiles.active为test或者prod

有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

-over-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/243199.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python中使用多种方法输出哈沙德数

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 哈沙德数&#xff08;Harshad Number&#xff09;&#xff0c;又称Niven数&#xff0c;是指一个自然数&#xff0c;它可以被它的各位数字之和整除。换句话说&#xff0c;如果一个数字是哈沙德数&#xff0c;那么…

CSS 下载进度条

<template><view class=btn>下载中</view></template><script></script><style>/* 设置整个页面的样式 */body {width: 100vw; /* 页面宽度为视口宽度 */background: #000000; /* 背景颜色为白色 */display: flex; /* 使用 flex…

【JVM】JVM概述

JVM概述 基本介绍 JVM&#xff1a;全称 Java Virtual Machine&#xff0c;即 Java 虚拟机&#xff0c;一种规范&#xff0c;本身是一个虚拟计算机&#xff0c;直接和操作系统进行交互&#xff0c;与硬件不直接交互&#xff0c;而操作系统可以帮我们完成和硬件进行交互的工作特…

最优传输学习及问题总结

文章目录 参考内容lam0.1lam3lam10lam50lam100lam300画图线性规划matlabpython代码 欢迎关注我们组的微信公众号&#xff0c;更多好文章在等你呦&#xff01; 微信公众号名&#xff1a;碳硅数据 公众号二维码&#xff1a; 参考内容 https://blog.csdn.net/qq_41129489/artic…

使用ffmpeg调整视频中音频采样率及声道

1 原始视频信息 通过ffmpeg -i命令查看视频基本信息 ffmpeg -i example2.mp4 ffmpeg version 6.1-essentials_build-www.gyan.dev Copyright (c) 2000-2023 the FFmpeg developersbuilt with gcc 12.2.0 (Rev10, Built by MSYS2 project)configuration: --enable-gpl --enable…

网页无法访问但是有网什么原因

目录 1.运行网络诊断&#xff0c;确认原因 原因A.远程计算机或设备将不接受连接(该设备或资源(Web 代理)未设置为接受端口“7890”上的连接 原因B.DNS服务器未响应 场景A.其他的浏览器可以打开网页&#xff0c;自带的Edge却不行 方法A&#xff1a;关闭代理 Google自带翻译…

C++中命名空间、缺省参数、函数重载

目录 1.命名空间 2.缺省参数 3.函数重载 1.命名空间 在C中定义命名空间我们需要用到namespace关键字&#xff0c;后面跟上命名空间的名字&#xff0c;结构框架有点类似结构体&#xff08;如图所示&#xff09; 上面的代码我一一进行讲解&#xff1a; 1.我们先来说第三行和main函…

如何搭建MariaDB并实现无公网ip环境远程连接本地数据库

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 1. 配置MariaDB数据库1.1 安装MariaDB数据库1.2 测试局域网内远程连接 2. 内网穿透2.1 创建隧道映射…

【C++】priority_queue模拟实现过程中值得注意的点

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 前言 本篇文章旨在记录博主在模…

HCIA vlan练习

目录 实验拓扑 实验要求 实验步骤 1、交换机创建vlan 2、交换机上的各个接口划分到对应vlan中 3、trunk干道 4、路由器单臂路由 5、路由器DHCP设置 实验测试 华为交换机更换端口连接模式报错处理 实验拓扑 实验要求 根据图划分vlan&#xff0c;并通过DHCP给主机下发…

Docker(三)使用 Docker 镜像:从仓库获取镜像;管理本地主机上的镜像;介绍镜像实现的基本原理

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; 使用 Docker 镜像 在之前的介绍中&#xff0c;我们知道镜像是 Docker 的三大组件之一。 Docker 运行容器前需要本地存在对应的镜像&#x…

c语言冒泡排序

系列文章目录 c语言冒泡排序 c语言冒泡排序 系列文章目录一、冒泡排序原理二、冒泡排序案例 一、冒泡排序原理 有几个数就需要排序几次-1 从数组第一个元素开始和相邻的元素比对&#xff0c;大的元素放在后面&#xff0c;小的放在前面 如&#xff0c;428057139 4与2对比&#x…

「优选算法刷题」:在排序数组中查找元素的第一个和最后个位置

一、题目 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&a…

Spring--@Async解析

一、Async 简介 从Spring3开始提供了Async注解&#xff0c;被该注解标注的方法&#xff0c;Spring底层会新建一个线程池或者使用已有的线程池中的线程去异步的执行被标注的方法。 二、Async 工作原理 Async与Transactional 工作原理基本是一样的&#xff0c;也是通过Spring …

【数据结构初阶】——顺序表

本文由睡觉待开机原创&#xff0c;转载请注明出处。 本内容在csdn网站首发 欢迎各位点赞—评论—收藏 如果存在不足之处请评论留言&#xff0c;共同进步&#xff01; 这里写目录标题 1.数据结构2.顺序表线性表顺序表的结构 3.动态顺序表的实现 1.数据结构 数据结构的概念&…

【JavaEE进阶】 Spring Boot⽇志

文章目录 &#x1f38b;关于日志&#x1f6a9;为什么要学习⽇志&#x1f6a9;⽇志的⽤途&#x1f6a9;日志的简单使用 &#x1f384;打印⽇志&#x1f6a9;程序中得到⽇志对象&#x1f6a9;使⽤⽇志对象打印⽇志 &#x1f38d;⽇志格式的说明&#x1f6a9;⽇志级别的作用&#…

QQ数据包解密

Windows版qq数据包格式&#xff1a; android版qq数据包格式&#xff1a; 密钥&#xff1a;16个0 算法&#xff1a;tea_crypt算法 pc版qq 0825数据包解密源码&#xff1a; #include "qq.h" #include "qqcrypt.h" #include <WinSock2.h> #include…

Win10下在Qt项目中配置SQlite3环境

资源下载 官网资源&#xff1a;SQLite Download Page 1、sqlite.h sqlite-amalgamation-3450000.zip (2.60 MiB) 2、sqlite3.def&#xff0c;sqlite3.dll sqlite-dll-win-x64-3450000.zip (1.25 MiB) 3、 win10下安装sqlite3所需要文件 sqlite-tools-win-x64-3450000.zipht…

node介绍

1.node是什么 Node是一个基于Chrome V8引擎的JS运行环境。 Node不是一个独立的语言、node不是JS框架。 Node是一个除了浏览器之外的、可以让JS运行的环境 Node.js是一个让JS运行在服务端的开发平台&#xff0c;是使用事件驱动&#xff0c;异步非阻塞I/O&#xff0c;单线程&…

fastJson和jackson的日期数据处理

目录 1.jackson 2.fastjson 3.总结 1.jackson jackson是spring mvc默认的JSON解析方法&#xff0c;前端的数据序列化处理之后&#xff0c;后端经过反序列化处理可以直接使用实体对象进行接收。后端接口返回实体对象&#xff0c;经过序列化处理后前端可以接收并进行处理。 …