基于SpringBoot的Tcc Java Client 增强组件:Tcc-plus的设计与实现
背景
TCC 提供了非常灵活的配置管理,并具备丰富的功能,包括版本管理、权限管理、多环境支持、灰度发布等。
开发此工具的动机
以下几个场景促使我们开发了这个组件:
- 原生侵入性:对代码有一定侵入,需要使用
@DynamicConfig
注解,与业务代码耦合。 - 远程配置限制:对于大量基于 Spring Boot 的组件,无法直接使用 TCC 的远程配置能力。例如,需要修改请求控制参数、数据库连接参数等基础组件配置,或业务自身的其他 Spring Boot 配置变更场景。这些配置未在 TCC 上维护,需要修改
application.yml
等配置文件或启动参数/环境变量,前者发布流程较长,后者不便于集中维护且无法动态生效。 - 安全性考虑:基于安全原因,一些组件的秘钥配置不建议放在工程文件中。可以利用 TCC 权限管理提高安全性,但同样不能直接配置在 TCC,需要引入额外代码处理。
- 日志级别调整:调整 logger 日志级别的场景,同样不能直接使用 TCC 动态灵活变更配置。
基于以上原因,我们开发了 Tcc-plus,与原生 Spring Boot 结合更紧密。
tcc-plus简介
Tcc-plus 基于 Spring Boot 和 Spring Cloud Context,在启动阶段增强 TCC,兼容原生 TCC 功能,并具备以下特性:
- 配置源集成:基于 TCC 的配置源作为 Spring
PropertySource
,启动时绑定 Spring Bean。 - 配置变更监听:监听 TCC 配置变更,实现 Bean 重绑定。
主要特点
- 无侵入性增强:仅对 TCC SDK 进行增强,不改变其原生功能,二者共存。
- 业务代码简洁:业务使用 Spring Boot 原生方式即可使用 TCC 远程数据源,无需额外侵入。
- 多格式支持:支持
properties
、yaml
、json
格式的配置解析。 - 动态绑定:支持
@ConfigurationProperties
、@Value
、Logger 日志级别等配置的动态绑定。 - 版本兼容:兼容不同版本的 Spring Boot 环境。
接入指导
设计与实现
核心流程
下图展示了原生 Spring、TCC 和 Tcc-plus 组件在 Spring Boot 启动过程中的扩展原理,不同颜色分别表示不同组件。
核心步骤
-
Spring Cloud 上下文启动阶段:
- 读取 Tcc-plus 配置,加载自定义
Locator
。
- 读取 Tcc-plus 配置,加载自定义
-
Spring Boot 上下文准备阶段:
- 自定义
Locator
创建远程属性源TccPropertySource
,通过原生TccConfigClient
获取配置中心数据加载到Environment
。
- 自定义
-
Spring Boot 上下文刷新阶段:
TccWatchAutoConfiguration
绑定监听处理器到TccConfigClient
。
-
TCC 配置变更时:
- 利用
ApplicationEventPublisher
发布EnvironmentChangeEvent
,触发重新绑定。
- 利用
详细说明
1. prepareEnvironment:创建 Spring Cloud 上下文阶段
BootstrapApplicationListener
监听ApplicationEnvironmentPreparedEvent
,创建 Spring Cloud 上下文。- 读取 Spring Cloud 上下文引导配置,默认为
bootstrap.yml/properties
。 - 创建
TccPlusProperties
:保存从bootstrap.yml
读取的 Tcc-plus 配置。 - 创建
TccPropertySourceLocator
:Spring Cloud 的PropertySourceLocator
实现类,其locate
方法在准备 Spring Boot 上下文阶段触发,并载入到Environment
,用于加载远程配置数据。
2. prepareContext:准备 Spring Boot 上下文阶段
- Spring Cloud 的
PropertySourceBootstrapConfiguration
实现了ApplicationContextInitializer
,在此阶段触发其initialize
方法,回调所有Locator
的locate
方法。 - 自定义的
TccPropertySourceLocator
创建TccPropertySource
,通过原生TccConfigClient
拉取 TCC 配置中心数据。 - 拉取的配置数据为
String
类型,根据 Tcc-plus 配置的 key 后缀使用yamlLoader
或propertiesLoader
解析,解析后的配置载入到Environment
的PropertySources
,并排序高于其他 Spring Boot 的PropertySource
,确保在 Spring IoC 创建 Bean 时,远程配置的优先级高于本地配置和启动参数等。
3. refreshContext:刷新 Spring Boot 上下文阶段
TccWatchAutoConfiguration
作为 Spring Boot 上下文,注入ApplicationEventPublisher
和RefreshScope
,绑定handleChangeEvent
到TccConfigClient
的配置变更处理器。- 容器创建其他 Bean(包括
ConfigurationProperties
、@Value
、LoggingSystem
等)时,将优先使用TccPropertySource
加载的配置参数。
4. handleChangeEvent:TCC 配置变更
-
TccConfigClient
默认每 10 秒读取 TCC 配置中心最新数据,检测到变更时通过异步线程触发 Listener 的onLoad
方法。 -
在
TccWatchAutoConfiguration
中,将handleChangeEvent
绑定到onLoad
方法。变更处理步骤
- 从
TccConfigClient
读取最新配置,解析为Properties
。 - 对比新旧值差异。
- 封装
RichEnvironmentChangeEvent
,发布事件;ConfigurationPropertiesRebinder
和LoggingRebinder
将监听该事件,分别触发@ConfigurationProperties
的 Bean 重绑定和LoggingSystem
的刷新。 - 刷新
RefreshScope
,标注了@RefreshScope
的 Bean 将被清空缓存,重新getBean
将根据最新Environment
创建 Bean。
- 从
ModuleConfigDuplicateChecker
Tcc-plus 禁止多个远程配置存在相同的 key(此 key 为 Spring 配置项的 key,非 TCC 的 key)。在启动阶段和配置变更阶段分别进行校验拦截。
知识点补充
整体实现方案需要了解一定的 Spring 原理和 TCC 本身的实现原理,以下是补充说明:
1. Spring 相关知识
Spring Boot 启动流程
Spring Boot 的启动过程复杂,提供了丰富的扩展点,允许用户在各个阶段运行自定义组件。以下是几个关键步骤:
-
创建 SpringApplication
- 从
spring.factories
提取ApplicationListener
,若项目依赖了spring-cloud-context
,BootstrapApplicationListener
将被注册到SpringApplication
。
- 从
-
prepareEnvironment
- 当
Environment
创建完成,发布ApplicationEnvironmentPreparedEvent
事件,BootstrapApplicationListener
监听该事件,创建一个 Spring Cloud 上下文的SpringApplication
并运行。 - 这里将加载
bootstrap.yml
以及spring.factories
中的BootstrapConfiguration
等,用户可以自定义 Spring Cloud 上下文的配置类。
- 当
-
prepareContext
- 准备 Spring Boot 上下文,将触发所有
ApplicationContextInitializer
来初始化上下文。 - 其中的
PropertySourceBootstrapConfiguration
实现initialize
方法,遍历所有的属性源加载器PropertySourceLocators
的locate
方法,用于读取外部化配置,解析成PropertySource
并组装到上下文的Environment
。 - 用户可以自定义
PropertySourceLocator
来加载自定义属性。
注意:
Environment
中的PropertySources
是一个列表,保存了环境变量、命令行参数、application.yml
、bootstrap.yml
的属性值配置;排序越靠前,最终初始化 Bean 时使用的优先级越高。自定义PropertySourceLocator
加载的PropertySource
将被addFirst
,即优先级高于其他配置。 - 准备 Spring Boot 上下文,将触发所有
-
refreshContext
- 刷新
SpringApplicationContext
,完成 IOC 容器的 Bean 创建等 Spring Framework 工作。 - 可以注册
ApplicationPreparedEvent
监听器,当应用准备完成时处理一些基于 Spring Boot 上下文的工作。
- 刷新
Spring Boot 的属性值绑定
-
@EnableConfigurationProperties
注解引入了EnableConfigurationPropertiesImportSelector
后置处理器。 -
EnableConfigurationPropertiesImportSelector
后置处理器向 Spring 容器中注册了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
两个 Bean。 -
ConfigurationPropertiesBeanRegistrar
向 Spring 容器中注册了XxxProperties
类型的 Bean。 -
ConfigurationPropertiesBindingPostProcessorRegistrar
向 Spring 容器中注册了ConfigurationBeanFactoryMetadata
和ConfigurationPropertiesBindingPostProcessor
两个后置处理器。ConfigurationBeanFactoryMetadata
后置处理器在初始化 Bean Factory 时将@Bean
注解的元数据存储起来,以便在后续的外部配置属性绑定中使用。ConfigurationPropertiesBindingPostProcessor
后置处理器将外部配置属性值绑定到XxxProperties
类属性的逻辑委托给ConfigurationPropertiesBinder
对象,后者再将属性绑定的逻辑委托给Binder
对象完成。
参考:掘金文章
2. 如何实现重绑定
- ConfigurationPropertiesRebinder:Spring Cloud 的组件,监听
EnvironmentChangeEvent
,对@ConfigurationProperties
的 Bean 进行重绑定。
3. 如何动态刷新日志级别
- LoggingRebinder:Spring Cloud 的组件,监听
EnvironmentChangeEvent
,动态刷新 Logger 级别。
TCC 工作原理
Java SDK 源码:TCC Client V2
TccAutoConfiguration
spring.factories
指定的配置类 TccAutoConfiguration
在容器早期,将 DynamicConfigAnnotationProcessor
注册到上下文。
java
Copy
@Configuration
@Import(TccAutoConfiguration.Register.class)
public class TccAutoConfiguration {
public static class Register implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(DynamicConfigAnnotationProcessor.BEAN_NAME)) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(DynamicConfigAnnotationProcessor.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
definition.setSynthetic(true);
registry.registerBeanDefinition(DynamicConfigAnnotationProcessor.BEAN_NAME, definition);
}
}
}
}
DynamicConfigAnnotationProcessor
DynamicConfigAnnotationProcessor
实现了 BeanPostProcessor
和 BeanFactoryPostProcessor
接口。
- BeanFactoryPostProcessor:作为 Bean 工厂的后置处理器,在 Bean Factory 创建完成后执行一些自定义操作,此处通过 Bean Factory 包装了自定义的
StringValueResolver
。 - BeanPostProcessor:实现
postProcessBeforeInitialization
方法,在 Bean 初始化之前,读取 TCC 配置,针对用户自定义 Bean 中@DynamicConfig
注解的属性进行设置,并注册配置变更监听方法。
TccConfigClient
- 初始化全局静态成员
AbstractDataLoader
中的 TCC 元数据。 - 其线程池定时每 10 秒拉取 TCC 配置,检测到变化时通过异步线程触发 Listener 的
onLoad
方法。
写在最后
有问题或想法欢迎交流~
欢迎共建,代码仓库:Tcc-plus 仓库
参考文献
许可证
本项目采用 MIT 许可证 进行许可。
贡献
欢迎贡献代码!请参阅 贡献指南 了解更多详情。
联系我们
若有任何问题或建议,请通过以下方式联系我们:
- 邮箱:[email protected]
- GitHub:Tcc-plus Issues
致谢
感谢所有贡献者和使用者,你们的支持是我们前进的动力!
版本历史
- v1.0.0 - 初始发布
结束语
感谢您阅读本文档,希望 Tcc-plus 能为您的项目带来帮助!
代码示例
以下是一个简单的 Spring Boot 应用集成 Tcc-plus 的示例:
java
Copy
@SpringBootApplication
@EnableConfigurationProperties(MyProperties.class)
public class TccPlusApplication {
public static void main(String[] args) {
SpringApplication.run(TccPlusApplication.class, args);
}
}
@ConfigurationProperties(prefix = "my.config")
public class MyProperties {
private String name;
private int timeout;
// Getters and Setters
}
在 application.yml
中:
yaml
Copy
my:
config:
name: "defaultName"
timeout: 30
当 Tcc-plus 监测到配置变更时,MyProperties
将自动重绑定为最新配置。
常见问题
问:Tcc-plus 如何保证配置安全性?
答:Tcc-plus 利用 TCC 的权限管理功能,对敏感配置进行权限控制,确保只有授权的应用和用户可以访问和修改配置。
问:Tcc-plus 支持哪些格式的配置文件?
答:Tcc-plus 支持 properties
、yaml
、json
格式的配置解析。
贡献指南
欢迎各位开发者为 Tcc-plus 做出贡献!请按照以下步骤进行:
- Fork 本仓库。
- 创建您的特性分支:
git checkout -b feature/YourFeature
- 提交您的更改:
git commit -m "Add some feature"
- 推送到分支:
git push origin feature/YourFeature
- 创建一个 Pull Request
详细信息请参阅 贡献指南。
许可证
本项目采用 MIT 许可证,详见 LICENSE。
联系方式
如有任何问题或建议,欢迎通过以下方式与我们联系:
- 邮箱:[email protected]
- GitHub:Tcc-plus Issues
致谢
感谢所有贡献者的辛勤工作和支持!
结束语
感谢您的关注和支持,期待 Tcc-plus 在实际项目中为您带来便利!
免责声明
本项目仅供学习和研究使用,使用前请确保理解其功能和限制。
相关资源
Example Usage
Here’s an example of how to use Tcc-plus in a Spring Boot application:
java
Copy
@RestController
public class ConfigController {
@Autowired
private MyProperties myProperties;
@GetMapping("/config")
public MyProperties getConfig() {
return myProperties;
}
}
When the configuration changes in TCC, MyProperties
will automatically update without requiring a restart.
Future Work
- 扩展支持:支持更多类型的配置绑定。
- 性能优化:优化配置变更的检测和应用速度。
- 文档完善:提供更详细的教程和示例。
Feedback
欢迎通过 GitHub Issues 提交您的反馈和建议。
最后声明
本文档为项目的非正式文档,可能随时更新。请以实际项目中的文档为准。
版本日志
[v1.0.0] - 2023-10-01
初始发布。