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

news2024/12/28 4:29:42

文章目录

  • 一,序言
  • 二,准备工作
    • 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.coloradmin.cn/o/1184953.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go 语言初探:从基础到实战

1.Go概述 程序是一段计算机指令的有序组合。程序算法数据结构。任何程序都可以将模块通过三种基本的控制结构&#xff08;顺序、分支、循环&#xff09;进行组合来实现。 Go&#xff08;也称为Golang&#xff09;是一种由Google开发的开源编程语言。设计目标是使编程更简单、…

配置802.1x本地认证,以识别用户身份的示例

组网图形 图1 802.1x本地认证组网图 规格组网需求操作步骤配置注意事项 规格 适用于所有版本、所有形态的AR路由器。 组网需求 PC1(10.10.10.2/30)直接连接到RouterA的Eth2/0/1端口&#xff0c;RouterA的VLANIF10接口IP地址10.10.10.1/30&#xff08;为PC1上的网关IP地址&a…

基于CLIP的图像分类、语义分割和目标检测

OpenAI CLIP模型是一个创造性的突破&#xff1b; 它以与文本相同的方式处理图像。 令人惊讶的是&#xff0c;如果进行大规模训练&#xff0c;效果非常好。 在线工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 3D…

一维码和二维码图像优化——提高读码率

1.算子 1.1 decompose3 &#xff08;彩色图像分割算子&#xff09; 算子&#xff1a;decompose3 ——将三通道图像转换为三个图像函数原型&#xff1a;decompose3(MultiChannelImage : Image1, Image2, Image3 : : ) 功能&#xff1a;将3通道图像转换为具有相同定义域&#…

智慧安防:监控防盗两不误的安防视频监控系统是什么样的?

随着社会的不断发展&#xff0c;安全问题越来越受到人们的关注&#xff0c;特别是对于居住在城市里的人们来说&#xff0c;盗窃问题是影响他们生活质量的重要因素之一。因此&#xff0c;根据市场需求&#xff0c;以监控防盗两不误的智慧监控系统得到了广泛的推广和应用。 一般…

不充不行(同时跑三辆车)

欢迎来到程序小院 不充不行 玩法&#xff1a;点击鼠标左键长按充电桩&#xff0c;别让车落回底线&#xff0c;三辆车同时在跑&#xff0c;要控制三个充电桩的电量&#xff0c;电量为0即为游戏结束&#xff0c;看看你能坚持多少秒哦^^。开始游戏https://www.ormcc.com/play/gam…

迅镭激光与江苏中红外激光研究院达成战略合作意向

11月6日&#xff0c;江苏中红外激光研究院院长沈德元、江苏师范大学物电学院系主任韩彩芹、江苏中红外激光研究院技术副总王飞等领导莅临迅镭激光调研指导并进行合作会谈&#xff0c;迅镭激光董事长颜章健热情接待。双方就成果转化、产业合作、专业人才培养等方面进行深入洽谈&…

Centos7下安装-使用K3S

本文主要内容&#xff1a; 1.安装k3s 2.在idea中&#xff0c;编辑yml文件创建pod 3.在k3d中pod基本操作 4.在k3d中Labeles标签基本操作 5.在k3d中容器的基本操作 &#xff08;k3s的操作与k8s操作不同处在于&#xff0c;k3s每次执行命令&#xff0c;前面需要加上k3s&#xff09;…

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

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

linux基础:3.linux基础环境开发工具和配置。

linux基础环境开发工具和配置 一.学习yum工具进行软件安装&#xff1a;1.什么是yum&#xff1a;2.查看软件包&#xff1a;3.安装和删除&#xff1a;4.yum生态&#xff1a; 二.vim的使用&#xff1a;一.快速介绍一下vim二.vim正常模式&#xff1a;2-1&#xff1a;命令模式1.光标…

HarmonyOS应用开发-ArkTS基础知识

作者&#xff1a;杨亮Jerry 作为多年的大前端程序开发工作者&#xff0c;就目前的形式&#xff0c;个人浅见&#xff0c;在未来3-5年&#xff0c;移动端依旧是Android系统和iOS系统的天下。不过基于鸿蒙系统的应用开发还是值得我们去花点时间去了解下的&#xff0c;阅读并实践官…

一杯子三变:揭秘vue单页应用(spa)与内容动态加载的奥秘

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、什…

找不到x3daudio1_7.dll怎么办?五种解决方法帮你解决x3daudio1_7.dll问题

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“X3DAudio1_7.dll丢失”。这个错误通常会导致音频播放异常或无法正常工作。为了解决这个问题&#xff0c;本文将介绍5种修复X3DAudio1_7.dll丢失的方法&#xff0c;帮助大家快速恢复X3DAudi…

Redis的三种特殊数据类型

文章目录 一、Redis geospatial 地理位置二、Redis Hyperloglog 基数统计的算法三、Redis Bitmaps 位存储&#xff08;0、1&#xff09;总结 一、Redis geospatial 地理位置 1.geoadd&#xff1a;将指定的地理空间位置&#xff08;纬度、经度、名称&#xff09;添加到指定的ke…

vue中 process.env 对象为空对象问题

问题&#xff1a;今天在处理vue项目环境问题的时候&#xff0c;发现直接打印 process 对象和打印 process.env 时 env 对象输出结果是不一样的&#xff0c;如下图所示&#xff1a; 在网上搜索了一番后发现还是有挺多朋友对此感到疑惑的&#xff0c;询问了同事&#xff0c;同…

VINS-Mono-后端优化 (一:预积分残差计算-IMU预积分约束)

这里先回顾一下预积分是怎么来的 VINS-Mono-IMU预积分 &#xff08;三&#xff1a;为什么要预积分预积分推导&#xff09; 这里贴出预积分的公式 具体含义解释看对对应的文章 整个误差函数如下 预积分 α \alpha α β \beta β γ \gamma γ 是用 IMU 预积分获得的增量&a…

Xtrabackup将本地数据迁移上云

本机和云端服务器安装xtrabackup #为了防止每次yum操作都会自动更新&#xff0c;卸载这个软件 yum -y remove mysql57-community-release-el7-10.noarch#下载XtraBackup yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm#激活该yum仓库 percon…

vue使用Echarts5实现词云图

先上官网 词云图有些特殊&#xff0c;它属于Echarts 的扩展&#xff0c;需要额外安装Echarts-wordcloud包。 Echarts 官网 Echarts-wordcloud 词云图官网 先安装 npm install echarts-wordcloud npm install echarts echarts-wordcloud再引入 echarts选一个引入就行&#xff…

Unity 利用UGUI制作圆形进度条

在Unity中使用Image和Text组件就可以制作简单的进度条。 1、首先准备好一张环状的PNG图&#xff0c;如下图。 2、把该图导入Unity中并转换成精灵。 3、在场景中创建Image和Text组件&#xff0c;并把上图中的精灵拖到Image的Source Image中&#xff0c;其中Image组件中的Image …

出口美国操作要点汇总│走美国海运拼箱的注意事项│箱讯科技

01服务标准 美国的货物需要细致的服务&#xff0c;货物到港后的服务也是非常重要的。如果在货物到港15天内&#xff0c;如果没有报关行进行(PROCEED)&#xff0c;货物就会进入了G.O.仓库&#xff0c;G.O.仓库的收费标准是非常高的。 02代理资格审核 美国航线除了各家船公司&a…