文章目录
- 一,序言
- 二,准备工作
- 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对象
注意: 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();
@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 docker\docker-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\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-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 src\main\java\com\fly\BootApplication.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 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;
@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 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
{
@Override
public 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
{
@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 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;
@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 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
{
@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 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
{
@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 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;
@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 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
{
@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 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");
@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 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
{
@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 src\main\resources\application-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 src\main\resources\application-prod.yml
//goto src\main\resources\application-test.yml
//goto src\main\resources\application.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 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;
/**
* 初始化
*/
@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\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
*/
@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\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();
@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\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;
@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\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();
@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\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.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-