springboot配置项动态刷新

文章目录

  • 一,序言
  • 二,准备工作
    • 1. pom.xml引入组件
    • 2. 配置文件示例
  • 三,自定义配置项动态刷新编码实现
    • 1. 定义自定义配置项对象
    • 2. 添加注解实现启动时自动注入
    • 3. 实现yml文件监听以及文件变化处理
  • 四,yaml文件转换为java对象
    • 1. 无法使用前缀绑定的处理
    • 2. 实现yaml文件转换java对象
  • 五、完整代码
    • 1. 代码结构
    • 2. 完整代码备份
    • 3. 运行说明

一,序言

springboot 配置文件一般以yaml方式保存,除了系统配置项如spring、server等外,还有我们自定义的配置项,方便系统启动时自动注入。

自定义的配置项一般是动态配置项,在系统运行过程中,可能需要在线修改,来实现自定义的配置项不停服更新,也就是类似于spring-cloud-starter-config的动态刷新。

由于系统不重启,无法通过自动注入的方式自动更新自定义配置, 这儿便需要我们手动加载yaml文件,转换为java对象,将变化赋值到spring管理的对象中

二,准备工作

采用最常见的snakeyaml、YAMLMapper来实现yaml文件处理。

1. pom.xml引入组件

因 jackson-dataformat-yaml 已经包含snakeyaml ,只需引入前者。

<dependency>
	<groupId>com.fasterxml.jackson.dataformatgroupId>
	<artifactId>jackson-dataformat-yamlartifactId>
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 spring 来保存spring节点数据。

import lombok.Data;

/**
 * 定义Result实体绑定Person
* 与下面的Spring配置等价
* @Component
* @ConfigurationProperties(prefix = "person")
* 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


	4.0.0
	com.fly
	spring-config-refresh
	1.0.0
	jar

	
		UTF-8
		yyyyMMdd-HH
		registry.cn-shanghai.aliyuncs.com
		1.8
		true
	
	
		org.springframework.boot
		spring-boot-starter-parent
		2.2.4.RELEASE
	
	
		
			org.springframework.boot
			spring-boot-starter-web
			
				
					org.springframework.boot
					spring-boot-starter-logging
				
			
		
		
			org.springframework.boot
			spring-boot-starter-log4j2
		
		
			org.springframework.boot
			spring-boot-starter-jdbc
			
				
					org.apache.tomcat
					tomcat-jdbc
				
			
		
		
			org.springframework.boot
			spring-boot-configuration-processor
			true
		
		
			com.alibaba
			druid-spring-boot-starter
			1.2.16
		
		
			com.github.xiaoymin
			knife4j-spring-boot-starter
			2.0.5
		
		
			com.h2database
			h2
			runtime
		
		
			commons-configuration
			commons-configuration
			1.10
		
		
			com.fasterxml.jackson.dataformat
			jackson-dataformat-yaml
		
		
			com.fasterxml.jackson.dataformat
			jackson-dataformat-properties
		
		
			commons-io
			commons-io
			2.6
		
		
			org.springframework.boot
			spring-boot-devtools
			true
		
		
			org.apache.commons
			commons-lang3
		
		
			org.projectlombok
			lombok
			provided
		

		
		
			org.apache.commons
			commons-configuration2
			2.8.0
			test
		
		
			commons-beanutils
			commons-beanutils
			1.9.4
			test
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
	
	
		${project.artifactId}-${project.version}
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			

			
			
				io.fabric8
				docker-maven-plugin
				0.41.0
				
					
						package
						
							build
							push
							remove
						
					
				
				
					
					

					
					${docker.hub}
					
						
							
							
								${docker.hub}/00fly/${project.artifactId}:${project.version}-UTC-${maven.build.timestamp}
							
							
								${project.basedir}
							
						
						
							
								${docker.hub}/00fly/${project.artifactId}:${project.version}
							
								${project.basedir}
							
						
					
				
			
		
		
			
				src/main/java
				
					**/*.java</exclude>
				</excludes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/**
				
			
		
	

//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
     * 
     * @param yamlContent
     * @return
     * @throws IOException
     */
    public static Map<String, String> yamlToMap(String yamlContent)
        throws IOException
    {
        JsonNode jsonNode = yamlMapper.readTree(yamlContent);
        return javaPropsMapper.writeValueAsMap(jsonNode);
    }
    
    /**
     * yaml转properties
     * 
     * @param yamlContent
     * @return
     * @throws IOException
     */
    public static Properties yamlToProperties(String yamlContent)
        throws IOException
    {
        JsonNode jsonNode = yamlMapper.readTree(yamlContent);
        return javaPropsMapper.writeValueAsProperties(jsonNode);
    }
    
    /**
     * yaml转properties字符串
     * 
     * @param yamlContent
     * @return
     * @throws IOException
     */
    public static String yamlToPropText(String yamlContent)
        throws IOException
    {
        JsonNode jsonNode = yamlMapper.readTree(yamlContent);
        return javaPropsMapper.writeValueAsString(jsonNode);
    }
    
    private YamlUtils()
    {
        super();
    }
}
//goto src\main\java\com\fly\refresh\back\ReloadByDataBase.java
package com.fly.refresh.back;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import com.fly.refresh.entity.Welcome;

import lombok.extern.slf4j.Slf4j;

/**
 * 数据库配置表手动刷新
 */
@Slf4j
@Service
public class ReloadByDataBase
{
    @Autowired
    Welcome welcome;
    
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    /**
     * 更新到数据库
     * 
     * @param message
     * @return
     */
    public int update(String message)
    {
        int count = jdbcTemplate.update("UPDATE sys_config SET `value`=? WHERE `key` = 'welcome.message'", message);
        if (count > 0)
        {
            log.info("#### autoRefresh to: {}", message);
            welcome.setMessage(message);
        }
        return count;
    }
}
//goto src\main\java\com\fly\refresh\back\ReloadByFileAlterationMonitor.java
package com.fly.refresh.back;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;

import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.fly.refresh.entity.Person;
import com.fly.refresh.entity.Result;
import com.fly.refresh.entity.Welcome;

import lombok.extern.slf4j.Slf4j;

/**
 * 监听文件变化(推荐)
 */
@Slf4j
@Component
public class ReloadByFileAlterationMonitor
{
    @Autowired
    private Person person;
    
    @Autowired
    private Welcome welcome;
    
    /**
     * thread-safe
     */
    YAMLMapper yamlMapper = new YAMLMapper();
    
    /**
     * thread-safe
     */
    JavaPropsMapper javaPropsMapper = new JavaPropsMapper();
    
    /**
     * 初始化yml文件监听器
     */
    @PostConstruct
    public void initYamlMonitor()
    {
        try
        {
            URL url = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX);
            if (ResourceUtils.isFileURL(url))
            {
                FileAlterationObserver observer = new FileAlterationObserver(url.getPath(), FileFilterUtils.suffixFileFilter(".yml"));
                observer.addListener(new FileAlterationListenerAdaptor()
                {
                    @Override
                    public void onFileChange(File file)
                    {
                        log.info("★★★★★★★★ {} changed.", file.getName());
                        if (StringUtils.equals("application-dev.yml", file.getName()))
                        {
                            try
                            {
                                // yaml to JavaBean
                                String text = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
                                Result javaBean = yamlMapper.readValue(text, Result.class);
                                
                                // Welcome属性拷贝
                                if (javaBean != null && javaBean.getWelcome() != null)
                                {
                                    Welcome from = javaBean.getWelcome();
                                    BeanUtils.copyProperties(from, welcome);
                                    log.info("#### autoRefresh to: {}", welcome);
                                }
                                // Person属性拷贝
                                if (javaBean != null && javaBean.getPerson() != null)
                                {
                                    Person from = javaBean.getPerson();
                                    BeanUtils.copyProperties(from, person);
                                    log.info("#### autoRefresh to: {}", person);
                                }
                            }
                            catch (IOException e)
                            {
                                log.error(e.getMessage(), e.getCause());
                            }
                        }
                    }
                });
                long interval = TimeUnit.SECONDS.toMillis(10);
                FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
                monitor.start();
            }
        }
        catch (Exception e)
        {
            log.error(e.getMessage(), e.getCause());
        }
    }
    
    /**
     * 初始化Properties文件监听器
     */
    @PostConstruct
    public void initPropsMonitor()
    {
        try
        {
            URL url = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX);
            if (ResourceUtils.isFileURL(url))
            {
                FileAlterationObserver observer = new FileAlterationObserver(url.getPath(), FileFilterUtils.suffixFileFilter(".properties"));
                observer.addListener(new FileAlterationListenerAdaptor()
                {
                    @Override
                    public void onFileChange(File file)
                    {
                        log.info("★★★★★★★★ {} changed.", file.getName());
                        if (StringUtils.equals("welcome.properties", file.getName()))
                        {
                            try
                            {
                                // Properties to JavaBean
                                Properties prop = PropertiesLoaderUtils.loadProperties(new ClassPathResource(file.getName()));
                                Result javaBean = javaPropsMapper.readPropertiesAs(prop, Result.class);
                                if (javaBean != null && javaBean.getWelcome() != null)
                                {
                                    String value = javaBean.getWelcome().getMessage();
                                    log.info("#### autoRefresh to: {}", value);
                                    welcome.setMessage(value);
                                }
                            }
                            catch (IOException e)
                            {
                                log.error(e.getMessage(), e.getCause());
                            }
                        }
                    }
                });
                long interval = TimeUnit.SECONDS.toMillis(10);
                FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
                monitor.start();
            }
        }
        catch (Exception e)
        {
            log.error(e.getMessage(), e.getCause());
        }
    }
    
}
//goto src\main\java\com\fly\refresh\back\ReloadByReloadingStrategy.java
package com.fly.refresh.back;

import javax.annotation.PostConstruct;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.FileConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.fly.refresh.entity.Welcome;

import lombok.extern.slf4j.Slf4j;

/**
 * 文件重加载策略(不推荐)
 */
@Slf4j
@Component
public class ReloadByReloadingStrategy
{
    String lastMsg;
    
    @Autowired
    Welcome welcome;
    
    FileConfiguration propConfig;
    
    /**
     * 初始化properties文件重加载策略
     */
    @PostConstruct
    public void initReloadingStrategy()
    {
        try
        {
            // 只支持properties
            propConfig = new PropertiesConfiguration("welcome.properties");
            FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
            strategy.setRefreshDelay(10000L);
            propConfig.setReloadingStrategy(strategy);
            lastMsg = propConfig.getString("welcome.message");
        }
        catch (ConfigurationException e)
        {
            log.error(e.getMessage(), e.getCause());
        }
    }
    
    /**
     * 配置变更时刷新
     */
    @Scheduled(initialDelay = 30000L, fixedRate = 10000L)
    public void autoRefresh()
    {
        // 是否变更,何时刷新逻辑实现
        String message = propConfig.getString("welcome.message");
        if (!StringUtils.equals(message, lastMsg))
        {
            log.info("#### autoRefresh to: {}, after properties Changed", message);
            welcome.setMessage(message);
            lastMsg = message;
        }
    }
}
//goto src\main\java\com\fly\refresh\entity\Dog.java
package com.fly.refresh.entity;

import lombok.Data;

@Data
public class Dog
{
    private String name;
    
    private Integer age;
}
//goto src\main\java\com\fly\refresh\entity\Person.java
package com.fly.refresh.entity;

import java.util.Date;
import java.util.List;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;

import lombok.Data;

@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person
{
    private String name;
    
    private Integer age;
    
    private Boolean happy;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birth;
    
    private Map<String, Object> maps;
    
    private List<Object> lists;
    
    private Dog dog;
}
//goto src\main\java\com\fly\refresh\entity\Result.java
package com.fly.refresh.entity;

import java.util.Map;

import lombok.Data;

/**
 * 定义Result实体绑定Person、Welcome
* 与下面的Spring配置等价
*
* @Component
* @ConfigurationProperties(prefix = "person")
* public class Person { ... }
*
* @Component
* @ConfigurationProperties(prefix = "welcome")
* 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配置文件实体
* 使用@Lazy待SysDataBaseConfig方法initDatabasePropertySource执行完再注入
* 否则仅使用数据库初始化时开发环境和Jar运行message值不一致 * * @author 00fly * @version [版本号, 2023年11月3日] * @see [相关类/方法] * @since [产品/模块版本] */
@Data @Lazy @Component @ConfigurationProperties(prefix = "welcome") public class Welcome { /** * message赋值方式:
* 1. Configuration注解在SysDataBaseConfig
* 2. spring.profiles.active指定dev即application-dev.yml
* 3. welcome.properties内容变更时触发
* 4. /show/refresh接口被调用时触发
* 方式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. /show/refresh 接口调用刷新配置
  4. 如需要测试数据库配置,请在application.yml设置spring.profiles.active为test或者prod

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

-over-

你可能感兴趣的:(系统架构,Spring,spring,boot,java,后端)