【springboot配置项动态刷新】与【yaml文件转换为java对象】

文章目录

  • 一,序言
  • 二,准备工作
    • 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.stereotype.Component;

import lombok.Data;

@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;
}
//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. 运行说明

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

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

-over-

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习——逻辑回归

一、分类问题 监督学习的最主要类型 分类&#xff08;Classification&#xff09;&#xff1a; 身高1.85m&#xff0c;体重100kg的男人穿什么尺码的T恤&#xff1f;根据肿瘤的体积、患者的年龄来判断良性或恶性&#xff1f;根据用户的年龄、职业、存款数量来判断信用卡是否会…

Mac VsCode g++编译报错:不支持C++11语法解决

编译运行时报错&#xff1a; [Running] cd “/Users/yiran/Documents/vs_projects/c/” && g 1116.cpp -o 1116 && "/Users/yiran/Documents/vs_projects/c/"1116 1116.cpp:28:22: warning: range-based for loop is a C11 extension [-Wc11-extensi…

浅谈前端自定义VectorGrid矢量瓦片样式

目录 前言 一、VectorGrid相关API介绍 1、VectorGrid 2、 LayerStyles样式详解 二、样式自动配置 1、页面定义 2、地图及PBF瓦片引入 3、矢量瓦片样式定义 4、鼠标事件交互 三、最终效果 1、自定义样式展示 2、鼠标交互 总结 前言 在上一篇博客中&#xff0c;详细讲…

支付卡行业(PCI)PIN安全要求和测试程序 7个控制目标、33个要求及规范性附录ABC 密钥注入-PCI认证-安全行业基础篇4

概述 用于在ATM和POS终端进行在线和离线支付卡交易处理期间&#xff0c;对个人身份号码&#xff08;PIN&#xff09;数据进行安全管理、处理和传输。 该标准具体包括 7 个控制目标和 33 个安全要求&#xff0c; 标准的结构分为标准主体部分&#xff0c;标准附录&#xff08;N…

FPGA高端项目:图像缩放+GTP+UDP架构,高速接口以太网视频传输,提供2套工程源码加QT上位机源码和技术支持

目录 1、前言免责声明本项目特点 2、相关方案推荐我这里已有的 GT 高速接口解决方案我这里已有的以太网方案我这里已有的图像处理方案 3、设计思路框架设计框图视频源选择ADV7611 解码芯片配置及采集动态彩条跨时钟FIFO图像缩放模块详解设计框图代码框图2种插值算法的整合与选择…

C语言:深入浅出qsort方法,编写自己的qsort完成冒泡排序

目录 什么是qsort&#xff1f; 函数原型 比较函数 compar 排序整型数组 排序结构体数组 根据成员字符排序 strcmp函数 根据成员整型排序 自定义qsort实现冒泡排序 qsort的实现原理 具体步骤 快速排序示例代码&#xff1a; 什么是qsort&#xff1f; qsort是 C …

YOLO目标检测——交通标志分类数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;交通标志识别数据集在自动驾驶、交通安全监控、智能交通系统、驾驶员辅助系统和城市规划等领域都有广泛应用的潜力数据集说明&#xff1a;交通标志分类数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含多场景白天黑…

OOM排查

OOM排查 一&#xff0c;原因 1.一次性申请对象太多&#xff0c;创建了大量对象&#xff0c;尤其从表中读取了大量数据&#xff0c;循环中大量创建对象&#xff0c;放入list中。方案&#xff1a;限量 2.内存资源耗尽为释放&#xff0c;如connction&#xff0c;线程。方案&#…

猫罐头什么牌子好?2023营养又美味的猫主食罐头推荐!

亲爱的猫咪主人&#xff0c;你是否为你家小猫咪的挑食问题感到困扰&#xff1f;作为一位在宠物店工作了七年&#xff0c;负责喂养三十多只猫咪的店长&#xff0c;我对许多品牌的猫罐头都非常熟悉了。对于猫罐头哪个牌子好这个问题&#xff0c;我想借此机会分享一些见解。 在本…

软约束与硬约束

软约束硬约束 软约束硬约束 硬约束优化 1.基于走廊的光滑轨迹生成 2.基于贝塞尔曲线的轨迹优化 软约束优化 1.基于距离的轨迹优化 2.目标函数的设计 目标函数 光滑代价函数 碰撞代价函数 动力学代价函数。 光滑代价函数&#xff1a; 使用minimum snap来实现。 碰撞…

lua中的循环 while、for、repeat until三种循环方式、pairs和ipairs区别

lua中的循环 while、for、repeat until三种循环方式、pairs和ipairs区别 介绍for循环参数ipairs和pairs whilerepeat until总结 介绍 这里我用while、for、repeat until分别输出1-20之间的奇数 &#xff0c;具体的语法可以看下面的代码 for循环 参数 定义一个初始值为start…

毫米波雷达技术的医疗创新:开启无创检测与监测的新时代

随着科技的不断进步&#xff0c;毫米波雷达技术正日益成为医疗领域的一项引人注目的创新。其无创性质、高分辨率和多功能性为医学诊断和监测带来了新的可能性。本文将深入探讨毫米波雷达技术在医疗创新中的应用&#xff0c;着眼于无创检测与监测领域的突破性发展。 1. 毫米波雷…

Babylonjs学习笔记(八)——网格行为

书接上回&#xff0c;这里讨论MeshAction网格行为&#xff01;&#xff01;&#xff01; 一、搭建基础场景 let box:AbstractMesh; let cube:AbstractMesh; let sphere:AbstractMesh; let cylinder:AbstractMesh; let mat:PBRMaterial;// 创建天空盒 const createSkyBox (sc…

如何在在线Excel文档中规范单元格输入

在日常的工作中&#xff0c;我们常常需要处理大量的数据。为了确保数据的准确性和可靠性。我们需要对输入的数据进行规范化和验证。其中一个重要的方面是规范单元格输入。而数据验证作为Excel中一种非常实用的功能&#xff0c;它可以帮助用户规范单元格的输入&#xff0c;从而提…

简单代理模式

代理模式 代理模式(Proxy)&#xff0c;为其他对象提供一种代理以控制对这个对象的访问。 结构图如下&#xff1a; ISubject接口&#xff0c;定义了RealSubject和Proxy的共用接口方法&#xff0c;这样就可以在任何使用RealSubject的地方使用Proxy代理。 ISubject接口 public…

JavaScript使用正则表达式

正则表达式(RegExp)也称规则表达式(regular expression)&#xff0c;是非常强大的字符串操作工具&#xff0c;语法格式为一组特殊字符构成的匹配模式&#xff0c;用来匹配字符串。ECMAScript 3以Perl为基础规范JavaScript正则表达式&#xff0c;实现Perl 5正则表达式的子集。Ja…

小米手机怎么识别图片上的表格文字?

前言&#xff1a; 小米手机怎么识别图片上的文字&#xff1f;有二种解决方案&#xff1a;一是直接用系统自带的OCR工具来实现&#xff0c;二是借助第三方软件&#xff08;如金鸣识别&#xff09;来实现。 一、用小米自带的系统工具来实现。 对于MIUI12.5以上系统的小米手机来…

学C++跟着视频学还是跟着书学?

学C跟着视频学还是跟着书学&#xff1f; 感觉得看基础和目标 如果不是喜欢 C 或者以求职 / 完成 C 相关工作为目标的话&#xff0c;菜鸟教程其实都够了&#xff0c;基本语法掌握就差不多&#xff0c;然后多去写。 最近很多小伙伴找我&#xff0c;说想要一些C的资料&#xff0…

如何在在线Excel文档中对数据进行统计

本次我们将用zOffice表格的公式与数据透视表分析样例&#xff08;三个班级的学生成绩&#xff09;。zOffice表格内置了大量和Excel相同的统计公式&#xff0c;可以进行各种常见的统计分析&#xff0c;如平均值、标准差、相关性等。同时&#xff0c;zOffice也有数据透视表功能&a…

【Web】在前端中,HTML<meta>标签

<meta>实例 <head><meta name"description" content"免费在线教程"><meta name"keywords" content"HTML,CSS,XML,JAVASCRIPT"><meta name"author" content"runoob"><meta char…
最新文章