forked from mercyblitz/java-training-camp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
43c6028
commit a6a1c6c
Showing
1 changed file
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,304 @@ | ||
> <a name="oAod9"></a> | ||
#### 主要内容 | ||
> - 配置操作:理解配置客户端通过 Open API 操作配置的设计 | ||
> - 生态整合:将配置客户端 API 整合业界成熟的框架,如 Spring 配置、MicroProfile 配置等 | ||
> - 配置验证:实现客户端与服务端的配置版本控制,提供合法性校验等手段,确保客户端配置是合法有效的 | ||
<a name="JQpiM"></a> | ||
# 配置操作 | ||
<a name="PVhPN"></a> | ||
## Open API | ||
基于 REST 来开发,构建在 HTTP 协议上(版本化)<br />gRPC 的流行,不排除未来基于 HTTP 2.0 协议来开发<br />Dubbo 整合 gRPC | ||
|
||
<a name="yjC7R"></a> | ||
## 参考实现 | ||
<a name="NqWxF"></a> | ||
### Alibaba Nacos | ||
[https://nacos.io/zh-cn/docs/open-api.html](https://nacos.io/zh-cn/docs/open-api.html) | ||
<a name="ZudIc"></a> | ||
### Ctrip Apollo | ||
[https://www.apolloconfig.com/#/zh/usage/apollo-open-api-platform](https://www.apolloconfig.com/#/zh/usage/apollo-open-api-platform) | ||
<a name="tRTvI"></a> | ||
### Hashicorp Consul | ||
[https://developer.hashicorp.com/consul/api-docs/config](https://developer.hashicorp.com/consul/api-docs/config) | ||
|
||
<a name="oi1hY"></a> | ||
### 特性分类 | ||
<a name="JJfnc"></a> | ||
#### 功能特性 | ||
<a name="voYpN"></a> | ||
##### 发布配置 | ||
<a name="KvglV"></a> | ||
##### 修改配置 | ||
<a name="g29Xu"></a> | ||
##### 删除配置 | ||
<a name="TjqUK"></a> | ||
##### 加载配置 | ||
<a name="e7pm2"></a> | ||
##### 列举配置 | ||
<a name="Or6yK"></a> | ||
##### 配置更新通知 | ||
<a name="KchzX"></a> | ||
#### 非功能特性 | ||
<a name="yqQhv"></a> | ||
##### 缓存 - Caching | ||
<a name="Hh7XU"></a> | ||
##### 安全 - ACL | ||
<a name="DXhQ0"></a> | ||
##### 指标 - Metrics | ||
<a name="PHPeD"></a> | ||
##### 日志 - Logging | ||
|
||
<a name="BHKUJ"></a> | ||
# 生态整合 | ||
<a name="OevY5"></a> | ||
## Spring Environment Abstract | ||
<a name="AEKvS"></a> | ||
### Spring Profiles | ||
配置化条件(运行时条件) | ||
<a name="G3jQZ"></a> | ||
### Spring PropertySourcesPropertyResolver | ||
PropertySourcesPropertyResolver = (PropertyResolver + PropertySources)<br />属性配置(Properties)-> 用于 Spring Bean 属性(Properties) | ||
<a name="neXEs"></a> | ||
#### 配置处理 - PropertyResolver | ||
配置处理 = 配置读取 + 展位替换处理(可嵌套) + 类型转换 | ||
<a name="ybApk"></a> | ||
##### 注解注入 - @Value | ||
<a name="BldvO"></a> | ||
##### API 读取 - PropertyResolver#getProperty | ||
<a name="roUCN"></a> | ||
##### XML 占位符 - ${...} | ||
<a name="HjT7Y"></a> | ||
#### 配置来源 - PropertySources | ||
由多个 PropertySource 组成,有序(优先级) | ||
<a name="XIOXu"></a> | ||
##### 实现类 - MutablePropertySources | ||
内部存储一个 PropertySource CopyOnWriteArrayList | ||
|
||
<a name="pMa3l"></a> | ||
#### 类型转换 | ||
<a name="IxjF1"></a> | ||
##### 类型转换服务 - org.springframework.core.convert.ConversionService | ||
<a name="pVOYR"></a> | ||
##### 类型转换器 - org.springframework.core.convert.converter.Converter | ||
|
||
<a name="Q7vNt"></a> | ||
#### 配置变化事件监听(依赖 Spring Cloud Context) | ||
<a name="nn48Y"></a> | ||
##### 属性配置变更事件 - EnvironmentChangeEvent | ||
事件驱动内容需要只读(快照)。<br />不足: | ||
|
||
- 类命名 - EnvironmentChangeEvent -> ConfigurationPropertiesChangeEvent | ||
- 缺少配置值 - | ||
- keys:变更 Property Keys | ||
- (缺少)oldValues:历史配置值 | ||
- (缺少)newValues:新的配置值 | ||
```java | ||
Environment environment = ...; | ||
EnvironmentChangeEvent event = ...; | ||
Set<String> keys = event.getKeys(); | ||
for(String key : keys){ | ||
// 当前配置值可能与 EnvironmentChangeEvent 事件发生时发生改变 | ||
String value = environment.getProperty(key); | ||
} | ||
``` | ||
<a name="V6JIx"></a> | ||
## MicroProfile Config | ||
<a name="H2800"></a> | ||
### 规范 | ||
3.0 - [https://download.eclipse.org/microprofile/microprofile-config-3.0/microprofile-config-spec-3.0.html](https://download.eclipse.org/microprofile/microprofile-config-3.0/microprofile-config-spec-3.0.html)<br />参考实现:[https://github.com/mercyblitz/geekbang-lessons/tree/master/projects/stage-1/middleware-frameworks/my-configuration](https://github.com/mercyblitz/geekbang-lessons/tree/master/projects/stage-1/middleware-frameworks/my-configuration) | ||
<a name="sP3nH"></a> | ||
### 配置 Profiles | ||
<a name="qRUtA"></a> | ||
### 属性配置 | ||
<a name="PN6R2"></a> | ||
#### 配置提供者 - org.eclipse.microprofile.config.spi.ConfigSourceProvider | ||
一个或多个 ConfigSource | ||
<a name="MYW9E"></a> | ||
#### 单个配置源 - org.eclipse.microprofile.config.spi.ConfigSource | ||
<a name="rRFcf"></a> | ||
#### 配置处理 | ||
<a name="Cmh9x"></a> | ||
##### API 配置获取 - org.eclipse.microprofile.config.spi.ConfigSource#getValue | ||
<a name="EI9Iz"></a> | ||
##### 注解配置获取 - @Inject + @ConfigProperty | ||
<a name="KIQ0Z"></a> | ||
#### 类型转换 - org.eclipse.microprofile.config.spi.Converter | ||
|
||
<a name="XzCtp"></a> | ||
## Alibaba Naocs | ||
<a name="PDXlX"></a> | ||
### Nacos Client | ||
Nacos Client 对 Nacos Open API 支持<br />Nacos Client 本地缓存<br />Nacos Client 支持配置变更 | ||
<a name="spAzl"></a> | ||
### Nacos Spring | ||
<a name="oWVhs"></a> | ||
#### Nacos Spring 的 @Value 注解行为 | ||
@Value 行为与 Spring Framework 保持一致 | ||
```java | ||
@Value("${my.name}" | ||
private String name; | ||
|
||
// 当 my.name 初始配置为 "abc" ,name 属性内容为 "abc" | ||
// 当 my.name 配置变更为 "def" ,name 属性内容仍为 "abc" | ||
``` | ||
如果需要支持配置变更的话,可使用 @NacosValue,并且可控制是否需要动态变更。 | ||
```java | ||
@NacosValue(value = "${useLocalCache:false}", autoRefreshed = true) | ||
private boolean useLocalCache; | ||
``` | ||
假设 @NacosValue 设置 autoRefreshed = false 时,它与 Spring @Value 行为是相同的。但是 @NacosValule 注解来自于 Nacos Client,它有可能在 Java EE 环境被实现。 | ||
> 注:配置注解不要绑定在某个具体实现上,比如 Spring Framework。 | ||
> 假设 @AbcValue 仅绑定在 Spring Framework 上,它可以如下实现: | ||
|
||
```java | ||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Documented | ||
@Value | ||
public @interface AbcValue { | ||
|
||
/** | ||
* The actual value expression: e.g. "#{systemProperties.myProp}". | ||
* | ||
* @return value expression | ||
*/ | ||
@AliasFor(value = "value" , annotation = Value.class) | ||
String value(); | ||
|
||
// 其他扩展属性 | ||
} | ||
``` | ||
<a name="Y6MNP"></a> | ||
## Ctrip Apollo | ||
<a name="NKT65"></a> | ||
### Apollo Client | ||
Apollo Client 对 Apollo Open API 支持<br />Apollo Client 本地缓存<br />Apollo Client 支持配置变更 | ||
|
||
<a name="TYMQs"></a> | ||
#### 模块依赖 | ||
|
||
- apollo-client | ||
- apollo-core | ||
|
||
<a name="MMZsM"></a> | ||
#### 版本分布 | ||
<a name="VKEaw"></a> | ||
##### apollo-client | ||
通常 Java 应用 接入的 Apollo 客户端 | ||
<a name="a6rX3"></a> | ||
##### apollo-client-config-data | ||
Apollo 客户端 Spring Boot 2.4 的支持 | ||
|
||
<a name="VAGmg"></a> | ||
#### 监听配置变化事件 | ||
<a name="cIXbu"></a> | ||
##### 事件 - ConfigChangeEvent | ||
<a name="oryRx"></a> | ||
##### Spring 事件 - ApolloConfigChangeEvent | ||
基于 Spring 事件 - ApplicationEvent 扩展,不过可以使用 PayloadApplicationEvent 替代。<br />ApolloConfigChangeEvent 包装了 ConfigChangeEvent 对象: | ||
```java | ||
public class ApolloConfigChangeEvent extends ApplicationEvent { | ||
|
||
public ApolloConfigChangeEvent(ConfigChangeEvent source) { | ||
super(source); | ||
} | ||
|
||
public ConfigChangeEvent getConfigChangeEvent() { | ||
return (ConfigChangeEvent) getSource(); | ||
} | ||
} | ||
``` | ||
当 Spring 事件监听器时,可以如下: | ||
```java | ||
@EventListener(ApolloConfigChangeEvent.class) | ||
public void onApolloConfigChangeEvent(ApolloConfigChangeEvent event){ | ||
ConfigChangeEvent configChangeEvent = event.getConfigChangeEvent(); | ||
} | ||
``` | ||
实际上,可以简化为如下: | ||
```java | ||
@EventListener(ConfigChangeEvent.class) | ||
public void onApolloConfigChangeEvent(ConfigChangeEvent event){ | ||
// 前提是 ConfigChangeEvent 作为 Spring PayloadApplicationEvent 对象传输: | ||
// ApplicationEventPublisher#publish(ConfigChangeEvent) | ||
} | ||
``` | ||
|
||
<a name="oBF5X"></a> | ||
##### 监听器 - ConfigChangeListener | ||
<a name="DSvMv"></a> | ||
##### 异步事件监听 | ||
基于线程池 | ||
```java | ||
static { | ||
m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory | ||
.create("Config", true)); | ||
} | ||
``` | ||
|
||
- 优点 | ||
- 异步 | ||
- 性能相对高 | ||
- ConfigChangeListener 不会相互影响 | ||
- 不足 | ||
- 消费线程使用随着 Listener 增多而增多 | ||
- 不支持自定义的超时,默认使用 Executors.newCachedThreadPool 的 Keep-Live 60 秒 | ||
|
||
<a name="f9WjK"></a> | ||
#### 配置优化 | ||
<a name="Bevv7"></a> | ||
##### APP ID 配置 | ||
```properties | ||
app.id = ${spring.application.name} # 最低优先级 | ||
``` | ||
<a name="mpfMW"></a> | ||
##### SpringValue 优化 | ||
|
||
1. SpringValue 对象是否需要使用 WeakReference<Object> 作为 Bean 引用? | ||
```java | ||
public class User { | ||
|
||
@Value("${my.name}" | ||
private String user; | ||
|
||
} | ||
``` | ||
假设 User Bean Scope 为 "singleton" 的话,那么该 Bean 会被 Spring IoC 容器托管,并且管理其生命周期,也就是在 Spring IoC 容器生命周期类,它是一个强引用,所以 WeakReference<Object> 引用该 Bean 是没有必要的。<br />假设 User Bean Scope 为 prototype 的话,那么该 Bean 初始化后,就不被 Spring IoC 容器托管。该被存在两种情况: | ||
|
||
- 一次性使用,类似于 Request 对象,SpringValue 对象也会一次性创建,WeakReference<Object> 作为 SpringValue 对象成员,它不会长期使用 Spring Bean,所以没有必要配置更改。 | ||
- 非一次性使用,该 Bean 对象将被另外一个容器对象持久 | ||
- 如果容器对象也是一次性的话,该 Bean 也会一次性的。 | ||
- 如果容器对象不是一次性的话,该 Bean 也会被尝试驻留 JVM Heap 中 | ||
|
||
综上所述,WeakReference<Object> 似乎没有必要设计。 | ||
|
||
2. SpringValue 对 @Value 支持优先 | ||
- 支持 | ||
- 字段注入 - Field | ||
- 属性注入 - Setter 方法 | ||
- @Value 还有方法注入 | ||
```java | ||
public void init(@Value("${my.name}" name, @Value("${my.age}" age) { | ||
|
||
} | ||
``` | ||
可参考 @Value 实现类 - AutowiredAnnotationBeanPostProcessor | ||
<a name="xj7ZI"></a> | ||
### Apollo Spring | ||
<a name="uuK6f"></a> | ||
#### Apollo Spring 的 @Value 注解行为 | ||
@Value 支持配置动态变更,调整 Spring @Value 注解行为: | ||
```java | ||
@Value("${my.name}" | ||
private String name; | ||
|
||
// 当 my.name 初始配置为 "abc" ,name 属性内容为 "abc" | ||
// 当 my.name 配置变更为 "def" ,name 属性内容变为 "def" | ||
``` | ||
|
||
<a name="wExgS"></a> | ||
# 作业:配置验证 | ||
[https://github.com/mercyblitz/java-training-camp/issues/16](https://github.com/mercyblitz/java-training-camp/issues/16)<br /> | ||
|
||
|