1. 概述
在《Apollo 极简入门》中,我们已经学习了如何搭建一个 Apollo 服务。如果还没有的胖友,赶紧先去简单学习下,重点是跟着该文「2. 单机部署」小节,自己搭建一个 Apollo 服务。
本文,我们来学习下如何在 Spring Boot 中,将 Apollo 作为一个配置中心,实现分布式环境下的配置管理。
2. 快速入门
示例代码对应仓库:lab-45-apollo-demo。
本小节,我们会在 Apollo 服务中定义配置,并使用 并使用 @ConfigurationProperties 和 @Value 注解,读取该配置。
友情提示:如果胖友看过《Spring Boot 配置文件入门》的「2. 自定义配置」小节,就会发现本小节是对标这块的内容。
如果没看过,也没关系,艿艿只是提一下而已,嘿嘿。继续往下看即可。
2.1 引入依赖
在 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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-45-apollo-demo</artifactId>
<dependencies>
<!-- Spring Boot Starter 基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 引入 Apollo 客户端,内置对 Apollo 的自动化配置 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
</project>
- 引入 apollo-client 依赖,Apollo 客户端,内置对 Apollo 的自动化配置。
2.2 配置文件
在 application.yml 中,添加 Apollo 配置,如下:
server:
port: 7070 # 避免和本地的 Apollo Portal 端口冲突
app:
id: demo-application # 使用的 Apollo 的项目(应用)编号
apollo:
meta: http://127.0.0.1:8080 # Apollo Meta Server 地址
bootstrap:
enabled: true # 是否开启 Apollo 配置预加载功能。默认为 false。
eagerLoad:
enable: true # 是否开启 Apollo 支持日志级别的加载时机。默认为 false。
namespaces: application # 使用的 Apollo 的命名空间,默认为 application。
- app.id 配置项,使用的 Apollo 的项目(应用)编号。稍后,我们在「2.3 创建 Apollo 配置」小节中进行创建。
- apollo.meta 配置项,使用的 Apollo Meta Server 地址。
- apollo.bootstrap.enabled 配置项,是否开启 Apollo 配置预加载功能。默认为 false。😈 这里,我们设置为 true,保证使用 @Value 和 @ConfigurationProperties 注解,可以读取到来自 Apollo 的配置项。
- apollo.bootstrap.eagerLoad.enable 配置项,是否开启 Apollo 支持日志级别的加载时机。默认为 false。😈 这里,我们设置为 true,保证 Spring Boot 应用的 Logger 能够使用来自 Apollo 的配置项。
- apollo.bootstrap.namespaces 配置项,使用的 Apollo 的命名空间,默认为 application。关于 Apollo 的概念,可见《Apollo 核心概念之“Namespace”》文章。
2.3 创建 Apollo 配置
在 Apollo 中创建 Apollo 配置,内容如下图所示:
2.4 OrderProperties
创建 OrderProperties 配置类,读取 order 配置项。代码如下:
@Component
@ConfigurationProperties(prefix = "order")
public class OrderProperties {
/**
* 订单支付超时时长,单位:秒。
*/
private Integer payTimeoutSeconds;
/**
* 订单创建频率,单位:秒
*/
private Integer createFrequencySeconds;
// ... 省略 set/get 方法
}
- 在类上,添加 @Component 注解,保证该配置类可以作为一个 Bean 被扫描到。
- 在类上,添加 @ConfigurationProperties 注解,并设置 prefix = "order" 属性,这样它就可以读取前缀为 order 配置项,设置到配置类对应的属性上。
2.5 Application
创建 Application.java 类,配置 @SpringBootApplication 注解即可。代码如下:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Component
public class OrderPropertiesCommandLineRunner implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private OrderProperties orderProperties;
@Override
public void run(String... args) {
logger.info("payTimeoutSeconds:" + orderProperties.getPayTimeoutSeconds());
logger.info("createFrequencySeconds:" + orderProperties.getCreateFrequencySeconds());
}
}
@Component
public class ValueCommandLineRunner implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Value(value = "${order.pay-timeout-seconds}")
private Integer payTimeoutSeconds;
@Value(value = "${order.create-frequency-seconds}")
private Integer createFrequencySeconds;
@Override
public void run(String... args) {
logger.info("payTimeoutSeconds:" + payTimeoutSeconds);
logger.info("createFrequencySeconds:" + createFrequencySeconds);
}
}
}
① 在 OrderPropertiesCommandLineRunner 类中,我们测试了使用 @ConfigurationProperties 注解的 OrderProperties 配置类,读取 order 配置项的效果。
② 在 ValueCommandLineRunner 类中,我们测试了使用 @Value 注解,读取 order 配置项的效果
下面,我们来执行 Application 的 #main(String[] args) 方法,启动 Spring Boot 应用。输出日志如下:
# 从 Apollo 中,读取配置。
2020-01-26 12:10:04.574 INFO 12179 --- [ main] c.c.f.a.i.DefaultMetaServerProvider : Located meta services from apollo.meta configuration: http://127.0.0.1:8080!
2020-01-26 12:10:04.581 INFO 12179 --- [ main] c.c.f.apollo.core.MetaDomainConsts : Located meta server address http://127.0.0.1:8080 for env UNKNOWN from com.ctrip.framework.apollo.internals.DefaultMetaServerProvider
2020-01-26 12:10:04.945 INFO 12179 --- [ main] o.s.c.a.ConfigurationClassPostProcessor : Cannot enhance @Configuration bean definition 'com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.
# ValueCommandLineRunner 输出
2020-01-26 12:10:05.085 INFO 12179 --- [ main] s.l.a.Application$ValueCommandLineRunner : payTimeoutSeconds:120
2020-01-26 12:10:05.086 INFO 12179 --- [ main] s.l.a.Application$ValueCommandLineRunner : createFrequencySeconds:120
# OrderPropertiesCommandLineRunner 输出
2020-01-26 12:10:05.086 INFO 12179 --- [ main] ication$OrderPropertiesCommandLineRunner : payTimeoutSeconds:120
2020-01-26 12:10:05.086 INFO 12179 --- [ main] ication$OrderPropertiesCommandLineRunner : createFrequencySeconds:120
- 两个 CommandLineRunner 都读取 order 配置项成功,美滋滋。
友情提示:艿艿自己尝试了在《芋道 Spring Boot 配置文件入门》的如下小节的功能,都可以完美支持:
- 「3. 配置随机值」
- 「4. 配置引用」
3. 多环境配置
示例代码对应仓库:lab-45-apollo-demo-profiles。
在《芋道 Spring Boot 配置文件入门》的「6. 多环境配置」中,我们介绍如何基于 spring.profiles.active 配置项,在 Spring Boot 实现多环境的配置功能。在本小节中,我们会在该基础之上,实现结合 Apollo 的多环境配置。
在 Apollo 中,我们可以通过搭建不同环境的 Config Service + Admin Service 服务。然后,在每个 application-${profile}.yaml 配置文件中,配置对应的 Config Service + Admin Service 服务即可。
下面,我们来搭建一个结合 Apollo 的多环境的示例。
3.1 创建 Apollo 配置
在 Apollo 中创建 Apollo 配置,创建一个 AppId 为 demo-application-profiles 的项目,并配置 DEV(开发环境)和 PRO(生产环境)两套配置。如下图所示:
这里,我们通过不同环境,使用不同 server.port 配置项。这样, Spring Boot 项目启动后,从日志中就可以看到生效的服务器端口,嘿嘿~从而模拟不同环境,不同配置。
3.2 引入依赖
在 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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-45-apollo-demo-profiles</artifactId>
<dependencies>
<!-- 实现对 SpringMVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Apollo 客户端,内置对 Apollo 的自动化配置 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
</project>
- 引入 spring-boot-starter-web 原来的原因是,我们会使用 server.port 配置项,配置 Tomcat 的端口。
3.3 配置文件
在 resources 目录下,创建 2 个配置文件,对应不同的环境。如下:
- application-dev.yaml,开发环境。
app:
id: demo-application-profiles # 使用的 Apollo 的项目(应用)编号
apollo:
meta: http://127.0.0.1:8080 # Apollo Meta Server 地址
bootstrap:
enabled: true # 是否开启 Apollo 配置预加载功能。默认为 false。
eagerLoad:
enable: true # 是否开启 Apollo 支持日志级别的加载时机。默认为 false。
namespaces: application # 使用的 Apollo 的命名空间,默认为 application。
- 和「2.2 配置文件」不同的点,重点是 apollo.meta 配置项,设置为 DEV 环境的 Apollo Meta Server 地址。
- application-prod.yaml,生产环境。
app:
id: demo-application-profiles # 使用的 Apollo 的项目(应用)编号
apollo:
meta: http://127.0.0.1:18080 # Apollo Meta Server 地址
bootstrap:
enabled: true # 是否开启 Apollo 配置预加载功能。默认为 false。
eagerLoad:
enable: true # 是否开启 Apollo 支持日志级别的加载时机。默认为 false。
namespaces: application # 使用的 Apollo 的命名空间,默认为 application。
- 和「2.2 配置文件」不同的点,重点是 apollo.meta 配置项,设置为 PROD 环境的 Apollo Meta Server 地址。
另外,我们会创建 application.yaml 配置文件,放不同环境的相同配置。例如说,spring.application.name 配置项,肯定是相同的啦。配置如下:
#server:
spring:
application:
name: demo-application
3.4 ProfilesApplication
创建 ProfilesApplication.java 类,配置 @SpringBootApplication 注解即可。代码如下:
@SpringBootApplication
public class ProfilesApplication {
public static void main(String[] args) {
SpringApplication.run(ProfilesApplication.class, args);
}
}
3.5 简单测试
下面,我们使用命令行参数进行 --spring.profiles.active 配置项,实现不同环境,读取不同配置文件。
① 开发环境示例:直接在 IDEA 中,增加 --spring.profiles.active=dev 到 Program arguments 中。如下图所示:
启动 Spring Boot 应用,输出日志如下:
# 省略其它日志...
2020-01-27 12:08:57.051 INFO 27951 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
- Tomcat 启动在 8081 端口,符合读取 DEV 环境的配置。
② 生产环境示例:直接在 IDEA 中,增加 --spring.profiles.active=prod 到 Program arguments 中。如下图所示:
启动 Spring Boot 应用,输出日志如下:
# 省略其它日志...
2020-01-27 12:11:31.159 INFO 28150 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8084 (http)
- Tomcat 启动在 8084 端口,符合读取 PROD 环境的配置。
另外,关于 Spring Boot 应用的多环境部署,胖友也可以看看《芋道 Spring Boot 持续交付 Jenkins 入门》文章。
4. 自动刷新配置
示例代码对应仓库:lab-45-apollo-demo-auto-refresh。
在上面的示例中,我们已经实现从 Apollo 读取配置。那么,在应用已经启动的情况下,如果我们将读取的 Apollo 的配置进行修改时,应用是否会自动刷新本地的配置呢?答案是,针对 @Value 注解的属性是的,针对 @ConfigurationProperties 注解的配置类需要做特殊处理。
下面,我们来搭建一个自动刷新配置的示例。
4.1 引入依赖
在 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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-45-apollo-demo-auto-refresh</artifactId>
<dependencies>
<!-- 实现对 SpringMVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Apollo 客户端,内置对 Apollo 的自动化配置 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
</project>
- 引入 spring-boot-starter-web 依赖的原因,稍后我们会编写 API 接口,查看来自 Apollo 配置的最新值。
4.2 创建 Apollo 配置
在 Apollo 中创建 Apollo 配置,内容如下图所示:
4.3 配置文件
在 application.yml 中,添加 Apollo 配置,如下:
server:
port: 7070 # 避免和本地的 Apollo Portal 端口冲突
app:
id: demo-application # 使用的 Apollo 的项目(应用)编号
apollo:
meta: http://127.0.0.1:8080 # Apollo Meta Server 地址
bootstrap:
enabled: true # 是否开启 Apollo 配置预加载功能。默认为 false。
eagerLoad:
enable: true # 是否开启 Apollo 支持日志级别的加载时机。默认为 false。
namespaces: application # 使用的 Apollo 的命名空间,默认为 application。
- 和「2.2 配置文件」的差异点,只是添加了 server.port 配置项。
4.4 TestProperties
在 cn.iocoder.springboot.lab45.apollodemo.properties 包下,创建 TestProperties 配置类,读取 test 配置项。代码如下:
@Component
@ConfigurationProperties(prefix = "test")
public class TestProperties {
/**
* 测试属性
*/
private String test;
public String getTest() {
return test;
}
public TestProperties setTest(String test) {
this.test = test;
return this;
}
}
4.5 DemoController
在 cn.iocoder.springboot.lab45.apollodemo.controller 包下,创建 DemoController 类,提供返回配置的 API 接口。代码如下:
@RestController
@RequestMapping("/demo")
public class DemoController {
@Value("${test.test}")
private String test;
@GetMapping("/test")
public String test() {
return test;
}
@Autowired
private TestProperties testProperties;
@GetMapping("/test_properties")
public TestProperties testProperties() {
return testProperties;
}
}
- /demo/test 接口,测试 @Value 注解的属性的动刷新配置的功能。
- /demo/test_properties 接口,测试 @ConfigurationProperties 注解的 TestProperties 配置类的动刷新配置的功能。
4.6 Application
创建 Application.java 类,配置 @SpringBootApplication 注解即可。代码如下:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.7 简单测试
启动 Spring Boot 应用,开始我们本轮的测试。
① 分别请求 /demo/test、/demo/test_properties 接口,响应结果如下:
# /demo/test 接口
哈哈哈哈
# /demo/test_properties 接口
{
"test": "哈哈哈哈"
}
② 修改 Apollo 中的 test.test 配置项设置为 呵呵呵呵。如下图所示:
并且,我们可以看到控制台会输出 Apollo 的自动刷新配置相关的日志。日志如下:
2020-01-27 15:42:52.537 INFO 31590 --- [Apollo-Config-1] c.f.a.s.p.AutoUpdateConfigChangeListener : Auto update apollo changed value successfully, new value: 呵呵呵呵, key: test.test, beanName: demoController, field: cn.iocoder.springboot.lab45.apollodemo.controller.DemoController.test
③ 分别请求 /demo/test、/demo/test_properties 接口,响应结果如下:
# /demo/test 接口
呵呵呵呵
# /demo/test_properties 接口
{
"test": "哈哈哈哈"
}
- @Value 注解的属性,自动刷新配置成功。
- @ConfigurationProperties 注解的配置类,自动刷新配置失败。
- 目前 Apollo 暂时未提供 @ConfigurationProperties 注解的配置类的自动刷新配置的功能,并且在纯 Spring Boot 项目中,没有太好的实现自动刷新配置的方式,具体可见 issues#1657 讨论。
- 针对 Spring Cloud 项目,可以参考 issue#2846 讨论,基于 EnvironmentChangeEvent 或 RefreshScope。相关代码实现,可以参考 apollo-use-cases 项目中的 ZuulPropertiesRefresher.java 和 apollo-demo 项目中的 SampleRedisConfig.java 以及 SpringBootApolloRefreshConfig.java。
4.8 Apollo 配置监听器
默认情况下,Apollo 已经能够满足我们绝大多数场景下的自动刷新配置的功能。但是,在一些场景下,我们仍然需要自定义 Apollo 配置监听器,实现对 Apollo 配置的监听,执行自定义的逻辑。
例如说,当数据库连接的配置发生变更时,我们需要通过监听该配置的变更,重新初始化应用中的数据库连接,从而访问到新的数据库地址。
又例如说,当日志级别发生变更时,我们需要通过监听该配置的变更,设置应用中的 Logger 的日志级别,从而后续的日志打印可以根据新的日志级别。
可能这么说,胖友会觉得有点抽象,我们来搭建一个日志级别的示例。
在 cn.iocoder.springboot.lab45.apollodemo.listener 包下,创建 LoggingSystemConfigListener 类,监听 logging.level 配置项的变更,修改 Logger 的日志级别。代码如下:
@Component
public class LoggingSystemConfigListener {
/**
* 日志配置项的前缀
*/
private static final String LOGGER_TAG = "logging.level.";
@Resource
private LoggingSystem loggingSystem;
@ApolloConfig
private Config config;
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) throws Exception {
// <X> 获得 Apollo 所有配置项
Set<String> keys = config.getPropertyNames();
// <Y> 遍历配置集的每个配置项,判断是否是 logging.level 配置项
for (String key : keys) {
// 如果是 logging.level 配置项,则设置其对应的日志级别
if (key.startsWith(LOGGER_TAG)) {
// 获得日志级别
String strLevel = config.getProperty(key, "info");
LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
// 设置日志级别到 LoggingSystem 中
loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level);
}
}
}
}
- loggingSystem 属性,是 Spring Boot Logger 日志系统,通过 LoggingSystem 可以进行日志级别的修改。
- config 属性,是 Apollo Config 对象,通过它获取本地缓存的 Apollo 配置。
- 在 #onChange(ConfigChangeEvent changeEvent) 方法上,我们添加了 @ApolloConfigChangeListener 注解,声明该方法处理 Apollo 的配置变化。
- <X> 处,通过 Apollo Config 对象,获得所有配置项的 KEY。
- <Y> 处,遍历每个配置项的 KEY,判断如果是 logging.level 配置项,则设置到 LoggingSystem 中,从而修改日志级别。详细的整个过程,胖友看看艿艿的详细的注释,嘿嘿~
4.9 再次测试
① 在 DemoController 类中,增加如下 API 接口。代码如下:
private Logger logger = LoggerFactory.getLogger(getClass());
@GetMapping("/logger")
public void logger() {
logger.debug("[logger][测试一下]");
}
- 如果 DemoController 对应的 Logger 日志级别是 DEBUG 以上,则无法打印出日志。
② 在 Apollo 中,增加 logging.level.cn.iocoder.springboot.lab45.apollodemo.controller 配置项为 INFO,具体内容如下图:
③ 启动 Spring Boot 应用,开始我们本轮的测试。
④ 请求 /demo/logger 接口,控制台并未打印日志,因为当前日志级别是 INFO。
⑤ 在 Apollo 中,增加 logging.level.cn.iocoder.springboot.lab45.apollodemo.controller 配置项为 DEBUG,具体内容如下图:
⑥ 请求 /demo/logger 接口,控制台打印日志,因为当前日志级别是 DEBUG。日志内容如下:
2020-01-27 16:36:24.231 DEBUG 33860 --- [nio-7070-exec-3] c.i.s.l.a.controller.DemoController : [logger][测试一下]
- 符合预期。
更多 Apollo 自定义配置监听器,实现各种组件刷新的文章,也可以阅读如下两篇文章:
- 《spring boot 动态调整线上日志级别》
- 《Apollo 应用之动态调整线上数据源(DataSource)》
5. 配置加密
示例代码对应仓库:lab-45-apollo-demo-jasypt。
考虑到安全性,我们可能最好将配置文件中的敏感信息进行加密。例如说,MySQL 的用户名密码、第三方平台的 Token 令牌等等。不过,Apollo 暂时未内置配置加密的功能。官方文档说明如下:
FROM https://github.com/ctripcorp/apollo/wiki/FAQ
7. Apollo 是否支持查看权限控制或者配置加密?
从 1.1.0 版本开始,apollo-portal 增加了查看权限的支持,可以支持配置某个环境只允许项目成员查看私有 Namespace 的配置。
这里的项目成员是指:
- 项目的管理员
- 具备该私有Namespace在该环境下的修改或发布权限
配置方式很简单,用超级管理员账号登录后,进入 管理员工具 - 系统参数 页面新增或修改 configView.memberOnly.envs 配置项即可。
配置加密可以参考 spring-boot-encrypt demo项目
因此,我们暂时只能在客户端进行配置的加解密。这里,我们继续采用在《芋道 Spring Boot 配置文件入门》的「8. 配置加密」小节中使用的 Jasypt。
下面,我们来使用 Apollo + Jasypt 搭建一个配置加密的示例。
5.1 引入依赖
在 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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-45-apollo-demo-jasypt</artifactId>
<dependencies>
<!-- 实现对 SpringMVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Apollo 客户端,内置对 Apollo 的自动化配置 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.5.1</version>
</dependency>
<!-- 实现对 Jasypt 实现自动化配置 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<!-- 方便等会写单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 引入 jasypt-spring-boot-starter 依赖,实现对 Jasypt 的自动化配置。
5.2 创建 Apollo 配置
在 Apollo 中创建 Apollo 配置,内容如下图所示:
这里为了测试简便,我们直接添加加密秘钥 jasypt.encryptor.password 配置项在该 Apollo 配置中。如果为了安全性更高,实际建议把加密秘钥和配置隔离。不然,如果配置泄露,岂不是可以拿着加密秘钥,直接进行解密。
5.3 配置文件
在 application.yml 中,添加 Apollo 配置,如下:
server:
port: 7070 # 避免和本地的 Apollo Portal 端口冲突
app:
id: demo-application-jasypt # 使用的 Apollo 的项目(应用)编号
apollo:
meta: http://127.0.0.1:8080 # Apollo Meta Server 地址
bootstrap:
enabled: true # 是否开启 Apollo 配置预加载功能。默认为 false。
eagerLoad:
enable: true # 是否开启 Apollo 支持日志级别的加载时机。默认为 false。
namespaces: application # 使用的 Apollo 的命名空间,默认为 application。
- 和「2.2 配置文件」一样,就是换了一个 Apollo 项目为 demo-application-jasypt。
5.4 Application
创建 Application.java 类,配置 @SpringBootApplication 注解即可。代码如下:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.5 简单测试
下面,我们进行下简单测试。
- 首先,我们会使用 Jasypt 将 demo-application 进行加密,获得加密结果。
- 然后,将加密结果,赋值到 Apollo 的 spring.application.name 配置项中。
- 最后,我们会使用 Jasypt 将 spring.application.name 配置项解密。
创建 JasyptTest 测试类,编写测试代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class JasyptTest {
@Autowired
private StringEncryptor encryptor;
@Test
public void encode() {
String applicationName = "demo-application";
System.out.println(encryptor.encrypt(applicationName));
}
@Value("${spring.application.name}")
private String applicationName;
@Test
public void print() {
System.out.println(applicationName);
}
}
- 首先,执行 #encode() 方法,手动使用 Jasypt 将 demo-application 进行加密,获得加密结果。加密结果如下:
/kuMQ839qSDG18veiA0zRldpv3ugWzuIE+34COY755wYSXVZ9bVtgw==
- 然后,将加密结果,赋值到 Apollo 的 spring.application.name 配置项中。如下图所示:
- 最后,执行 #print() 方法,自动使用 Jasypt 将 spring.application.name 配置项解密。解密结果如下:
demo-application
- 成功正确解密,符合预期。
5.6 补充说明
目前测试下来,在将 Jasypt 集成进来时,Apollo 的「4. 自动配置刷新」功能,竟然失效了。
- 具体的验证,胖友可以将 jasypt-spring-boot-starter 依赖设置成 <scope>test</scope>,并是使用 DemoController 进行测试。
- 具体的原因,艿艿暂时没去调试与研究,有了解的胖友,麻烦告知下哟。在 issues#2162 中,也有其它胖友提到该问题。
如果说,胖友暂时不需要自动配置刷新功能的话,可以考虑选择使用 Jasypt 集成。如果需要的话,那么就等待官方支持吧,暂时不要考虑使用 Jasypt 咧。
6. 配置加载顺序
示例代码对应仓库:lab-45-apollo-demo-multi。
在《芋道 Spring Boot 配置文件入门》的「9. 配置加载顺序」小节,我们了解了 Spring Boot 自带的配置加载顺序。本小节,我们来看看来自 Apollo 的配置,在其中的顺序。同时,我们将配置多个 Apollo Namespace 命名空间,看看它们互相之间的加载顺序。
下面,我们来搭建一个用于测试配置加载顺序的示例。
6.1 创建 Apollo 配置
在 Apollo 中创建 Apollo 配置,内容如下图所示:
6.2 引入依赖
在 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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-45-apollo-demo-multi</artifactId>
<dependencies>
<!-- 实现对 SpringMVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Apollo 客户端,内置对 Apollo 的自动化配置 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
</project>
- 和「2.1 引入依赖」是一致的。
6.3 配置文件
在 application.yml 中,添加 Apollo 配置,如下:
server:
port: 7070 # 避免和本地的 Apollo Portal 端口冲突
app:
id: demo-application-multi # 使用的 Apollo 的项目(应用)编号
apollo:
meta: http://127.0.0.1:8080 # Apollo Meta Server 地址
bootstrap:
enabled: true # 是否开启 Apollo 配置预加载功能。默认为 false。
eagerLoad:
enable: true # 是否开启 Apollo 支持日志级别的加载时机。默认为 false。
namespaces: application, db # 使用的 Apollo 的命名空间,默认为 application。
- 注意,我们在 apollo.bootstrap.namespaces 配置项中,设置了「6.1 创建 Apollo 配置」的两个 Namespace 命名空间。
6.4 Application
创建 Application.java 类,配置 @SpringBootApplication 注解即可。代码如下:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 启动 Spring Boot 应用
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 查看 Environment
Environment environment = context.getEnvironment();
System.out.println(environment);
}
}
在代码中,我们去获取了 Spring Environment 对象,因为我们要从其中获取到 PropertySource 配置来源。DEBUG 运行 Application,并记得在 System.out.println(environment); 代码块打一个断点,可以看到如下图的调试信息:
- 对于 apollo.bootstrap 对应一个 CompositePropertySource 对象,即使有对应多个 Apollo Namespace。并且,多个 Namespace 是按照在 apollo.bootstrap.namespaces 配置顺序。
- 所有 Apollo 对应的 PropertySource 对象,优先级非常高,目前看下来仅仅低于 server.ports 对应的 MapPropertySource。基本上,我们可以认为是最高优先级了。
Apollo 官方在如下两个仓库,提供了很多示例,胖友可以自己去瞅瞅:
- https://github.com/ctripcorp/apollo-use-cases
- https://github.com/ctripcorp/apollo/tree/master/apollo-demo