当我们项目下面有很多个模块,而且每个模块都是独立运行时,我们将面临一个问题 “解决配置繁琐问题”(通常需要在每个模块配置数据库、Redis、MongoDB等信息)。
最容易想到的解决方法就是搭建一个配置中心,但是项目已经成型,再去搭建配置中心,成本和工期不允许(就拿 nacos 为例,我们需要将 @Value 注解修改为 @NacosValue,还有其他配置和注解需要引入)。
那么有没有更简单的方法呢!有的,自定义 PropertySourcesPlaceholderConfigurer 类,根据自己的需要从一个默认的全局配置文件加载所有项目模块公用的配置信息。
本文将介绍怎样自定义 PropertySourcesPlaceholderConfigurer,然后实现配置公用。
本项目使用 Spring Boot 2.3.1.RELEASE 为基础
本项目采用属性文件 properties 作为默认配置文件
本项目默认将读取 D:\application.properties 配置文件,用户可以通过 my_config 环境变量修改配置文件路径
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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.hxstrive</groupId> <artifactId>springboot_hello_world</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_hello_world</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.7</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
MyPropertySourcesPlaceholderConfigurer.java
直接继承 PropertySourcesPlaceholderConfigurer 类的 loadProperties() 方法,代码如下:
package com.hxstrive.hello_world.customer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.*; /** * 自定义属性资源加载器 * @author huangxin 2022/8/16 */ @Component public class MyPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer { private static final Logger LOG = LoggerFactory.getLogger(MyPropertySourcesPlaceholderConfigurer.class); /** * 默认配置文件路径 */ private static final String DEFAULT_CONFIG_PATH = "D:\\application.properties"; /** * 系统配置的环境变量 */ private static final String CFG_ENVIRONMENT_VARIABLE = "my_config"; /** * 这里重新加载 * @param props 元素 * @throws IOException 异常 */ @Override protected void loadProperties(Properties props) throws IOException { super.loadProperties(props); // 开始解析自定义的配置文件 // 1.获取配置文件路径,如果没有配置环境变量,则采用默认配置文件 String cfgPath = System.getenv(CFG_ENVIRONMENT_VARIABLE); if(StringUtils.isEmpty(cfgPath)) { cfgPath = DEFAULT_CONFIG_PATH; } // 2.判断配置文件是否存在、是否有效 File file = new File(cfgPath); if(!file.exists()) { LOG.info("Config file does not exist. path={}", cfgPath); return; } if(!file.isFile()) { LOG.info("Config file is not a file. path={}", cfgPath); return; } // 3.开始解析配置文件 Properties properties = new Properties(); properties.load(new FileReader(file)); Enumeration<?> propKeys = properties.propertyNames(); while(propKeys.hasMoreElements()) { String name = String.valueOf(propKeys.nextElement()); String value = properties.getProperty(name); // 在这里可以对加密的 VALUE 进行解密操作 // 将自定义的配置放到 Spring 标准配置中 props.put(name, value); } } }
MyPropertySourcesPlaceholderConfigurerTest.java
测试代码,使用 @SpringBootTest 注解创建一个简单的测试类,该类通过 @Value 自动注入我们在默认配置文件中配置的属性。代码如下:
package com.hxstrive.hello_world.customer; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MyPropertySourcesPlaceholderConfigurerTest { @Value("${key1}") private String key1; @Value("${key2}") private String key2; @Test void contextLoads() { System.out.println("key1=" + key1); System.out.println("key2=" + key2); } }
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.1.RELEASE) 2022-08-17 13:46:11.475 INFO 13388 --- [ main] PropertySourcesPlaceholderConfigurerTest : Starting MyPropertySourcesPlaceholderConfigurerTest on hxstrive with PID 13388 (started by Administrator in D:\learn\demo_workspace\demo_springboot\springboot_hello_world) 2022-08-17 13:46:11.507 INFO 13388 --- [ main] PropertySourcesPlaceholderConfigurerTest : No active profile set, falling back to default profiles: default 2022-08-17 13:46:14.163 INFO 13388 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2022-08-17 13:46:14.600 INFO 13388 --- [ main] PropertySourcesPlaceholderConfigurerTest : Started MyPropertySourcesPlaceholderConfigurerTest in 4.247 seconds (JVM running for 6.795) key1=value1 key2=def value2 2022-08-17 13:46:15.210 INFO 13388 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' Process finished with exit code 0
有时,项目鉴于安全的目的,不能在配置文件中以明文的方式配置数据库、Redis、第三方系统的密码。此时,我们可以通过自定义 PropertySourcesPlaceholderConfigurer,实现加载自定义属性配置。在加载自定义属性配置时,根据一些规则,对某些配置进行解密即可。这样就能很好的解决明文密码配置问题,当然还有其他方法,读者可以留言,一起探讨。
注意:使用该种方式加载的属性不能在其他属性配置文件中使用 ${} 进行引用。
如果要解决该问题,可以参考“Spring Boot 加载自定义属性文件(@PropertySource注解)”