springboot实现动态加载自定义配置
一、背景在项目开发中,遇到有一个很特殊的需求:有一个系统服务要求必须不依赖Mysql,Redis等中间件来完成可以新增配置信息,删除配置信息,并且要求配置可以被程序感知到,完成不同的逻辑。后来想了下决定使用定时任务,定时读取配置文件,然后将配置信息定时加载进程序中二、代码实现1. pom依赖其中各个依赖版本跟随项目即可<dependencies><dependency>&l
·
一、背景
在项目开发中,遇到有一个很特殊的需求:有一个系统服务要求必须不依赖Mysql,Redis等中间件来完成可以新增配置信息,删除配置信息,并且要求配置可以被程序感知到,完成不同的逻辑。
后来想了下决定使用定时任务,定时读取配置文件,然后将配置信息定时加载进程序中
二、代码实现
1. pom依赖
其中各个依赖版本跟随项目即可
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
2. 新加配置文件
2.1 access-system.properties
自定义配置文件,用于配置允许访问本系统的系统以及系统秘钥,该文件必须放在与jar包平级的config目录下,如需新增配置,将配置按照格式写入,即可自动加载
000001=MDAwMDAxQQ==
000002=MDAwMDAyQg==
000003=MDAwMDAzQw==
2.2 application.properties
springboot项目配置文件
server.port=8082
#设置自动加载配置信息的时间(每分钟执行一次)
read.access.system.schedule=0 * * * * ?
2.3 logback.xml
日志配置文件,非核心配置,随意即可
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="5 minutes" debug="false">
<property name="APPNAME" value="task-read-config"/>"
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n</pattern>
</encoder>
</appender>
<!-- 以天为单位生成日志文件-->
<appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/app/logs/${APPNAME}/${APPNAME}.log</file>
<!--每天生成一个日志文件,保存30天的日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志保存地址 -->
<fileNamePattern>/app/logs/${APPNAME}/${APPNAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} [%file:%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!--错误信息日志-->
<appender name="ERROR_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>/app/logs/${APPNAME}/${APPNAME}-error.log</File>
<!-- 只打印错误日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/app/logs/${APPNAME}/${APPNAME}-error-%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} [%file:%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="com.htsc.boot" level="INFO" additivity="false">
<appender-ref ref="ROOT_APPENDER"/>
<appender-ref ref="ERROR_APPENDER"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT" additivity="false"/>
<appender-ref ref="ROOT_APPENDER" additivity="false"/>
<appender-ref ref="ERROR_APPENDER" additivity="false"/>
</root>
</configuration>
3. 新建相关实体类
3.1 启动类
package com.task.read;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 定时读取配置文件加载到程序中
* @EnableScheduling表示对定时任务的支持
*
* @author zhang
*/
@EnableScheduling
@SpringBootApplication
public class TaskReadConfigApplication {
public static void main(String[] args) {
SpringApplication.run(TaskReadConfigApplication.class, args);
}
}
3.1 新建配置文件对应实体类
package com.task.read.entity;
import java.io.Serializable;
/**
* 配置信息的实体类
*
* @author zhang
* @date 2021-09-03 23:02:43
*/
public class AccessSystem implements Serializable {
private static final long serialVersionUID = 8333665889439802146L;
private String systemId;
private String secretKey;
public AccessSystem() {
}
public AccessSystem(String systemId, String secretKey) {
this.systemId = systemId;
this.secretKey = secretKey;
}
public String getSystemId() {
return systemId;
}
public void setSystemId(String systemId) {
this.systemId = systemId;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("{")
.append("\"systemId\":").append(systemId)
.append(", \"secretKey\":").append("******")
.append('}');
return sb.toString();
}
}
3.2 新建config类用于存储所有配置信息
package com.task.read.config;
import com.task.read.entity.AccessSystem;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 配置了用于存储所有配置信息
*
* @author zhang
* @date 2021-09-03 23:04:01
*/
@Component
public class AccessSystemConfig {
private List<AccessSystem> systemList = new ArrayList<>();
public List<AccessSystem> getSystemList() {
return systemList;
}
public void setSystemList(List<AccessSystem> systemList) {
this.systemList = systemList;
}
}
3.3 定时任务类
package com.task.read.schedule;
import com.task.read.config.AccessSystemConfig;
import com.task.read.entity.AccessSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
/**
* 定时任务,定时读取所有accessSystem配置信息,加载到程序中
*
* @author zhang
* @date 2021-09-03 23:19:09
*/
@Component
public class ScheduleTask implements ApplicationRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleTask.class);
private static String configLocation = null;
//获取配置文件所在目录
static {
//获取当前类所在的目录,即jar包目录
String property = System.getProperty("user.dir");
//获取自定义配置文件的路径
configLocation = property + "/config/access-system.properties";
LOGGER.info("access-system.properties location:[{}]", configLocation);
}
/**
* 项目启动后,会调用该方法
*/
@Autowired
private AccessSystemConfig accessSystemConfig;
@Override
public void run(ApplicationArguments args) {
LOGGER.info("======================================================================");
LOGGER.info("=== The task for reading the accessSystemConfig has been started ===");
LOGGER.info("======================================================================");
readConfig();
}
/**
* 定时加载 accessSystem
*/
@Scheduled(cron = "${read.access.system.schedule}")
public void scheduleTask() {
readConfig();
}
/**
* 读取配置,将配置加载到程序中
*/
private void readConfig() {
LOGGER.info("============= parse accessSystemConfig is start =============");
long startTime = System.currentTimeMillis();
//加载文件
try (FileInputStream inputStream = new FileInputStream(configLocation);
Reader reader = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(reader)) {
//存放本次读取出的配置
List<AccessSystem> newSystemList = new ArrayList<>();
//将文件映射成properties对象
Properties properties = new Properties();
properties.load(br);
//解析properties对象,存入newSystemList中
properties.forEach((k, v) -> {
//解析秘钥,并保存
AccessSystem accessSystem = new AccessSystem(k.toString(), v.toString());
newSystemList.add(accessSystem);
});
//清空原有数据,并保存新获取到的数据(注这里可能存在并发问题,实际使用中需要处理)
accessSystemConfig.getSystemList().clear();
accessSystemConfig.setSystemList(newSystemList);
} catch (Exception e) {
LOGGER.warn("read accessSystemConfig is fail, cause by:", e);
} finally {
//打印出已加载的系统信息
List<String> systemNoList = accessSystemConfig.getSystemList().stream().map(AccessSystem::getSystemId).collect(Collectors.toList());
LOGGER.info("Loaded systemNoList:{}", systemNoList);
LOGGER.info("======= parse accessSystemConfig is complete, cost:{}ms ======", System.currentTimeMillis() - startTime);
}
}
}
3.4 Controller类
package com.task.read.controller;
import com.alibaba.fastjson.JSON;
import com.task.read.config.AccessSystemConfig;
import com.task.read.entity.AccessSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 控制器层,简单模拟下
*
* @author zhang
* @date 2021-09-03 23:13:59
*/
@RestController
@RequestMapping
public class AdminController {
private static final Logger LOGGER = LoggerFactory.getLogger(AdminController.class);
@Autowired
private AccessSystemConfig accessSystemConfig;
@GetMapping("/getConfig")
public Map<String, Object> getConfig() {
Map<String, Object> result = new HashMap<>();
//这里模拟根据配置 完成不同的逻辑
List<AccessSystem> systemList = accessSystemConfig.getSystemList();
systemList.forEach(accessSystem -> {
result.put(accessSystem.getSystemId(), accessSystem.getSecretKey());
LOGGER.info("accessSystem:{}", JSON.toJSONString(accessSystem));
});
return result;
}
}
三、 功能测试
1. 系统启动
系统启动后,看下启动日志,如果成功启动,会打印如下信息
2. postman请求/getConfig接口
请求完成,看具体返回值,是否正常返回
3. 修改access-system.properties文件
修改配置文件中的内容后,定时任务执行时,就会重新加载配置,这时候再去请求/getConfig接口,应该会随着你的修改而产生变化
四、总结
因为领导催的很急,所以只是做了利用了定时任务来简单的实现,也基本满足了需求,但是如果硬扣的话,这么做是无法做到准实时的,并且肯定还有其他方式来实现,但是我觉得这是比较简单快速的实现了,就这样吧,完事收工
更多推荐
已为社区贡献3条内容
所有评论(0)