springboot实现动态加载自定义配置
一、背景
在项目开发中,遇到有一个很特殊的需求:有一个系统服务要求必须不依赖Mysql,Redis等中间件来完成可以新增配置信息,删除配置信息,并且要求配置可以被程序感知到,完成不同的逻辑。
后来想了下决定使用定时任务,定时读取配置文件,然后将配置信息定时加载进程序中
二、代码实现
1. pom依赖
其中各个依赖版本跟随项目即可
<dependencies><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starterartifactId>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-webartifactId>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-testartifactId><scope>testscope>dependency><dependency><groupId>junitgroupId><artifactId>junitartifactId><scope>testscope>dependency><dependency><groupId>org.projectlombokgroupId><artifactId>lombokartifactId><optional>trueoptional>dependency><dependency><groupId>com.alibabagroupId><artifactId>fastjsonartifactId>dependency><dependency><groupId>org.apache.commonsgroupId><artifactId>commons-lang3artifactId>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
日志配置文件,非核心配置,随意即可
<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%npattern>encoder>appender><appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>/app/logs/${APPNAME}/${APPNAME}.logfile><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>/app/logs/${APPNAME}/${APPNAME}-%d{yyyy-MM-dd}.logfileNamePattern><maxHistory>30maxHistory>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%npattern><charset>UTF-8charset>encoder>appender><appender name="ERROR_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"><File>/app/logs/${APPNAME}/${APPNAME}-error.logFile><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERRORlevel><onMatch>ACCEPTonMatch><onMismatch>DENYonMismatch>filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>/app/logs/${APPNAME}/${APPNAME}-error-%d{yyyy-MM-dd}.logfileNamePattern><maxHistory>30maxHistory>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%npattern><charset>UTF-8charset>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;}@Overridepublic 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);}/*** 项目启动后,会调用该方法*/@Autowiredprivate AccessSystemConfig accessSystemConfig;@Overridepublic 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);@Autowiredprivate 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接口,应该会随着你的修改而产生变化
四、总结
因为领导催的很急,所以只是做了利用了定时任务来简单的实现,也基本满足了需求,但是如果硬扣的话,这么做是无法做到准实时的,并且肯定还有其他方式来实现,但是我觉得这是比较简单快速的实现了,就这样吧,完事收工
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!