This is an automated email from the ASF dual-hosted git repository.
dengliming pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shenyu-website.git
The following commit(s) were added to refs/heads/main by this push:
new 3a5780e909f feat: Upgrade Http-register-analysis to v2.5.0 (#827)
3a5780e909f is described below
commit 3a5780e909fdfff6dfa7efde79b64281db73087e
Author: RayayChung <[email protected]>
AuthorDate: Tue Nov 22 12:43:56 2022 +0800
feat: Upgrade Http-register-analysis to v2.5.0 (#827)
* feat: Upgrade Chinese doc to v2.5.0
* feat: Upgrade both Chinese and English blog to v2.5.0
* fix: markdown issue
Co-authored-by: ray <[email protected]>
---
...sterCenter-SourceCode-Analysis-Http-Register.md | 802 +++++++++++---------
...sterCenter-SourceCode-Analysis-Http-Register.md | 844 +++++++++++----------
.../client-metadata-uri-register-en.png | Bin 0 -> 75978 bytes
.../client-metadata-uri-register-zh.png | Bin 0 -> 117020 bytes
.../client-register-init-en.png | Bin 70652 -> 67386 bytes
.../client-register-init-zh.png | Bin 94416 -> 120623 bytes
.../client-register-service.png | Bin 33938 -> 73278 bytes
.../shenyu-client-event-listener.png | Bin 0 -> 45342 bytes
.../shenyu-client-register-repository.png | Bin 15028 -> 41360 bytes
9 files changed, 898 insertions(+), 748 deletions(-)
diff --git a/blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
b/blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
index 6d6f450ebc6..1759678b960 100644
--- a/blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
+++ b/blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
@@ -6,12 +6,12 @@ author_url: https://github.com/midnight2104
tags: [http,register center,Apache ShenYu]
---
-> [Apache ShenYu](https://shenyu.apache.org/zh/docs/index) is an asynchronous,
high-performance, cross-language, responsive API gateway.
+> [Apache ShenYu](https://shenyu.apache.org/docs/index) is an asynchronous,
high-performance, cross-language, responsive API gateway.
In `ShenYu` gateway, the registration center is used to register the client
information to `shenyu-admin`, `admin` then synchronizes this information to
the gateway through data synchronization, and the gateway completes traffic
filtering through these data. The client information mainly includes `interface
information` and `URI information`.
-> This article is based on `shenyu-2.4.1` version for source code analysis,
please refer to [Client Access
Principles](https://shenyu.apache.org/zh/docs/design/register-center-design)
for the introduction of the official website.
+> This article is based on `shenyu-2.5.0` version for source code analysis,
please refer to [Client Access
Principles](https://shenyu.apache.org/docs/design/register-center-design) for
the introduction of the official website.
### 1. Registration Center Principle
@@ -20,7 +20,7 @@ When the client starts, it reads the interface information
and `uri information`

-The registration center in the figure requires the user to specify which
registration type to use. `ShenYu` currently supports `Http`, `Zookeeper`,
`Etcd`, `Consul` and `Nacos` for registration. Please refer to [Client Access
Configuration](https://shenyu.apache.org/zh/docs/user-guide/register-center-access)
for details on how to configure them.
+The registration center in the figure requires the user to specify which
registration type to use. `ShenYu` currently supports `Http`, `Zookeeper`,
`Etcd`, `Consul` and `Nacos` for registration. Please refer to [Client Access
Configuration](https://shenyu.apache.org/docs/user-guide/property-config/register-center-access)
for details on how to configure them.
`ShenYu` introduces `Disruptor` in the principle design of the registration
center, in which the `Disruptor` queue plays a role in decoupling data and
operations, which is conducive to expansion. If too many registration requests
lead to registration exceptions, it also has a data buffering role.
@@ -48,7 +48,7 @@ On the client side, after the data is out of the queue, the
data is transferred
### 2. Client Registration Process
-When the client starts, it reads the attribute information according to the
relevant configuration, and then writes it to the queue. Let's take the
official
[shenyu-examples-http](https://github.com/apache/incubator-shenyu/tree/master/shenyu-examples/shenyu-examples-http)
as an example and start the source code analysis . The official example is a
microservice built by `springboot`. For the configuration of the registration
center, please refer to the official website [client access conf [...]
+When the client starts, it reads the attribute information according to the
relevant configuration, and then writes it to the queue. Let's take the
official
[shenyu-examples-http](https://github.com/apache/incubator-shenyu/tree/master/shenyu-examples/shenyu-examples-http)
as an example and start the source code analysis . The official example is a
microservice built by `springboot`. For the configuration of the registration
center, please refer to the official website [client access conf [...]
#### 2.1 Load configuration, read properties
@@ -58,11 +58,14 @@ Let's start with a diagram that ties together the
initialization process of the
We are analyzing registration by means of `http`, so the following
configuration is required.
-```xml
+```yaml
shenyu:
register:
registerType: http
serverLists: http://localhost:9095
+ props:
+ username: admin
+ password: 123456
client:
http:
props:
@@ -76,6 +79,8 @@ Each attribute indicates the following meaning.
- `registerType`: the service registration type, fill in `http`.
- `serverList`: The address of the `Shenyu-Admin` project to fill in for the
`http` registration type, note the addition of `http://` and separate multiple
addresses with English commas.
+- `username`: The username of the `Shenyu-Admin`
+- `password`: The password of the `Shenyu-Admin`
- `port`: the start port of your project, currently `springmvc/tars/grpc`
needs to be filled in.
- `contextPath`: the routing prefix for your `mvc` project in `shenyu`
gateway, such as `/order`, `/product`, etc. The gateway will route according to
your prefix.
- `appName`: the name of your application, if not configured, it will take the
value of `spring.application.name` by default.
@@ -83,7 +88,7 @@ Each attribute indicates the following meaning.
After the project starts, it will first load the configuration file, read the
property information and generate the corresponding `Bean`.
-The first configuration file read is `ShenyuSpringMvcClientConfiguration`,
which is the `http` registration configuration class for the `shenyu` client,
indicated by `@Configuration` which is a configuration class, and by
`@ImportAutoConfiguration` which is a configuration class. to introduce other
configuration classes. Create `SpringMvcClientBeanPostProcessor`, which mainly
handles metadata. Create `ContextRegisterListener`, which mainly handles `URI`
information.
+The first configuration file read is `ShenyuSpringMvcClientConfiguration`,
which is the `http` registration configuration class for the `shenyu` client,
indicated by `@Configuration` which is a configuration class, and by
`@ImportAutoConfiguration` which is a configuration class. to introduce other
configuration classes. Create `SpringMvcClientEventListener`, which mainly
handles metadata and `URI` information.
```java
/**
@@ -91,18 +96,14 @@ The first configuration file read is
`ShenyuSpringMvcClientConfiguration`, which
*/
@Configuration
@ImportAutoConfiguration(ShenyuClientCommonBeanConfiguration.class)
+@ConditionalOnProperty(value = "shenyu.register.enabled", matchIfMissing =
true, havingValue = "true")
public class ShenyuSpringMvcClientConfiguration {
- //create SpringMvcClientBeanPostProcessor to handle metadata
+ // create SpringMvcClientEventListener to handle metadata and URI
@Bean
- public SpringMvcClientBeanPostProcessor
springHttpClientBeanPostProcessor(final ShenyuClientConfig clientConfig,final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
- return new
SpringMvcClientBeanPostProcessor(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()),
shenyuClientRegisterRepository);
- }
-
- //create ContextRegisterListener to handle URI
- @Bean
- public ContextRegisterListener contextRegisterListener(final
ShenyuClientConfig clientConfig) {
- return new
ContextRegisterListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()));
+ public SpringMvcClientEventListener springHttpClientEventListener(final
ShenyuClientConfig clientConfig,
+ final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
+ return new
SpringMvcClientEventListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()),
shenyuClientRegisterRepository);
}
}
@@ -190,7 +191,7 @@ public final class ShenyuClientRegisterRepositoryFactory {
The load type is specified by `registerType`, which is the type we specify in
the configuration file at
-```xml
+```yaml
shenyu:
register:
registerType: http
@@ -202,209 +203,308 @@ We specified `http`, so it will go to load
`HttpClientRegisterRepository`. After
```java
@Join
public class HttpClientRegisterRepository implements
ShenyuClientRegisterRepository {
-
+
@Override
public void init(final ShenyuRegisterCenterConfig config) {
+ this.username = config.getProps().getProperty(Constants.USER_NAME);
+ this.password = config.getProps().getProperty(Constants.PASS_WORD);
this.serverList =
Lists.newArrayList(Splitter.on(",").split(config.getServerLists()));
+ this.setAccessToken();
}
// ......
}
```
-Read `serverLists` from the configuration file, the address of
`sheenyu-admin`, in preparation for subsequent data sending. The class
annotation `@Join` is used for `SPI` loading.
+Read `username`, `password` and `serverLists` from the configuration file, the
username, password and address of `sheenyu-admin`, in preparation for
subsequent data sending. The class annotation `@Join` is used for `SPI` loading.
> `SPI`, known as `Service Provider Interface`, is a service provider
> discovery feature built into the `JDK`, a mechanism for dynamic replacement
> discovery.
>
> [shenyu-spi](https://github.com/apache/incubator-shenyu/tree/master/shenyu-spi)
> is a custom `SPI` extension implementation for the `Apache ShenYu` gateway,
> designed and implemented with reference to Dubbo [SPI extension
> implementation](https://dubbo.apache.org/zh/docs/v2.7/dev/impls/).
-#### 2.3 SpringMvcClientBeanPostProcessor
-Create `SpringMvcClientBeanPostProcessor`, which is responsible for metadata
construction and registration, with the following constructor logic.
+#### 2.3 SpringMvcClientEventListener
+
+Create `SpringMvcClientEventListener`, which is responsible for the
construction and registration of client-side metadata and `URI` data, and its
creation is done in the configuration file.
+
```java
+@Configuration
+@ImportAutoConfiguration(ShenyuClientCommonBeanConfiguration.class)
+public class ShenyuSpringMvcClientConfiguration {
+ // ......
+
+ // create SpringMvcClientEventListener
+ @Bean
+ public SpringMvcClientEventListener springHttpClientEventListener(final
ShenyuClientConfig clientConfig,
+ final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
+ return new
SpringMvcClientEventListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()),
shenyuClientRegisterRepository);
+ }
+}
+```
-/**
- * spring mvc client BeanPostProcessor
- */
-public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor {
- /**
- * Instantiation by constructor
- */
- public SpringMvcClientBeanPostProcessor(final PropertiesConfig
clientConfig,
- final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
- // read Properties
+`SpringMvcClientEventListener` implements the
`AbstractContextRefreshedEventListener`
+
+
+
+The `AbstractContextRefreshedEventListener` is an abstract class. it
implements the `ApplicationListener` interface and overrides the
`onApplicationEvent()` method, which is executed when a Spring event occurs. It
has several implementation classes, which support different kind of `RPC`
styles.
+
+- `AlibabaDubboServiceBeanListener`:handles `Alibaba Dubbo` protocol.
+- `ApacheDubboServiceBeanListener`:handles `Apache Dubbo` protocol.
+- `GrpcClientEventListener`:handles `grpc` protocol.
+- `MotanServiceEventListener`:handles `Motan` protocol.
+- `SofaServiceEventListener`:handles `Sofa` protocol.
+- `SpringMvcClientEventListener`:handles `http` protocol.
+- `SpringWebSocketClientEventListener`:handles `Websocket` protocol.
+- `TarsServiceBeanEventListener`:handles `Tars` protocol.
+
+```java
+public abstract class AbstractContextRefreshedEventListener<T, A extends
Annotation> implements ApplicationListener<ContextRefreshedEvent> {
+
+ //......
+
+ // Instantiation is done through the constructor
+ public AbstractContextRefreshedEventListener(final PropertiesConfig
clientConfig,
+ final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
+ // read shenyu.client.http properties
Properties props = clientConfig.getProps();
- // get port information and verify
- int port =
Integer.parseInt(props.getProperty(ShenyuClientConstants.PORT));
- if (port <= 0) {
- String errorMsg = "http register param must config the port must >
0";
- LOG.error(errorMsg);
- throw new ShenyuClientIllegalArgumentException(errorMsg);
- }
- // get appName
+ // appName
this.appName = props.getProperty(ShenyuClientConstants.APP_NAME);
- // get contextPath
- this.contextPath =
props.getProperty(ShenyuClientConstants.CONTEXT_PATH);
- // check appName and contextPath
+ // contextPath
+ this.contextPath =
Optional.ofNullable(props.getProperty(ShenyuClientConstants.CONTEXT_PATH)).map(UriUtils::repairData).orElse("");
if (StringUtils.isBlank(appName) && StringUtils.isBlank(contextPath)) {
- String errorMsg = "http register param must config the appName or
contextPath";
+ String errorMsg = "client register param must config the appName
or contextPath";
LOG.error(errorMsg);
throw new ShenyuClientIllegalArgumentException(errorMsg);
}
- // get isFull
- this.isFull =
Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL,
Boolean.FALSE.toString()));
- // start publisher
+ this.ipAndPort = props.getProperty(ShenyuClientConstants.IP_PORT);
+ // host
+ this.host = props.getProperty(ShenyuClientConstants.HOST);
+ // port
+ this.port = props.getProperty(ShenyuClientConstants.PORT);
+ // publish event
publisher.start(shenyuClientRegisterRepository);
}
- // ......
+ // This method is executed when a context refresh
event(ContextRefreshedEvent), occurs
@Override
- public Object postProcessAfterInitialization(@NonNull final Object bean,
@NonNull final String beanName) throws BeansException {
- // ......
+ public void onApplicationEvent(@NonNull final ContextRefreshedEvent event)
{
+ // The contents of the method are guaranteed to be executed only once
+ if (!registered.compareAndSet(false, true)) {
+ return;
+ }
+ final ApplicationContext context = event.getApplicationContext();
+ // get the specific beans
+ Map<String, T> beans = getBeans(context);
+ if (MapUtils.isEmpty(beans)) {
+ return;
+ }
+ // build URI data and register it
+ publisher.publishEvent(buildURIRegisterDTO(context, beans));
+ // build metadata and register it
+ beans.forEach(this::handle);
+ }
+
+ @SuppressWarnings("all")
+ protected abstract URIRegisterDTO buildURIRegisterDTO(ApplicationContext
context,
+ Map<String, T>
beans);
+
+
+ protected void handle(final String beanName, final T bean) {
+ Class<?> clazz = getCorrectedClass(bean);
+ final A beanShenyuClient =
AnnotatedElementUtils.findMergedAnnotation(clazz, getAnnotationType());
+ final String superPath = buildApiSuperPath(clazz, beanShenyuClient);
+ if (Objects.nonNull(beanShenyuClient) && superPath.contains("*")) {
+ handleClass(clazz, bean, beanShenyuClient, superPath);
+ return;
+ }
+ final Method[] methods =
ReflectionUtils.getUniqueDeclaredMethods(clazz);
+ for (Method method : methods) {
+ handleMethod(bean, clazz, beanShenyuClient, method, superPath);
+ }
}
+ // default implementation. build URI data and register it
+ protected void handleClass(final Class<?> clazz,
+ final T bean,
+ @NonNull final A beanShenyuClient,
+ final String superPath) {
+ publisher.publishEvent(buildMetaDataDTO(bean, beanShenyuClient,
pathJoin(contextPath, superPath), clazz, null));
+ }
+
+ // default implementation. build metadata and register it
+ protected void handleMethod(final T bean,
+ final Class<?> clazz,
+ @Nullable final A beanShenyuClient,
+ final Method method,
+ final String superPath) {
+ // get the annotation
+ A methodShenyuClient =
AnnotatedElementUtils.findMergedAnnotation(method, getAnnotationType());
+ if (Objects.nonNull(methodShenyuClient)) {
+ // 构建元数据,发送注册事件
+ publisher.publishEvent(buildMetaDataDTO(bean, methodShenyuClient,
buildApiPath(method, superPath, methodShenyuClient), clazz, method));
+ }
+ }
+
+ protected abstract MetaDataRegisterDTO buildMetaDataDTO(T bean,
+ @NonNull A
shenyuClient,
+ String path,
+ Class<?> clazz,
+ Method method);
}
```
In the constructor, the main purpose is to read the property information and
then perform the checksum.
-```xml
+```yaml
shenyu:
client:
http:
- props:
- contextPath: /http
- appName: http
- port: 8189
- isFull: false
+ props:
+ contextPath: /http
+ appName: http
+ port: 8189
+ isFull: false
```
-Finally, `publisher.start()` is executed to start event publishing and prepare
for registration.
-
-- ShenyuClientRegisterEventPublisher
+Finally, publisher.start() is executed to start event publishing and prepare
for registration.
-`ShenyuClientRegisterEventPublisher` is implemented via singleton pattern,
mainly generating `metadata` and `URI` subscribers (subsequently used for data
publishing), and then starting the `Disruptor` queue. A common method
`publishEvent()` is provided to publish events and send data to the Disruptor
queue.
+ShenyuClientRegisterEventPublisher is implemented via singleton pattern,
mainly generating metadata and URI subscribers (subsequently used for data
publishing), and then starting the Disruptor queue. A common method
publishEvent() is provided to publish events and send data to the Disruptor
queue.
```java
public class ShenyuClientRegisterEventPublisher {
- private static final ShenyuClientRegisterEventPublisher INSTANCE = new
ShenyuClientRegisterEventPublisher();
-
- private DisruptorProviderManage providerManage;
- private RegisterClientExecutorFactory factory;
+ private static final ShenyuClientRegisterEventPublisher INSTANCE = new
ShenyuClientRegisterEventPublisher();
+
+ private DisruptorProviderManage<DataTypeParent> providerManage;
public static ShenyuClientRegisterEventPublisher getInstance() {
return INSTANCE;
}
public void start(final ShenyuClientRegisterRepository
shenyuClientRegisterRepository) {
- factory = new RegisterClientExecutorFactory();
+ RegisterClientExecutorFactory factory = new
RegisterClientExecutorFactory();
factory.addSubscribers(new
ShenyuClientMetadataExecutorSubscriber(shenyuClientRegisterRepository));
factory.addSubscribers(new
ShenyuClientURIExecutorSubscriber(shenyuClientRegisterRepository));
providerManage = new DisruptorProviderManage(factory);
providerManage.startup();
}
- public <T> void publishEvent(final T data) {
- DisruptorProvider<Object> provider = providerManage.getProvider();
- provider.onData(f -> f.setData(data));
+ public <T> void publishEvent(final DataTypeParent data) {
+ DisruptorProvider<DataTypeParent> provider =
providerManage.getProvider();
+ provider.onData(data);
}
}
```
-The logic of the constructor of `SpringMvcClientBeanPostProcessor` is
analyzed, it mainly reads the property configuration, creates metadata and
`URI` subscribers, and starts the `Disruptor` queue. It is important to note
that it implements `BeanPostProcessor`, an interface provided by `Spring` that
executes the `postProcessAfterInitialization()` method of the post-processor
before it actually starts to be used in the `Bean` lifecycle.
+The logic of the constructor of `AbstractContextRefreshedEventListener` is
analyzed, it mainly reads the property configuration, creates metadata and URI
subscribers, and starts the Disruptor queue.
+
+The `onApplicationEvent()` method is executed when a `Spring` event occurs,
the parameter here is `ContextRefreshedEvent`, which means the context refresh
event.
-- postProcessAfterInitialization()
+> `ContextRefreshedEvent` is a `Spring` built-in event. It is fired when the
`ApplicationContext` is initialized or refreshed. This can also happen in the
`ConfigurableApplicationContext` interface using the `refresh()` method.
Initialization here means that all `Bean`s have been successfully loaded,
post-processing `Bean`s have been detected and activated, all `Singleton Bean`s
have been pre-instantiated, and the `ApplicationContext` container is ready to
be used.
+
-`SpringMvcClientBeanPostProcessor` acts as a post-processor which does the
following: reads the metadata in the annotation and registers it with `admin`.
+- `SpringMvcClientEventListener`: the `http` implementation of
`AbstractContextRefreshedEventListener`:
```java
-public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor {
- // ......
+public class SpringMvcClientEventListener extends
AbstractContextRefreshedEventListener<Object, ShenyuSpringMvcClient> {
+
+ private final List<Class<? extends Annotation>> mappingAnnotation = new
ArrayList<>(3);
+
+ private final Boolean isFull;
+
+ private final String protocol;
+
+ // 构造函数
+ public SpringMvcClientEventListener(final PropertiesConfig clientConfig,
+ final ShenyuClientRegisterRepository
shenyuClientRegisterRepository) {
+ super(clientConfig, shenyuClientRegisterRepository);
+ Properties props = clientConfig.getProps();
+ // get isFull
+ this.isFull =
Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL,
Boolean.FALSE.toString()));
+ // http protocol
+ this.protocol = props.getProperty(ShenyuClientConstants.PROTOCOL,
ShenyuClientConstants.HTTP);
+ mappingAnnotation.add(ShenyuSpringMvcClient.class);
+ mappingAnnotation.add(RequestMapping.class);
+ }
- // reads the metadata in the annotation and registers it with admin
@Override
- public Object postProcessAfterInitialization(@NonNull final Object bean,
@NonNull final String beanName) throws BeansException {
+ protected Map<String, Object> getBeans(final ApplicationContext context) {
// Configuration attribute, if isFull=true, means register the whole
microservice
- if (isFull) {
- return bean;
+ if (Boolean.TRUE.equals(isFull)) {
+ getPublisher().publishEvent(MetaDataRegisterDTO.builder()
+ .contextPath(getContextPath())
+ .appName(getAppName())
+ .path(PathUtils.decoratorPathWithSlash(getContextPath()))
+ .rpcType(RpcTypeEnum.HTTP.getName())
+ .enabled(true)
+ .ruleName(getContextPath())
+ .build());
+ return null;
}
- // get Controller annotation
- Controller controller =
AnnotationUtils.findAnnotation(bean.getClass(), Controller.class);
- // get RequestMapping annotation
- RequestMapping requestMapping =
AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
- if (controller != null || requestMapping != null) {
- // get the ShenyuSpringMvcClient annotation for the current bean
- ShenyuSpringMvcClient clazzAnnotation =
AnnotationUtils.findAnnotation(bean.getClass(), ShenyuSpringMvcClient.class);
- String prePath = "";
- // If there is no ShenyuSpringMvcClient annotation, it is
returned, indicating that the interface does not need to be registered
- if (Objects.isNull(clazzAnnotation)) {
- return bean;
- }
- // If the path attribute of the ShenyuSpringMvcClient annotation
includes *, it means that the entire interface is registered
- if (clazzAnnotation.path().indexOf("*") > 1) {
- // build metadata, publish registration event
- publisher.publishEvent(buildMetaDataDTO(clazzAnnotation,
prePath));
- return bean;
- }
-
- prePath = clazzAnnotation.path();
- // get all methods of the current bean
- final Method[] methods =
ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
- for (Method method : methods) {
- // get ShenyuSpringMvcClient annotation
- ShenyuSpringMvcClient shenyuSpringMvcClient =
AnnotationUtils.findAnnotation(method, ShenyuSpringMvcClient.class);
- // If the method has the annotation ShenyuSpringMvcClient, it
means that the method needs to be registered
- if (Objects.nonNull(shenyuSpringMvcClient)) {
- // build metadata, publish registration event
-
publisher.publishEvent(buildMetaDataDTO(shenyuSpringMvcClient, prePath));
- }
- }
+ // get bean with Controller annotation
+ return context.getBeansWithAnnotation(Controller.class);
+ }
+
+ @Override
+ protected URIRegisterDTO buildURIRegisterDTO(final ApplicationContext
context,
+ final Map<String, Object>
beans) {
+ // ...
+ }
+
+ @Override
+ protected String buildApiSuperPath(final Class<?> clazz, @Nullable final
ShenyuSpringMvcClient beanShenyuClient) {
+ if (Objects.nonNull(beanShenyuClient) &&
StringUtils.isNotBlank(beanShenyuClient.path())) {
+ return beanShenyuClient.path();
}
- return bean;
+ RequestMapping requestMapping = AnnotationUtils.findAnnotation(clazz,
RequestMapping.class);
+ // Only the first path is supported temporarily
+ if (Objects.nonNull(requestMapping) &&
ArrayUtils.isNotEmpty(requestMapping.path()) &&
StringUtils.isNotBlank(requestMapping.path()[0])) {
+ return requestMapping.path()[0];
+ }
+ return "";
}
-
- // build metadata
- private MetaDataRegisterDTO buildMetaDataDTO(final ShenyuSpringMvcClient
shenyuSpringMvcClient, final String prePath) {
- // contextPath
- String contextPath = this.contextPath;
- // appName
- String appName = this.appName;
- // path
- String path;
- if (StringUtils.isEmpty(contextPath)) {
- path = prePath + shenyuSpringMvcClient.path();
- } else {
- path = contextPath + prePath + shenyuSpringMvcClient.path();
+
+ @Override
+ protected Class<ShenyuSpringMvcClient> getAnnotationType() {
+ return ShenyuSpringMvcClient.class;
+ }
+
+ @Override
+ protected void handleMethod(final Object bean, final Class<?> clazz,
+ @Nullable final ShenyuSpringMvcClient
beanShenyuClient,
+ final Method method, final String superPath) {
+ // get RequestMapping annotation
+ final RequestMapping requestMapping =
AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
+ // get ShenyuSpringMvcClient annotation
+ ShenyuSpringMvcClient methodShenyuClient =
AnnotatedElementUtils.findMergedAnnotation(method, ShenyuSpringMvcClient.class);
+ methodShenyuClient = Objects.isNull(methodShenyuClient) ?
beanShenyuClient : methodShenyuClient;
+ // the result of ReflectionUtils#getUniqueDeclaredMethods contains
method such as hashCode, wait, toSting
+ // add Objects.nonNull(requestMapping) to make sure not register wrong
method
+ if (Objects.nonNull(methodShenyuClient) &&
Objects.nonNull(requestMapping)) {
+ getPublisher().publishEvent(buildMetaDataDTO(bean,
methodShenyuClient, buildApiPath(method, superPath, methodShenyuClient), clazz,
method));
}
- // desc info
- String desc = shenyuSpringMvcClient.desc();
- // ruleName
- String configRuleName = shenyuSpringMvcClient.ruleName();
- String ruleName = StringUtils.isBlank(configRuleName) ? path :
configRuleName;
- return MetaDataRegisterDTO.builder()
- .contextPath(contextPath)
- .appName(appName)
- .path(path)
- .pathDesc(desc)
- .rpcType(RpcTypeEnum.HTTP.getName())
- .enabled(shenyuSpringMvcClient.enabled())
- .ruleName(ruleName)
- .registerMetaData(shenyuSpringMvcClient.registerMetaData())
- .build();
+ }
+
+ //...
+
+ // 构造元数据
+ @Override
+ protected MetaDataRegisterDTO buildMetaDataDTO(final Object bean,
+ @NonNull final
ShenyuSpringMvcClient shenyuClient,
+ final String path, final
Class<?> clazz,
+ final Method method) {
+ //...
}
}
-
```
-In the post-processor, you need to read the configuration property, if
`isFull=true`, it means register the whole microservice. Get the `Controller`
annotation, `RequestMapping` annotation, `ShenyuSpringMvcClient` annotation of
the current `bean` and determine if the current `bean` is an interface by
reading these annotations? Does the interface need to be registered? Does the
method need to be registered? Then build metadata based on the properties in
the `ShenyuSpringMvcClient` annotat [...]
-
+The registration logic is done through `publisher.publishEvent()`.
The `Controller` annotation and the `RequestMapping` annotation are provided
by `Spring`, which you should be familiar with, so I won't go into details. The
`ShenyuSpringMvcClient` annotation is provided by `Apache ShenYu` to register
the `SpringMvc` client, which is defined as follows.
@@ -416,10 +516,16 @@ The `Controller` annotation and the `RequestMapping`
annotation are provided by
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ShenyuSpringMvcClient {
- // path
+
+ // path
+ @AliasFor(attribute = "path")
+ String value() default "";
+
+ // path
+ @AliasFor(attribute = "value")
String path();
- // ruleName
+ // ruleName
String ruleName() default "";
// desc info
@@ -443,7 +549,7 @@ It is used as follows.
@RequestMapping("/test")
@ShenyuSpringMvcClient(path = "/test/**") // register the entire interface
public class HttpTestController {
- //......
+ //......
}
```
@@ -469,25 +575,27 @@ public class OrderController {
}
```
-- publisher.publishEvent()
+- publisher.publishEvent()
+
This method sends the data to the `Disruptor` queue. More details about the
`Disruptor` queue are not described here, which does not affect the flow of
analyzing the registration.
When the data is sent, the consumers of the `Disruptor` queue will process the
data for consumption.
This method sends the data to the `Disruptor` queue. More details about the
`Disruptor` queue are not described here, which does not affect the flow of
analyzing the registration.
-- QueueConsumer
-`QueueConsumer` is a consumer that implements the `WorkHandler` interface,
which is created in the `providerManage.startup()` logic. The `WorkHandler`
interface is the data consumption interface for `disruptor`, and the only
method is `onEvent()`.
+- QueueConsumer
+
+`QueueConsumer` is a consumer that implements the `WorkHandler` interface,
which is created in the `providerManage.startup()` logic. The `WorkHandler`
interface is the data consumption interface for `Disruptor`, and the only
method is `onEvent()`.
```java
package com.lmax.disruptor;
public interface WorkHandler<T> {
- void onEvent(T var1) throws Exception;
+ void onEvent(T event) throws Exception;
}
```
@@ -506,6 +614,8 @@ public class QueueConsumer<T> implements
WorkHandler<DataEvent<T>> {
@Override
public void onEvent(final DataEvent<T> t) {
if (t != null) {
+ // Use different thread pools based on DataEvent type
+ ThreadPoolExecutor executor = orderly(t);
// create queue consumption tasks via factory
QueueConsumerExecutor<T> queueConsumerExecutor = factory.create();
// set data
@@ -521,31 +631,30 @@ public class QueueConsumer<T> implements
WorkHandler<DataEvent<T>> {
`QueueConsumerExecutor` is the task that is executed in the thread pool, it
implements the `Runnable` interface, and there are two specific implementation
classes.
-- `RegisterClientConsumerExecutor`: the client-side consumer executor.
-- `RegisterServerConsumerExecutor`: server-side consumer executor.
+- `RegisterClientConsumerExecutor`:the client-side consumer executor.
+- `RegisterServerConsumerExecutor`:server-side consumer executor.
As the name implies, one is responsible for handling client-side tasks, and
one is responsible for handling server-side tasks (the server side is `admin`,
which is analyzed below).

-
-- RegisterClientConsumerExecutor
+- RegisterClientConsumerExecutor
The logic of the rewritten `run()` is as follows.
```java
-public final class RegisterClientConsumerExecutor extends
QueueConsumerExecutor<DataTypeParent> {
+public final class RegisterClientConsumerExecutor<T extends DataTypeParent>
extends QueueConsumerExecutor<T> {
//......
@Override
public void run() {
// get data
- DataTypeParent dataTypeParent = getData();
+ final T data = getData();
// call the appropriate processor for processing according to the data
type
-
subscribers.get(dataTypeParent.getType()).executor(Lists.newArrayList(dataTypeParent));
+ subscribers.get(data.getType()).executor(Lists.newArrayList(data));
}
}
@@ -555,19 +664,20 @@ Different processors are called to perform the
corresponding tasks based on diff
```java
public enum DataType {
-
+
META_DATA,
-
+
URI,
}
```
-- ExecutorSubscriber#executor()
+- ExecutorSubscriber#executor()
-The actuator subscribers are also divided into two categories, one that
handles metadata and one that handles `URIs`. There are two on the client side
and two on the server side, so there are four in total.
+The actuator subscribers are divided into two categories, one that handles
metadata and one that handles `URIs`. There are two on the client side and two
on the server side, so there are four in total.

+Here is the registration metadata information, so the execution class is
`ShenyuClientMetadataExecutorSubscriber`.
- ShenyuClientMetadataExecutorSubscriber#executor()
@@ -581,7 +691,7 @@ public class ShenyuClientMetadataExecutorSubscriber
implements ExecutorTypeSubsc
@Override
public DataType getType() {
- return DataType.META_DATA;
+ return DataType.META_DATA;
}
@Override
@@ -594,22 +704,23 @@ public class ShenyuClientMetadataExecutorSubscriber
implements ExecutorTypeSubsc
}
```
+The two registration interfaces get the data well and call the `publish()`
method to publish the data to the `Disruptor` queue.
-- ShenyuClientRegisterRepository#persistInterface()
+- `ShenyuServerRegisterRepository`
-`ShenyuClientRegisterRepository` is an interface to represent client-side data
registration, and it has five implementation classes at present, each of which
represents a registration method.
+The `ShenyuServerRegisterRepository` interface is a service registration
interface, which has five implementation classes, indicating five types of
registration.
-- `ConsulClientRegisterRepository`: client registration is achieved through
`Consul`.
-- `EtcdClientRegisterRepository`: client registration through `Etcd`.
-- `HttpClientRegisterRepository`: client registration via `Http`;
`NacosClientRegisterRepository`: client registration via `Http`.
-- `NacosClientRegisterRepository`: client registration via `Nacos`;
`ZookeeperClientRegisterRepository`: client registration via `Nacos`.
-- `ZookeeperClientRegisterRepository`: client registration via `Zookeeper`.
+- `ConsulServerRegisterRepository`: registration is achieved through `Consul`;
+- `EtcdServerRegisterRepository`: registration through `Etcd`.
+- `NacosServerRegisterRepository`: registration through `Nacos`.
+- `ShenyuHttpRegistryController`: registration via `Http`;
`ShenyuHttpRegistryController`: registration via `Http`.
+- `ZookeeperServerRegisterRepository`: registration through `Zookeeper`.

-As you can see from the diagram, the loading of the registry is done by means
of `SPI`. This was mentioned earlier, and the specific class loading is done in
the client-side generic configuration file by specifying the properties in the
configuration file.
+As you can see from the diagram, the loading of the registry is done by means
of SPI. This was mentioned earlier, and the specific class loading is done in
the client-side generic configuration file by specifying the properties in the
configuration file.
```java
@@ -638,52 +749,116 @@ public final class ShenyuClientRegisterRepositoryFactory
{
}
```
-The source code analysis in this article is based on the `Http` way of
registration, so we first analyze the `HttpClientRegisterRepository`, and the
other registration methods will be analyzed afterwards.
+The source code analysis in this article is based on the Http way of
registration, so we first analyze the HttpClientRegisterRepository, and the
other registration methods will be analyzed afterwards.
+Registration by way of `http` is very simple, it is to call the tool class to
send http requests. The registration metadata and URI are both called by the
same method `doRegister()`, specifying the interface and type.
-Registration by way of `http` is very simple, it is to call the tool class to
send `http` requests. The registration metadata and URI are both called by the
same method `doRegister()`, specifying the interface and type.
-
-- `/shenyu-client/register-metadata`: the interface provided by the server for
registering metadata.
-- `/shenyu-client/register-uri`: Server-side interface for registering URIs.
+- `Constants.URI_PATH` = `/shenyu-client/register-metadata`: the interface
provided by the server for registering metadata.
+- `Constants.META_PATH` = `/shenyu-client/register-uri`: Server-side interface
for registering URIs.
```java
@Join
-public class HttpClientRegisterRepository implements
ShenyuClientRegisterRepository {
- // server-side provided interface for registering metadata
- private static final String META_PATH = "/shenyu-client/register-metadata";
+public class HttpClientRegisterRepository extends FailbackRegistryRepository {
- // the interface provided by the server for registering URIs
- private static final String URI_PATH = "/shenyu-client/register-uri";
+ private static final Logger LOGGER =
LoggerFactory.getLogger(HttpClientRegisterRepository.class);
- @Override
- public void persistURI(final URIRegisterDTO registerDTO) {
- doRegister(registerDTO, URI_PATH, Constants.URI);
+ private static URIRegisterDTO uriRegisterDTO;
+
+ private String username;
+
+ private String password;
+
+ private List<String> serverList;
+
+ private String accessToken;
+
+ public HttpClientRegisterRepository() {
}
+ public HttpClientRegisterRepository(final ShenyuRegisterCenterConfig
config) {
+ init(config);
+ }
+
@Override
- public void persistInterface(final MetaDataRegisterDTO metadata) {
- doRegister(metadata, META_PATH, META_TYPE);
+ public void init(final ShenyuRegisterCenterConfig config) {
+ // admin username
+ this.username = config.getProps().getProperty(Constants.USER_NAME);
+ // admin paaword
+ this.password = config.getProps().getProperty(Constants.PASS_WORD);
+ // admin server address
+ this.serverList =
Lists.newArrayList(Splitter.on(",").split(config.getServerLists()));
+ // set access token
+ this.setAccessToken();
}
-
- // do register
+
+ /**
+ * Persist uri.
+ *
+ * @param registerDTO the register dto
+ */
+ @Override
+ public void doPersistURI(final URIRegisterDTO registerDTO) {
+ if (RuntimeUtils.listenByOther(registerDTO.getPort())) {
+ return;
+ }
+ doRegister(registerDTO, Constants.URI_PATH, Constants.URI);
+ uriRegisterDTO = registerDTO;
+ }
+
+ @Override
+ public void doPersistInterface(final MetaDataRegisterDTO metadata) {
+ doRegister(metadata, Constants.META_PATH, Constants.META_TYPE);
+ }
+
+ @Override
+ public void close() {
+ if (uriRegisterDTO != null) {
+ uriRegisterDTO.setEventType(EventType.DELETED);
+ doRegister(uriRegisterDTO, Constants.URI_PATH, Constants.URI);
+ }
+ }
+
+ private void setAccessToken() {
+ for (String server : serverList) {
+ try {
+ Optional<?> login = RegisterUtils.doLogin(username, password,
server.concat(Constants.LOGIN_PATH));
+ login.ifPresent(v -> this.accessToken = String.valueOf(v));
+ } catch (Exception e) {
+ LOGGER.error("Login admin url :{} is fail, will retry. cause:
{} ", server, e.getMessage());
+ }
+ }
+ }
+
private <T> void doRegister(final T t, final String path, final String
type) {
+ int i = 0;
// iterate through the list of admin services (admin may be clustered)
for (String server : serverList) {
+ i++;
+ String concat = server.concat(path);
try {
+ // 设置访问token
+ if (StringUtils.isBlank(accessToken)) {
+ this.setAccessToken();
+ if (StringUtils.isBlank(accessToken)) {
+ throw new NullPointerException("accessToken is null");
+ }
+ }
// calling the tool class to send http requests
- RegisterUtils.doRegister(GsonUtils.getInstance().toJson(t),
server + path, type);
+ RegisterUtils.doRegister(GsonUtils.getInstance().toJson(t),
concat, type, accessToken);
return;
} catch (Exception e) {
- LOGGER.error("register admin url :{} is fail, will retry",
server);
+ LOGGER.error("Register admin url :{} is fail, will retry.
cause:{}", server, e.getMessage());
+ if (i == serverList.size()) {
+ throw new RuntimeException(e);
+ }
}
}
}
}
-
```
-Serialize the data and send it via `OkHttp`.
+Serialize the data and send it via OkHttp.
```java
@@ -693,7 +868,12 @@ public final class RegisterUtils {
// Sending data via OkHttp
public static void doRegister(final String json, final String url, final
String type) throws IOException {
- String result = OkHttpTools.getInstance().post(url, json);
+ if (!StringUtils.hasLength(accessToken)) {
+ LOGGER.error("{} client register error accessToken is null, please
check the config : {} ", type, json);
+ return;
+ }
+ Headers headers = new Headers.Builder().add(Constants.X_ACCESS_TOKEN,
accessToken).build();
+ String result = OkHttpTools.getInstance().post(url, json, headers);
if (Objects.equals(SUCCESS, result)) {
LOGGER.info("{} client register success: {} ", type, json);
} else {
@@ -705,135 +885,11 @@ public final class RegisterUtils {
At this point, the logic of the client registering metadata by means of `http`
is finished. To summarize: construct metadata by reading custom annotation
information, send the data to the `Disruptor` queue, then consume the data from
the queue, put the consumer into the thread pool to execute, and finally send
an `http` request to the `admin`.
-
-The source code analysis process of the client-side metadata registration
process is completed and described in a flowchart as follows.
-
-
-
-
-
-#### 2.4 ContextRegisterListener
-
-Create `ContextRegisterListener`, which is responsible for the construction
and registration of client-side `URI` data, and its creation is done in the
configuration file.
-
-
-```java
-@Configuration
-@ImportAutoConfiguration(ShenyuClientCommonBeanConfiguration.class)
-public class ShenyuSpringMvcClientConfiguration {
- // ......
-
- // create ContextRegisterListener
- @Bean
- public ContextRegisterListener contextRegisterListener(final
ShenyuClientConfig clientConfig) {
- return new
ContextRegisterListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()));
- }
-}
-```
-
-
-`ContextRegisterListener` implements the `ApplicationListener` interface and
overrides the `onApplicationEvent()` method, which is executed when a Spring
event occurs.
-
-
-```java
-public class ContextRegisterListener implements
ApplicationListener<ContextRefreshedEvent> {
-
- //......
-
- //Instantiation is done through the constructor
- public ContextRegisterListener(final PropertiesConfig clientConfig) {
- // read shenyu.client.http properties
- Properties props = clientConfig.getProps();
- // isFull
- this.isFull =
Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL,
Boolean.FALSE.toString()));
- // contextPath
- String contextPath =
props.getProperty(ShenyuClientConstants.CONTEXT_PATH);
- this.contextPath = contextPath;
- if (isFull) {
- if (StringUtils.isBlank(contextPath)) {
- String errorMsg = "http register param must config the
contextPath";
- LOG.error(errorMsg);
- throw new ShenyuClientIllegalArgumentException(errorMsg);
- }
- this.contextPath = contextPath + "/**";
- }
- // port
- int port =
Integer.parseInt(props.getProperty(ShenyuClientConstants.PORT));
- // appName
- this.appName = props.getProperty(ShenyuClientConstants.APP_NAME);
- // host
- this.host = props.getProperty(ShenyuClientConstants.HOST);
- this.port = port;
- }
-
- // This method is executed when a context refresh
event(ContextRefreshedEvent), occurs
- @Override
- public void onApplicationEvent(@NonNull final ContextRefreshedEvent
contextRefreshedEvent) {
- // The contents of the method are guaranteed to be executed only once
- if (!registered.compareAndSet(false, true)) {
- return;
- }
- // If isFull=true means register the entire service, build the
metadata and register
- if (isFull) {
- publisher.publishEvent(buildMetaDataDTO());
- }
-
- // build URI data and register it
- publisher.publishEvent(buildURIRegisterDTO());
- }
-
- // build URI data
- private URIRegisterDTO buildURIRegisterDTO() {
- String host = IpUtils.isCompleteHost(this.host) ? this.host :
IpUtils.getHost(this.host);
- return URIRegisterDTO.builder()
- .contextPath(this.contextPath)
- .appName(appName)
- .host(host)
- .port(port)
- .rpcType(RpcTypeEnum.HTTP.getName())
- .build();
- }
-
- // build metadata
- private MetaDataRegisterDTO buildMetaDataDTO() {
- String contextPath = this.contextPath;
- String appName = this.appName;
- return MetaDataRegisterDTO.builder()
- .contextPath(contextPath)
- .appName(appName)
- .path(contextPath)
- .rpcType(RpcTypeEnum.HTTP.getName())
- .enabled(true)
- .ruleName(contextPath)
- .build();
- }
-}
-
-```
-
-The main thing in the constructor is to read the property configuration.
-
-The `onApplicationEvent()` method is executed when a `Spring` event occurs,
the parameter here is `ContextRefreshedEvent`, which means the context refresh
event. The logic here is executed when the `Spring` container is ready: if
`isFull=true` means register the whole service, build the metadata and register
it, the case `isFull=true` is not handled in the post-processor
`SpringMvcClientBeanPostProcessor` analyzed earlier, so here it is processing.
Then the `URI` data is constructed and [...]
-
-
-> `ContextRefreshedEvent` is a `Spring` built-in event. It is fired when the
`ApplicationContext` is initialized or refreshed. This can also happen in the
`ConfigurableApplicationContext` interface using the `refresh()` method.
Initialization here means that all `Bean`s have been successfully loaded,
post-processing `Bean`s have been detected and activated, all `Singleton Bean`s
have been pre-instantiated, and the `ApplicationContext` container is ready to
be used.
-
-
-The registration logic is done through `publisher.publishEvent()`. It has been
analyzed earlier: data is written to the `Disruptor` queue, consumed from it,
and finally processed by the `ExecutorSubscriber`.
-
-- ExecutorSubscriber#executor()
-
-The actuator subscribers are divided into two categories, one that handles
metadata and one that handles `URIs`. There are two on the client side and two
on the server side, so there are four in total.
-
-
-
-Here is the registration `URI` information, so the execution class is
`ShenyuClientURIExecutorSubscriber`.
-
+Similarly, `ShenyuClientURIExecutorSubscriber` is the execution class of
registering `URI` information.
- ShenyuClientURIExecutorSubscriber#executor()
-The main logic is to iterate through the URI data collection and implement
data registration through the `persistURI()` method.
-
+The main logic is to iterate through the URI data collection and implement
data registration through the persistURI() method.
```java
@@ -889,9 +945,9 @@ Data registration is achieved through the `persistURI()`
method. The whole logic
The analysis of the registration logic of the client is finished here, and the
metadata and URI data constructed are sent to the `Disruptor` queue, from which
they are then consumed, read, and sent to `admin` via `http`.
-The source code analysis of the client-side `URI` registration process is
complete, with the following flow chart.
+The source code analysis of the client-side metadata and `URI` registration
process is complete, with the following flow chart.
-
+
### 3. Server-side registration process
@@ -923,7 +979,7 @@ public class ShenyuHttpRegistryController implements
ShenyuServerRegisterReposit
@PostMapping("/register-metadata")
@ResponseBody
public String registerMetadata(@RequestBody final MetaDataRegisterDTO
metaDataRegisterDTO) {
- publish(metaDataRegisterDTO);
+ publisher.publish(metaDataRegisterDTO);
return ShenyuResultMessage.SUCCESS;
}
@@ -931,29 +987,13 @@ public class ShenyuHttpRegistryController implements
ShenyuServerRegisterReposit
@PostMapping("/register-uri")
@ResponseBody
public String registerURI(@RequestBody final URIRegisterDTO
uriRegisterDTO) {
- publish(uriRegisterDTO);
+ publisher.publish(uriRegisterDTO);
return ShenyuResultMessage.SUCCESS;
}
-
- // publish event
- private <T> void publish(final T t) {
- publisher.publish(Collections.singletonList(t));
- }
}
```
-The two registration interfaces get the data well and call the `publish()`
method to publish the data to the `Disruptor` queue.
-
-- `ShenyuServerRegisterRepository`
-
-The `ShenyuServerRegisterRepository` interface is a service registration
interface, which has five implementation classes, indicating five types of
registration.
-
-- `ConsulServerRegisterRepository`: registration is achieved through `Consul`;
-- `EtcdServerRegisterRepository`: registration through `Etcd`.
-- `NacosServerRegisterRepository`: registration through `Nacos`.
-- `ShenyuHttpRegistryController`: registration via `Http`;
`ShenyuHttpRegistryController`: registration via `Http`.
-- `ZookeeperServerRegisterRepository`: registration through `Zookeeper`.
The exact method used is specified by the configuration file and then loaded
via `SPI`.
@@ -961,7 +1001,7 @@ The exact method used is specified by the configuration
file and then loaded via
In the `application.yml` file in `shenyu-admin` configure the registration
method, `registerType` specify the registration type, when registering with
`http`, `serverLists` do not need to be filled in, for more configuration
instructions you can refer to the official website [Client Access
Configuration](https://shenyu.apache.org/zh/docs/user-guide/register-center-access).
-```java
+```yaml
shenyu:
register:
registerType: http
@@ -983,7 +1023,7 @@ public class RegisterCenterConfiguration {
}
//create ShenyuServerRegisterRepository to register in admin
- @Bean
+ @Bean(destroyMethod = "close")
public ShenyuServerRegisterRepository shenyuServerRegisterRepository(final
ShenyuRegisterCenterConfig shenyuRegisterCenterConfig, final
List<ShenyuClientRegisterService> shenyuClientRegisterService) {
// 1. get the registration type from the configuration property
String registerType = shenyuRegisterCenterConfig.getRegisterType();
@@ -1021,22 +1061,23 @@ In the process of creating
`shenyuServerRegisterRepository`, a series of prepara
-- RegisterServerDisruptorPublisher#publish()
+- RegisterClientServerDisruptorPublisher#publish()
The server-side publisher that writes data to the `Disruptor` queue , built
via the singleton pattern.
```java
-public class RegisterServerDisruptorPublisher implements
ShenyuServerRegisterPublisher {
- private static final RegisterServerDisruptorPublisher INSTANCE = new
RegisterServerDisruptorPublisher();
+public class RegisterClientServerDisruptorPublisher implements
ShenyuServerRegisterPublisher {
+ private static final RegisterClientServerDisruptorPublisher INSTANCE = new
private static final RegisterClientServerDisruptorPublisher INSTANCE = new
RegisterServerDisruptorPublisher();
+();
- public static RegisterServerDisruptorPublisher getInstance() {
+ public static RegisterClientServerDisruptorPublisher getInstance() {
return INSTANCE;
}
//prepare for event publishing, add server-side metadata and URI
subscribers, process data. And start the Disruptor queue.
public void start(final Map<String, ShenyuClientRegisterService>
shenyuClientRegisterService) {
- factory = new RegisterServerExecutorFactory();
+ RegisterServerExecutorFactory factory = new
RegisterServerExecutorFactory();
// add URI data subscriber
factory.addSubscribers(new
URIRegisterExecutorSubscriber(shenyuClientRegisterService));
// add Metadata subscriber
@@ -1048,9 +1089,16 @@ public class RegisterServerDisruptorPublisher implements
ShenyuServerRegisterPub
// write data to queue
@Override
- public <T> void publish(final T data) {
+ public <T> void publish(final DataTypeParent data) {
DisruptorProvider<Object> provider = providerManage.getProvider();
- provider.onData(f -> f.setData(data));
+ provider.onData(Collections.singleton(data));
+ }
+
+ // write data to queue on batch
+ @Override
+ public void publish(final Collection<? extends DataTypeParent> dataList) {
+ DisruptorProvider<Collection<DataTypeParent>> provider =
providerManage.getProvider();
+
provider.onData(dataList.stream().map(DataTypeParent.class::cast).collect(Collectors.toList()));
}
@Override
@@ -1096,6 +1144,8 @@ public class QueueConsumer<T> implements
WorkHandler<DataEvent<T>> {
@Override
public void onEvent(final DataEvent<T> t) {
if (t != null) {
+ // Use different thread pools based on DataEvent type
+ ThreadPoolExecutor executor = orderly(t);
// create queue consumption tasks via factory
QueueConsumerExecutor<T> queueConsumerExecutor = factory.create();
// set data
@@ -1129,10 +1179,11 @@ public final class RegisterServerConsumerExecutor
extends QueueConsumerExecutor<
@Override
public void run() {
- //get the data from the disruptor queue
- List<DataTypeParent> results = getData();
- // check data
- results = results.stream().filter(data ->
isValidData(data)).collect(Collectors.toList());
+ //get the data from the disruptor queue and check data
+ Collection<DataTypeParent> results = getData()
+ .stream()
+ .filter(this::isValidData)
+ .collect(Collectors.toList());
if (CollectionUtils.isEmpty(results)) {
return;
}
@@ -1141,9 +1192,9 @@ public final class RegisterServerConsumerExecutor extends
QueueConsumerExecutor<
}
// get subscribers by type
- private ExecutorSubscriber getType(final List<DataTypeParent> list) {
- DataTypeParent result = list.get(0);
- return subscribers.get(result.getType());
+ private ExecutorSubscriber<DataTypeParent> selectExecutor(final
Collection<DataTypeParent> list) {
+ final Optional<DataTypeParent> first = list.stream().findFirst();
+ return subscribers.get(first.orElseThrow(() -> new
RuntimeException("the data type is not found")).getType());
}
}
@@ -1175,15 +1226,15 @@ public class MetadataExecutorSubscriber implements
ExecutorTypeSubscriber<MetaDa
@Override
public void executor(final Collection<MetaDataRegisterDTO>
metaDataRegisterDTOList) {
// Traversing the metadata list
- for (MetaDataRegisterDTO metaDataRegisterDTO :
metaDataRegisterDTOList) {
- // Get registered Service by type
- ShenyuClientRegisterService shenyuClientRegisterService =
this.shenyuClientRegisterService.get(metaDataRegisterDTO.getRpcType());
- Objects.requireNonNull(shenyuClientRegisterService);
- // Registration of metadata, locking to ensure sequential
execution and prevent concurrent errors
- synchronized (ShenyuClientRegisterService.class) {
- shenyuClientRegisterService.register(metaDataRegisterDTO);
- }
- }
+ metaDataRegisterDTOList.forEach(meta -> {
+
Optional.ofNullable(this.shenyuClientRegisterService.get(meta.getRpcType())) //
Get registered Service by type
+ .ifPresent(shenyuClientRegisterService -> {
+ // Registration of metadata, locking to ensure
sequential execution and prevent concurrent errors
+ synchronized (shenyuClientRegisterService) {
+ shenyuClientRegisterService.register(meta);
+ }
+ });
+ });
}
}
```
@@ -1209,11 +1260,25 @@ public class URIRegisterExecutorSubscriber implements
ExecutorTypeSubscriber<URI
if (CollectionUtils.isEmpty(dataList)) {
return;
}
- // Build URI data types and register them with the registerURI method
+
findService(dataList).ifPresent(service -> {
Map<String, List<URIRegisterDTO>> listMap = buildData(dataList);
listMap.forEach(service::registerURI);
});
+ final Map<String, List<URIRegisterDTO>> groupByRpcType =
dataList.stream()
+ .filter(data -> StringUtils.isNotBlank(data.getRpcType()))
+ .collect(Collectors.groupingBy(URIRegisterDTO::getRpcType));
+ for (Map.Entry<String, List<URIRegisterDTO>> entry :
groupByRpcType.entrySet()) {
+ final String rpcType = entry.getKey();
+ // Get registered Service by type
+ Optional.ofNullable(shenyuClientRegisterService.get(rpcType))
+ .ifPresent(service -> {
+ final List<URIRegisterDTO> list = entry.getValue();
+ // Build URI data types and register them with the
registerURI method
+ Map<String, List<URIRegisterDTO>> listMap =
buildData(list);
+ listMap.forEach(service::registerURI);
+ });
+ }
}
// Find Service by type
@@ -1242,6 +1307,7 @@ public class URIRegisterExecutorSubscriber implements
ExecutorTypeSubscriber<URI
- `ShenyuClientRegisterSofaServiceImpl`: `Sofa` class, handles `Sofa`
registration types.
- `ShenyuClientRegisterSpringCloudServiceImpl`: `SpringCloud` class, handles
`SpringCloud` registration types.
- `ShenyuClientRegisterTarsServiceImpl`: `Tars` class, handles `Tars`
registration types.
+- `ShenyuClientRegisterWebSocketServiceImpl`: `Websocket` class,handles
`Websocket` registration types.
From the above, we can see that each microservice has a corresponding
registration implementation class. The source code analysis in this article is
based on the official
[shenyu-examples-http](https://github.com/apache/incubator-shenyu/tree/master/shenyu-examples/shenyu-examples-http)
as an example, it is of `http` registration type, so the registration
implementation class for metadata and URI data is
`ShenyuClientRegisterDivideServiceImpl`:
`ShenyuClientRegisterDivideServiceImpl`.
@@ -1249,7 +1315,11 @@ From the above, we can see that each microservice has a
corresponding registrati
- register():
```java
-public String register(final MetaDataRegisterDTO dto) {
+public abstract class AbstractShenyuClientRegisterServiceImpl extends
FallbackShenyuClientRegisterService implements ShenyuClientRegisterService {
+
+ //......
+
+ public String register(final MetaDataRegisterDTO dto) {
// 1.register selector information
String selectorHandler = selectorHandler(dto);
String selectorId = selectorService.registerDefault(dto,
PluginNameAdapter.rpcTypeAdapter(rpcType()), selectorHandler);
@@ -1266,6 +1336,7 @@ public String register(final MetaDataRegisterDTO dto) {
}
return ShenyuResultMessage.SUCCESS;
}
+}
```
The whole registration logic can be divided into 4 steps.
@@ -1287,7 +1358,11 @@ The source code of the server-side metadata registration
process is analyzed and
- registerURI()
```java
-public String registerURI(final String selectorName, final
List<URIRegisterDTO> uriList) {
+public abstract class AbstractShenyuClientRegisterServiceImpl extends
FallbackShenyuClientRegisterService implements ShenyuClientRegisterService {
+
+ //......
+
+ public String registerURI(final String selectorName, final
List<URIRegisterDTO> uriList) {
if (CollectionUtils.isEmpty(uriList)) {
return "";
}
@@ -1301,13 +1376,14 @@ public String registerURI(final String selectorName,
final List<URIRegisterDTO>
selectorDO.setHandle(handler);
SelectorData selectorData = selectorService.buildByName(selectorName,
PluginNameAdapter.rpcTypeAdapter(rpcType()));
selectorData.setHandle(handler);
-
+
// Update records in the database
selectorService.updateSelective(selectorDO);
// publish Event to gateway
eventPublisher.publishEvent(new
DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
Collections.singletonList(selectorData)));
return ShenyuResultMessage.SUCCESS;
}
+}
```
After `admin` gets the `URI` data, it mainly updates the `handler` information
in the selector, then writes it to the database, and finally publishes the
event notification gateway. The logic of notifying the gateway is done by the
data synchronization operation, which has been analyzed in the previous
article, so we will not repeat it.
@@ -1327,7 +1403,7 @@ This article focuses on the `http registration` module of
the `Apache ShenYu` ga
- The register center is for registering client information to `admin` to
facilitate traffic filtering.
- `http` registration is to register client metadata information and `URI`
information to `admin`.
- `http` service access is identified by the annotation
`@ShenyuSpringMvcClient`.
-- construction of the registration information mainly through the `Spring`
post-processor `BeanPostProcessor` and the application listener
`ApplicationListener`.
+- construction of the registration information mainly through the application
listener `ApplicationListener`.
- loading of the registration type is done through `SPI`.
- The `Disruptor` queue was introduced to decouple data from operations, and
data buffering.
- The implementation of the registry uses interface-oriented programming,
using design patterns such as template methods, singleton, and observer.
diff --git
a/i18n/zh/docusaurus-plugin-content-blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
b/i18n/zh/docusaurus-plugin-content-blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
index ca672d74448..b5fb8ae8fd3 100644
---
a/i18n/zh/docusaurus-plugin-content-blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
+++
b/i18n/zh/docusaurus-plugin-content-blog/RegisterCenter-SourceCode-Analysis-Http-Register.md
@@ -10,7 +10,7 @@ tags: [http,register center,Apache ShenYu]
在`ShenYu`网关中,注册中心是用于将客户端信息注册到`shenyu-admin`,`admin`再通过数据同步将这些信息同步到网关,网关通过这些数据完成流量筛选。客户端信息主要包括`接口信息`和`URI信息`。
-> 本文基于`shenyu-2.4.1`版本进行源码分析,官网的介绍请参考
[客户端接入原理](https://shenyu.apache.org/zh/docs/design/register-center-design) 。
+> 本文基于`shenyu-2.5.0`版本进行源码分析,官网的介绍请参考
[客户端接入原理](https://shenyu.apache.org/zh/docs/design/register-center-design) 。
### 1. 注册中心原理
@@ -20,7 +20,7 @@ tags: [http,register center,Apache ShenYu]

-图中的注册中心需要用户指定使用哪种注册类型,`ShenYu`当前支持`Http`、`Zookeeper`、`Etcd`、`Consul`和`Nacos`进行注册。具体如何配置请参考
[客户端接入配置](https://shenyu.apache.org/zh/docs/user-guide/register-center-access)
。
+图中的注册中心需要用户指定使用哪种注册类型,`ShenYu`当前支持`Http`、`Zookeeper`、`Etcd`、`Consul`和`Nacos`进行注册。具体如何配置请参考
[客户端接入配置](https://shenyu.apache.org/zh/docs/user-guide/property-config/register-center-access)
。
`ShenYu`在注册中心的原理设计上引入了`Disruptor`,`Disruptor`队列在其中起到数据与操作解耦,利于扩展。如果注册请求过多,导致注册异常,也有数据缓冲作用。
@@ -57,7 +57,7 @@ tags: [http,register center,Apache ShenYu]
-当客户端启动后,根据相关配置,读取属性信息,然后写入队列。以官方提供的
[shenyu-examples-http](https://github.com/apache/incubator-shenyu/tree/master/shenyu-examples/shenyu-examples-http)
为例,开始源码分析。官方提供的例子是一个由`springboot`构建的微服务。注册中心的相关配置可以参考官网
[客户端接入配置](https://shenyu.apache.org/zh/docs/user-guide/register-center-access) 。
+当客户端启动后,根据相关配置,读取属性信息,然后写入队列。以官方提供的
[shenyu-examples-http](https://github.com/apache/incubator-shenyu/tree/master/shenyu-examples/shenyu-examples-http)
为例,开始源码分析。官方提供的例子是一个由`springboot`构建的微服务。注册中心的相关配置可以参考官网
[客户端接入配置](https://shenyu.apache.org/zh/docs/user-guide/property-config/register-center-access)
。
@@ -75,11 +75,14 @@ tags: [http,register center,Apache ShenYu]
我们分析的是通过`http`的方式进行注册,所以需要进行如下配置:
-```xml
+```yaml
shenyu:
register:
registerType: http
serverLists: http://localhost:9095
+ props:
+ username: admin
+ password: 123456
client:
http:
props:
@@ -93,6 +96,8 @@ shenyu:
- `registerType`: 服务注册类型,填写 `http`。
- `serverList`: 为`http`注册类型时,填写`Shenyu-Admin`项目的地址,注意加上`http://`,多个地址用英文逗号分隔。
+- `username`: `Shenyu-Admin`用户名
+- `password`: `Shenyu-Admin`用户对应的密码
- `port`: 你本项目的启动端口,目前`springmvc/tars/grpc`需要进行填写。
- `contextPath`: 为你的这个`mvc`项目在`shenyu`网关的路由前缀, 比如`/order` ,`/product`
等等,网关会根据你的这个前缀来进行路由。
- `appName`:你的应用名称,不配置的话,会默认取 `spring.application.name` 的值。
@@ -102,7 +107,7 @@ shenyu:
-首先读取到的配置文件是 `ShenyuSpringMvcClientConfiguration`,它是`shenyu`
客户端`http`注册配置类,通过`@Configuration`表示这是一个配置类,通过`@ImportAutoConfiguration`引入其他配置类。创建`SpringMvcClientBeanPostProcessor`,主要处理元数据。创建`ContextRegisterListener`,主要处理
`URI` 信息。
+首先读取到的配置文件是 `ShenyuSpringMvcClientConfiguration`,它是`shenyu`
客户端`http`注册配置类,通过`@Configuration`表示这是一个配置类,通过`@ImportAutoConfiguration`引入其他配置类。创建`SpringMvcClientEventListener`,主要处理元数据和
`URI` 信息。
```java
/**
@@ -110,19 +115,15 @@ shenyu:
*/
@Configuration
@ImportAutoConfiguration(ShenyuClientCommonBeanConfiguration.class)
+@ConditionalOnProperty(value = "shenyu.register.enabled", matchIfMissing =
true, havingValue = "true")
public class ShenyuSpringMvcClientConfiguration {
-
- //创建SpringMvcClientBeanPostProcessor,主要处理元数据
- @Bean
- public SpringMvcClientBeanPostProcessor
springHttpClientBeanPostProcessor(final ShenyuClientConfig clientConfig,final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
- return new
SpringMvcClientBeanPostProcessor(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()),
shenyuClientRegisterRepository);
- }
- // 创建ContextRegisterListener,主要处理 URI信息
- @Bean
- public ContextRegisterListener contextRegisterListener(final
ShenyuClientConfig clientConfig) {
- return new
ContextRegisterListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()));
- }
+ // 创建SpringMvcClientEventListener,主要处理元数据和URI信息
+ @Bean
+ public SpringMvcClientEventListener springHttpClientEventListener(final
ShenyuClientConfig clientConfig,
+ final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
+ return new
SpringMvcClientEventListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()),
shenyuClientRegisterRepository);
+ }
}
```
@@ -215,7 +216,7 @@ public final class ShenyuClientRegisterRepositoryFactory {
加载类型通过`registerType`指定,也就是我们在配置文件中指定的类型:
-```xml
+```yaml
shenyu:
register:
registerType: http
@@ -226,20 +227,23 @@ shenyu:
我们指定的是`http`,所以会去加载`HttpClientRegisterRepository`。对象创建成功后,执行的初始化方法`init()`如下:
-```jav
+```java
@Join
public class HttpClientRegisterRepository implements
ShenyuClientRegisterRepository {
@Override
public void init(final ShenyuRegisterCenterConfig config) {
+ this.username = config.getProps().getProperty(Constants.USER_NAME);
+ this.password = config.getProps().getProperty(Constants.PASS_WORD);
this.serverList =
Lists.newArrayList(Splitter.on(",").split(config.getServerLists()));
+ this.setAccessToken();
}
// 暂时省略其他逻辑
}
```
-读取配置文件中的`serverLists`,即`sheenyu-admin`的地址,为后续数据发送做准备。类注解`@Join`用于`SPI`的加载。
+读取配置文件中的`username`、`password`和`serverLists`,即`sheenyu-admin`的访问账号、密码和地址信息,为后续数据发送做准备。类注解`@Join`用于`SPI`的加载。
@@ -248,90 +252,174 @@ public class HttpClientRegisterRepository implements
ShenyuClientRegisterReposit
> [shenyu-spi](https://github.com/apache/incubator-shenyu/tree/master/shenyu-spi)
> 是`Apache ShenYu`网关自定义的`SPI`扩展实现,设计和实现原理参考了`Dubbo`的
> [SPI扩展实现](https://dubbo.apache.org/zh/docs/v2.7/dev/impls/) 。
+#### 2.3 构建 元数据 和 URI信息 的 SpringMvcClientEventListener
+
+创建 `SpringMvcClientEventListener`,负责客户端 `元数据` 和 `URI` 数据的构建和注册,它的创建是在配置文件中完成。
+
+```java
+@Configuration
+@ImportAutoConfiguration(ShenyuClientCommonBeanConfiguration.class)
+@ConditionalOnProperty(value = "shenyu.register.enabled", matchIfMissing =
true, havingValue = "true")
+public class ShenyuSpringMvcClientConfiguration {
+ // ......
+
+ // 创建 SpringMvcClientEventListener
+ @Bean
+ public SpringMvcClientEventListener springHttpClientEventListener(final
ShenyuClientConfig clientConfig,
+ final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
+ return new
SpringMvcClientEventListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()),
shenyuClientRegisterRepository);
+ }
+}
+```
-#### 2.3 构建元数据的 SpringMvcClientBeanPostProcessor
+- `SpringMvcClientEventListener`继承了`AbstractContextRefreshedEventListener`
-创建`SpringMvcClientBeanPostProcessor`,负责元数据的构建和注册,它的构造函数逻辑如下:
+
+
+`AbstractContextRefreshedEventListener`是一个抽象类,它实现了`ApplicationListener`接口,并重写了`onApplicationEvent()`方法,当有Spring事件发生后,该方法会执行。它的实现目前有八种,每一种表示对应的RPC调用协议的
`元数据` 和`URI` 信息的注册。
+
+- `AlibabaDubboServiceBeanListener`:处理使用`Alibaba Dubbo`协议;
+- `ApacheDubboServiceBeanListener`:处理使用`Apacge Dubbo`协议;
+- `GrpcClientEventListener`:处理使用`grpc`协议;
+- `MotanServiceEventListener`:处理使用`Mortan`协议;
+- `SofaServiceEventListener`:处理使用`Sofa`协议;
+- `SpringMvcClientEventListener`:处理使用`http`协议;
+- `SpringWebSocketClientEventListener`:处理使用`websocket`协议;
+- `TarsServiceBeanEventListener`:处理使用`Tars`注册类型;
```java
+// 实现了ApplicationListener接口
+public abstract class AbstractContextRefreshedEventListener<T, A extends
Annotation> implements ApplicationListener<ContextRefreshedEvent> {
-/**
- * spring mvc 客户端bean的后置处理器
- */
-public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor {
+ //......
- /**
- * 通过构造函数进行实例化
- */
- public SpringMvcClientBeanPostProcessor(final PropertiesConfig
clientConfig,
- final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
- // 读取配置属性
+ //构造函数
+ public AbstractContextRefreshedEventListener(final PropertiesConfig
clientConfig,
+ final
ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
+ // 读取 shenyu.client.http 配置信息
Properties props = clientConfig.getProps();
- // 获取端口信息,并校验
- int port =
Integer.parseInt(props.getProperty(ShenyuClientConstants.PORT));
- if (port <= 0) {
- String errorMsg = "http register param must config the port must >
0";
- LOG.error(errorMsg);
- throw new ShenyuClientIllegalArgumentException(errorMsg);
- }
- // 获取appName
+ // appName 应用名称
this.appName = props.getProperty(ShenyuClientConstants.APP_NAME);
- // 获取contextPath
- this.contextPath =
props.getProperty(ShenyuClientConstants.CONTEXT_PATH);
- // 校验appName和contextPath
+ // contextPath上下文路径
+ this.contextPath =
Optional.ofNullable(props.getProperty(ShenyuClientConstants.CONTEXT_PATH)).map(UriUtils::repairData).orElse("");
if (StringUtils.isBlank(appName) && StringUtils.isBlank(contextPath)) {
- String errorMsg = "http register param must config the appName or
contextPath";
+ String errorMsg = "client register param must config the appName
or contextPath";
LOG.error(errorMsg);
throw new ShenyuClientIllegalArgumentException(errorMsg);
}
- // 获取 isFull
- this.isFull =
Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL,
Boolean.FALSE.toString()));
+ this.ipAndPort = props.getProperty(ShenyuClientConstants.IP_PORT);
+ // host信息
+ this.host = props.getProperty(ShenyuClientConstants.HOST);
+ // port 客户端端口信息
+ this.port = props.getProperty(ShenyuClientConstants.PORT);
// 开始事件发布
publisher.start(shenyuClientRegisterRepository);
}
- // 暂时省略了其他逻辑
+ // 当有上下文刷新事件ContextRefreshedEvent发生时,该方法会执行
@Override
- public Object postProcessAfterInitialization(@NonNull final Object bean,
@NonNull final String beanName) throws BeansException {
- // 暂时省略了其他逻辑
+ public void onApplicationEvent(@NonNull final ContextRefreshedEvent event)
{
+ //保证该方法的内容只执行一次
+ if (!registered.compareAndSet(false, true)) {
+ return;
+ }
+ final ApplicationContext context = event.getApplicationContext();
+ // 获取声明RPC调用的类
+ Map<String, T> beans = getBeans(context);
+ if (MapUtils.isEmpty(beans)) {
+ return;
+ }
+ // 构建URI数据并注册
+ publisher.publishEvent(buildURIRegisterDTO(context, beans));
+ // 构建元数据并注册
+ beans.forEach(this::handle);
}
-}
+ // 交给不同的子类实现
+ @SuppressWarnings("all")
+ protected abstract URIRegisterDTO buildURIRegisterDTO(ApplicationContext
context,
+ Map<String, T>
beans);
+
+
+ protected void handle(final String beanName, final T bean) {
+ Class<?> clazz = getCorrectedClass(bean);
+ //
获取当前bean的对应shenyu客户端的注解(对应不同的RPC调用注解不一样,像http的就是@ShenyuSpringMvcClient,而像SpringCloud的则是@ShenyuSpringCloudClient)
+ final A beanShenyuClient =
AnnotatedElementUtils.findMergedAnnotation(clazz, getAnnotationType());
+ // 根据bean获取对应的path(不同子类实现不一样)
+ final String superPath = buildApiSuperPath(clazz, beanShenyuClient);
+ // 如果包含Shenyu客户端注解或者path中包括'*',表示注册整个类的接口
+ if (Objects.nonNull(beanShenyuClient) && superPath.contains("*")) {
+ // 构建类的元数据,发送注册事件
+ handleClass(clazz, bean, beanShenyuClient, superPath);
+ return;
+ }
+ // 获取当前bean的所有方法
+ final Method[] methods =
ReflectionUtils.getUniqueDeclaredMethods(clazz);
+ // 遍历方法
+ for (Method method : methods) {
+ // 注册符合条件的方法
+ handleMethod(bean, clazz, beanShenyuClient, method, superPath);
+ }
+ }
-```
+ // 构建类元数据并注册的默认实现
+ protected void handleClass(final Class<?> clazz,
+ final T bean,
+ @NonNull final A beanShenyuClient,
+ final String superPath) {
+ publisher.publishEvent(buildMetaDataDTO(bean, beanShenyuClient,
pathJoin(contextPath, superPath), clazz, null));
+ }
+ // 构建方法元数据并注册的默认实现
+ protected void handleMethod(final T bean,
+ final Class<?> clazz,
+ @Nullable final A beanShenyuClient,
+ final Method method,
+ final String superPath) {
+ // 如果方法上有Shenyu客户端注解,就表示该方法需要注册
+ A methodShenyuClient =
AnnotatedElementUtils.findMergedAnnotation(method, getAnnotationType());
+ if (Objects.nonNull(methodShenyuClient)) {
+ // 构建元数据,发送注册事件
+ publisher.publishEvent(buildMetaDataDTO(bean, methodShenyuClient,
buildApiPath(method, superPath, methodShenyuClient), clazz, method));
+ }
+ }
+ // 交给不同子类实现
+ protected abstract MetaDataRegisterDTO buildMetaDataDTO(T bean,
+ @NonNull A
shenyuClient,
+ String path,
+ Class<?> clazz,
+ Method method);
+}
-在构造函数中,主要是读取属性信息,然后进行校验。
+```
+
+在构造函数中主要是读取属性配置。
-```xml
+```yaml
shenyu:
client:
http:
- props:
- contextPath: /http
- appName: http
- port: 8189
- isFull: false
+ props:
+ contextPath: /http
+ appName: http
+ port: 8189
+ isFull: false
```
-
-
最后,执行了`publisher.start()`,开始事件发布,为注册做准备。
- ShenyuClientRegisterEventPublisher
-`ShenyuClientRegisterEventPublisher`通过单例模式实现,主要是生成`元数据`和`URI`订阅器(后续用于数据发布),然后启动`Disruptor`队列。提供了一个共有方法`publishEvent()`,发布事件,向Disruptor队列发数据。
+`ShenyuClientRegisterEventPublisher`通过单例模式实现,主要是生成元数据和`URI`订阅器(后续用于数据发布),然后启动`Disruptor`队列。提供了一个共有方法`publishEvent()`,发布事件,向Disruptor队列发数据。
```java
public class ShenyuClientRegisterEventPublisher {
// 私有变量
private static final ShenyuClientRegisterEventPublisher INSTANCE = new
ShenyuClientRegisterEventPublisher();
-
- private DisruptorProviderManage providerManage;
-
- private RegisterClientExecutorFactory factory;
+
+ private DisruptorProviderManage<DataTypeParent> providerManage;
/**
* 公开静态方法
@@ -349,7 +437,7 @@ public class ShenyuClientRegisterEventPublisher {
*/
public void start(final ShenyuClientRegisterRepository
shenyuClientRegisterRepository) {
// 创建客户端注册工厂类
- factory = new RegisterClientExecutorFactory();
+ RegisterClientExecutorFactory factory = new
RegisterClientExecutorFactory();
// 添加元数据订阅器
factory.addSubscribers(new
ShenyuClientMetadataExecutorSubscriber(shenyuClientRegisterRepository));
// 添加URI订阅器
@@ -362,112 +450,122 @@ public class ShenyuClientRegisterEventPublisher {
/**
* 发布事件,向Disruptor队列发数据
*
- * @param <T> the type parameter
* @param data the data
*/
- public <T> void publishEvent(final T data) {
- DisruptorProvider<Object> provider = providerManage.getProvider();
- provider.onData(f -> f.setData(data));
+ public <T> void publishEvent(final DataTypeParent data) {
+ DisruptorProvider<DataTypeParent> provider =
providerManage.getProvider();
+ provider.onData(data);
}
}
```
-`SpringMvcClientBeanPostProcessor`的构造函数逻辑分析完了,主要是读取属性配置,创建元数据和`URI`订阅器,
启动`Disruptor`队列。要注意到它实现了`BeanPostProcessor`,这是`Spring`提供的一个接口,在`Bean`的生命周期中,真正开始使用之前,会执行后置处理器的`postProcessAfterInitialization()`方法。
+`AbstractContextRefreshedEventListener`的构造函数逻辑分析完成了,主要是读取属性配置,创建`元数据`和`URI`订阅器,启动`Disruptor`队列。
+
+`onApplicationEvent()`方法是有`Spring`事件发生时会执行,这里的参数是`ContextRefreshedEvent`,表示上下文刷新事件。当`Spring`容器就绪后执行此处逻辑:先构建`URI`数据并注册,再构建元数据并注册,
-- postProcessAfterInitialization() 方法
+>
`ContextRefreshedEvent`是`Spring`内置事件。`ApplicationContext`被初始化或刷新时,该事件被触发。这也可以在
`ConfigurableApplicationContext`接口中使用 `refresh()`
方法来发生。此处的初始化是指:所有的`Bean`被成功装载,后处理`Bean`被检测并激活,所有`Singleton Bean`
被预实例化,`ApplicationContext`容器已就绪可用。
-`SpringMvcClientBeanPostProcessor`作为一个后置处理器,它的功能是:读取注解中的元数据,并向`admin`注册。
+再来看`AbstractContextRefreshedEventListener`的http实现`SpringMvcClientEventListener`。
```java
-// 后置处理器
-public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor {
- // 省略了其他逻辑
+public class SpringMvcClientEventListener extends
AbstractContextRefreshedEventListener<Object, ShenyuSpringMvcClient> {
+
+ private final List<Class<? extends Annotation>> mappingAnnotation = new
ArrayList<>(3);
+
+ private final Boolean isFull;
+
+ private final String protocol;
+
+ // 构造函数
+ public SpringMvcClientEventListener(final PropertiesConfig clientConfig,
+ final ShenyuClientRegisterRepository
shenyuClientRegisterRepository) {
+ super(clientConfig, shenyuClientRegisterRepository);
+ Properties props = clientConfig.getProps();
+ // 获取 isFull
+ this.isFull =
Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL,
Boolean.FALSE.toString()));
+ // 表示是http协议的实现
+ this.protocol = props.getProperty(ShenyuClientConstants.PROTOCOL,
ShenyuClientConstants.HTTP);
+ mappingAnnotation.add(ShenyuSpringMvcClient.class);
+ mappingAnnotation.add(RequestMapping.class);
+ }
- // 后置处理器:读取注解中的元数据,并向admin注册
@Override
- public Object postProcessAfterInitialization(@NonNull final Object bean,
@NonNull final String beanName) throws BeansException {
+ protected Map<String, Object> getBeans(final ApplicationContext context) {
// 配置属性,如果 isFull=true 的话,表示注册整个微服务
- if (isFull) {
- return bean;
+ if (Boolean.TRUE.equals(isFull)) {
+ getPublisher().publishEvent(MetaDataRegisterDTO.builder()
+ .contextPath(getContextPath())
+ .appName(getAppName())
+ .path(PathUtils.decoratorPathWithSlash(getContextPath()))
+ .rpcType(RpcTypeEnum.HTTP.getName())
+ .enabled(true)
+ .ruleName(getContextPath())
+ .build());
+ return null;
}
- // 获取当前bean的Controller注解
- Controller controller =
AnnotationUtils.findAnnotation(bean.getClass(), Controller.class);
- // 获取当前bean的RequestMapping注解
- RequestMapping requestMapping =
AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
- // 如果这个bean是一个接口
- if (controller != null || requestMapping != null) {
- // 获取当前bean的 ShenyuSpringMvcClient 注解
- ShenyuSpringMvcClient clazzAnnotation =
AnnotationUtils.findAnnotation(bean.getClass(), ShenyuSpringMvcClient.class);
- String prePath = "";
- //如果没有 ShenyuSpringMvcClient 注解,就返回,表示这个接口不需要注册
- if (Objects.isNull(clazzAnnotation)) {
- return bean;
- }
- //如果 ShenyuSpringMvcClient 注解中的path属性包括 * ,表示注册整个接口
- if (clazzAnnotation.path().indexOf("*") > 1) {
- // 构建元数据,发送注册事件
- publisher.publishEvent(buildMetaDataDTO(clazzAnnotation,
prePath));
- return bean;
- }
-
- prePath = clazzAnnotation.path();
- // 获取当前bean的所有方法
- final Method[] methods =
ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
- // 遍历方法
- for (Method method : methods) {
- // 获取当前方法上的注解 ShenyuSpringMvcClient
- ShenyuSpringMvcClient shenyuSpringMvcClient =
AnnotationUtils.findAnnotation(method, ShenyuSpringMvcClient.class);
- // 如果方法上有注解ShenyuSpringMvcClient,就表示该方法需要注册
- if (Objects.nonNull(shenyuSpringMvcClient)) {
- // 构建元数据,发送注册事件
-
publisher.publishEvent(buildMetaDataDTO(shenyuSpringMvcClient, prePath));
- }
- }
+ // 否则获取带Controller注解的bean
+ return context.getBeansWithAnnotation(Controller.class);
+ }
+
+ // 构造URI数据
+ @Override
+ protected URIRegisterDTO buildURIRegisterDTO(final ApplicationContext
context,
+ final Map<String, Object>
beans) {
+ // ...
+ }
+
+ @Override
+ protected String buildApiSuperPath(final Class<?> clazz, @Nullable final
ShenyuSpringMvcClient beanShenyuClient) {
+ // 如果有带上Shenyu客户端注解,则优先取注解中的不为空的path属性
+ if (Objects.nonNull(beanShenyuClient) &&
StringUtils.isNotBlank(beanShenyuClient.path())) {
+ return beanShenyuClient.path();
}
- return bean;
+ // 如果有带上RequestMapping注解,且path属性不为空,则返回path数组的第一个值
+ RequestMapping requestMapping = AnnotationUtils.findAnnotation(clazz,
RequestMapping.class);
+ if (Objects.nonNull(requestMapping) &&
ArrayUtils.isNotEmpty(requestMapping.path()) &&
StringUtils.isNotBlank(requestMapping.path()[0])) {
+ return requestMapping.path()[0];
+ }
+ return "";
}
-
- // 构造元数据
- private MetaDataRegisterDTO buildMetaDataDTO(final ShenyuSpringMvcClient
shenyuSpringMvcClient, final String prePath) {
- // contextPath上下文名称
- String contextPath = this.contextPath;
- // appName应用名称
- String appName = this.appName;
- // path注册路径
- String path;
- if (StringUtils.isEmpty(contextPath)) {
- path = prePath + shenyuSpringMvcClient.path();
- } else {
- path = contextPath + prePath + shenyuSpringMvcClient.path();
+
+ // 声明http实现的客户端注解是ShenyuSpringMvcClient
+ @Override
+ protected Class<ShenyuSpringMvcClient> getAnnotationType() {
+ return ShenyuSpringMvcClient.class;
+ }
+
+ @Override
+ protected void handleMethod(final Object bean, final Class<?> clazz,
+ @Nullable final ShenyuSpringMvcClient
beanShenyuClient,
+ final Method method, final String superPath) {
+ // 获取当前bean的RequestMapping注解
+ final RequestMapping requestMapping =
AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
+ // 获取当前bean的 ShenyuSpringMvcClient 注解
+ ShenyuSpringMvcClient methodShenyuClient =
AnnotatedElementUtils.findMergedAnnotation(method, ShenyuSpringMvcClient.class);
+ methodShenyuClient = Objects.isNull(methodShenyuClient) ?
beanShenyuClient : methodShenyuClient;
+ //如果有 ShenyuSpringMvcClient 注解并且包含RequestMapping注解(表示是一个接口),则进行注册
+ if (Objects.nonNull(methodShenyuClient) &&
Objects.nonNull(requestMapping)) {
+ getPublisher().publishEvent(buildMetaDataDTO(bean,
methodShenyuClient, buildApiPath(method, superPath, methodShenyuClient), clazz,
method));
}
- // desc描述信息
- String desc = shenyuSpringMvcClient.desc();
- // ruleName规则名称,没有填写的话就和path一致
- String configRuleName = shenyuSpringMvcClient.ruleName();
- String ruleName = StringUtils.isBlank(configRuleName) ? path :
configRuleName;
- // 构建元数据
- return MetaDataRegisterDTO.builder()
- .contextPath(contextPath)
- .appName(appName)
- .path(path)
- .pathDesc(desc)
- .rpcType(RpcTypeEnum.HTTP.getName())
- .enabled(shenyuSpringMvcClient.enabled())
- .ruleName(ruleName)
- .registerMetaData(shenyuSpringMvcClient.registerMetaData())
- .build();
+ }
+
+ //...
+
+ // 构造元数据
+ @Override
+ protected MetaDataRegisterDTO buildMetaDataDTO(final Object bean,
+ @NonNull final
ShenyuSpringMvcClient shenyuClient,
+ final String path, final
Class<?> clazz,
+ final Method method) {
+ //...
}
}
-
```
-
-在后置处理器中,需要读取配置属性,如果 `isFull=true`
的话,表示注册整个微服务。获取当前`bean`的`Controller`注解、`RequestMapping`注解、`ShenyuSpringMvcClient`
注解,通过读取这些注解信息判断当前`bean`是否是接口?接口是否需要注册?方法是否需要注册?然后根据`ShenyuSpringMvcClient`
注解中的属性构建元数据,最后通过`publisher.publishEvent()`发布事件进行注册。
-
-
+注册逻辑都是通过 `publisher.publishEvent()`完成。
`Controller`注解和`RequestMapping`注解是由`Spring`提供的,这个大家应该很熟悉,不过多赘述。`ShenyuSpringMvcClient`
注解是由`Apache ShenYu`提供的,用于注册`SpringMvc`客户端,它的定义如下:
@@ -479,7 +577,13 @@ public class SpringMvcClientBeanPostProcessor implements
BeanPostProcessor {
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ShenyuSpringMvcClient {
+
+ // path 注册路径
+ @AliasFor(attribute = "path")
+ String value() default "";
+
// path 注册路径
+ @AliasFor(attribute = "value")
String path();
// ruleName 规则名称
@@ -530,6 +634,7 @@ public class OrderController {
orderDTO.setName("hello world save order");
return orderDTO;
}
+}
```
@@ -550,7 +655,7 @@ public class OrderController {
package com.lmax.disruptor;
public interface WorkHandler<T> {
- void onEvent(T var1) throws Exception;
+ void onEvent(T event) throws Exception;
}
```
@@ -569,6 +674,8 @@ public class QueueConsumer<T> implements
WorkHandler<DataEvent<T>> {
@Override
public void onEvent(final DataEvent<T> t) {
if (t != null) {
+ // 根据事件类型使用不同的线程池
+ ThreadPoolExecutor executor = orderly(t);
// 通过工厂创建队列消费任务
QueueConsumerExecutor<T> queueConsumerExecutor = factory.create();
// 保存数据
@@ -599,16 +706,16 @@ public class QueueConsumer<T> implements
WorkHandler<DataEvent<T>> {
```java
-public final class RegisterClientConsumerExecutor extends
QueueConsumerExecutor<DataTypeParent> {
+public final class RegisterClientConsumerExecutor<T extends DataTypeParent>
extends QueueConsumerExecutor<T> {
//......
@Override
public void run() {
// 获取数据
- DataTypeParent dataTypeParent = getData();
+ final T data = getData();
// 根据数据类型调用相应的处理器进行处理
-
subscribers.get(dataTypeParent.getType()).executor(Lists.newArrayList(dataTypeParent));
+ subscribers.get(data.getType()).executor(Lists.newArrayList(data));
}
}
@@ -636,6 +743,8 @@ public enum DataType {

+先看元数据处理
+
- ShenyuClientMetadataExecutorSubscriber#executor()
@@ -708,46 +817,110 @@ public final class ShenyuClientRegisterRepositoryFactory
{
-本文的源码分析是基于`Http`的方式进行注册,所以我们先分析`HttpClientRegisterRepository`,其他的注册方式后续再分析。
+本文的源码分析是基于`Http`的方式进行注册,所以我们先分析`HttpClientRegisterRepository`,其他的注册方式后续再分析。`HttpClientRegisterRepository`继承了`FailbackRegistryRepository`,而`FailbackRegistryRepository`本身主要用于对`Http`注册过程中的失败异常的处理,这里就省略了。
通过`http`的方式注册很简单,就是调用工具类发送`http`请求。注册元数据和URI都是调用的同一个方法`doRegister()`,指定接口和类型就好。
-- `/shenyu-client/register-metadata`:服务端提供的接口用于注册元数据。
-- `/shenyu-client/register-uri`: 服务端提供的接口用于注册URI。
+- `Constants.URI_PATH`的值`/shenyu-client/register-metadata`:服务端提供的接口用于注册元数据。
+- `Constants.META_PATH`的值`/shenyu-client/register-uri`: 服务端提供的接口用于注册URI。
```java
@Join
-public class HttpClientRegisterRepository implements
ShenyuClientRegisterRepository {
- // 服务端提供的接口用于注册元数据
- private static final String META_PATH = "/shenyu-client/register-metadata";
+public class HttpClientRegisterRepository extends FailbackRegistryRepository {
- // 服务端提供的接口用于注册URI
- private static final String URI_PATH = "/shenyu-client/register-uri";
+ private static final Logger LOGGER =
LoggerFactory.getLogger(HttpClientRegisterRepository.class);
- //注册URI
- @Override
- public void persistURI(final URIRegisterDTO registerDTO) {
- doRegister(registerDTO, URI_PATH, Constants.URI);
+ private static URIRegisterDTO uriRegisterDTO;
+
+ private String username;
+
+ private String password;
+
+ private List<String> serverList;
+
+ private String accessToken;
+
+ public HttpClientRegisterRepository() {
}
- //注册接口(就是元数据信息)
+ public HttpClientRegisterRepository(final ShenyuRegisterCenterConfig
config) {
+ init(config);
+ }
+
@Override
- public void persistInterface(final MetaDataRegisterDTO metadata) {
- doRegister(metadata, META_PATH, META_TYPE);
+ public void init(final ShenyuRegisterCenterConfig config) {
+ // admin的用户名
+ this.username = config.getProps().getProperty(Constants.USER_NAME);
+ // admin的用户名对应的密码
+ this.password = config.getProps().getProperty(Constants.PASS_WORD);
+ // admin服务列表
+ this.serverList =
Lists.newArrayList(Splitter.on(",").split(config.getServerLists()));
+ // 设置访问的token
+ this.setAccessToken();
}
-
- // 进行注册
+
+ /**
+ * Persist uri.
+ *
+ * @param registerDTO the register dto
+ */
+ @Override
+ public void doPersistURI(final URIRegisterDTO registerDTO) {
+ if (RuntimeUtils.listenByOther(registerDTO.getPort())) {
+ return;
+ }
+ doRegister(registerDTO, Constants.URI_PATH, Constants.URI);
+ uriRegisterDTO = registerDTO;
+ }
+
+ @Override
+ public void doPersistInterface(final MetaDataRegisterDTO metadata) {
+ doRegister(metadata, Constants.META_PATH, Constants.META_TYPE);
+ }
+
+ @Override
+ public void close() {
+ if (uriRegisterDTO != null) {
+ uriRegisterDTO.setEventType(EventType.DELETED);
+ doRegister(uriRegisterDTO, Constants.URI_PATH, Constants.URI);
+ }
+ }
+
+ private void setAccessToken() {
+ for (String server : serverList) {
+ try {
+ Optional<?> login = RegisterUtils.doLogin(username, password,
server.concat(Constants.LOGIN_PATH));
+ login.ifPresent(v -> this.accessToken = String.valueOf(v));
+ } catch (Exception e) {
+ LOGGER.error("Login admin url :{} is fail, will retry. cause:
{} ", server, e.getMessage());
+ }
+ }
+ }
+
private <T> void doRegister(final T t, final String path, final String
type) {
+ int i = 0;
// 遍历admin服务列表(admin可能是集群)
for (String server : serverList) {
+ i++;
+ String concat = server.concat(path);
try {
+ // 设置访问token
+ if (StringUtils.isBlank(accessToken)) {
+ this.setAccessToken();
+ if (StringUtils.isBlank(accessToken)) {
+ throw new NullPointerException("accessToken is null");
+ }
+ }
// 调用工具类发送 http 请求
- RegisterUtils.doRegister(GsonUtils.getInstance().toJson(t),
server + path, type);
+ RegisterUtils.doRegister(GsonUtils.getInstance().toJson(t),
concat, type, accessToken);
return;
} catch (Exception e) {
- LOGGER.error("register admin url :{} is fail, will retry",
server);
+ LOGGER.error("Register admin url :{} is fail, will retry.
cause:{}", server, e.getMessage());
+ if (i == serverList.size()) {
+ throw new RuntimeException(e);
+ }
}
}
}
@@ -767,7 +940,12 @@ public final class RegisterUtils {
// 通过OkHttp发送数据
public static void doRegister(final String json, final String url, final
String type) throws IOException {
- String result = OkHttpTools.getInstance().post(url, json);
+ if (!StringUtils.hasLength(accessToken)) {
+ LOGGER.error("{} client register error accessToken is null, please
check the config : {} ", type, json);
+ return;
+ }
+ Headers headers = new Headers.Builder().add(Constants.X_ACCESS_TOKEN,
accessToken).build();
+ String result = OkHttpTools.getInstance().post(url, json, headers);
if (Objects.equals(SUCCESS, result)) {
LOGGER.info("{} client register success: {} ", type, json);
} else {
@@ -782,141 +960,7 @@ public final class RegisterUtils {
至此,客户端通过`http`的方式注册元数据的逻辑就分析完了。小结一下:通过读取自定义的注解信息构造元数据,将数据发到`Disruptor`队列,然后从队列中消费数据,将消费者放到线程池中去执行,最终通过发送`http`请求到`admin`。
-
-客户端元数据注册流程的源码分析过程完成了,用流程图描述如下:
-
-
-
-
-
-
-
-#### 2.4 构建 URI 的 ContextRegisterListener
-
-创建 `ContextRegisterListener`,负责客户端`URI`数据的构建和注册,它的创建是在配置文件中完成。
-
-```java
-@Configuration
-@ImportAutoConfiguration(ShenyuClientCommonBeanConfiguration.class)
-public class ShenyuSpringMvcClientConfiguration {
- // ......
-
- // 创建 ContextRegisterListener
- @Bean
- public ContextRegisterListener contextRegisterListener(final
ShenyuClientConfig clientConfig) {
- return new
ContextRegisterListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()));
- }
-}
-```
-
-
-
-`ContextRegisterListener`实现了`ApplicationListener`接口,并重写了`onApplicationEvent()`方法,当有Spring事件发生后,该方法会执行。
-
-```java
-// 实现了ApplicationListener接口
-public class ContextRegisterListener implements
ApplicationListener<ContextRefreshedEvent> {
-
- //......
-
- //通过构造函数完成实例化
- public ContextRegisterListener(final PropertiesConfig clientConfig) {
- // 读取 shenyu.client.http 配置信息
- Properties props = clientConfig.getProps();
- // isFull是否注册整个服务
- this.isFull =
Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL,
Boolean.FALSE.toString()));
- // contextPath上下文路径
- String contextPath =
props.getProperty(ShenyuClientConstants.CONTEXT_PATH);
- this.contextPath = contextPath;
- if (isFull) {
- if (StringUtils.isBlank(contextPath)) {
- String errorMsg = "http register param must config the
contextPath";
- LOG.error(errorMsg);
- throw new ShenyuClientIllegalArgumentException(errorMsg);
- }
- this.contextPath = contextPath + "/**";
- }
- // port 客户端端口信息
- int port =
Integer.parseInt(props.getProperty(ShenyuClientConstants.PORT));
- // appName 应用名称
- this.appName = props.getProperty(ShenyuClientConstants.APP_NAME);
- // host信息
- this.host = props.getProperty(ShenyuClientConstants.HOST);
- this.port = port;
- }
-
- // 当有上下文刷新事件ContextRefreshedEvent发生时,该方法会执行
- @Override
- public void onApplicationEvent(@NonNull final ContextRefreshedEvent
contextRefreshedEvent) {
- //保证该方法的内容只执行一次
- if (!registered.compareAndSet(false, true)) {
- return;
- }
- // 如果是 isFull=true 代表注册整个服务,构建元数据并注册
- if (isFull) {
- publisher.publishEvent(buildMetaDataDTO());
- }
-
- // 构建URI数据并注册
- publisher.publishEvent(buildURIRegisterDTO());
- }
-
- // 构建URI数据
- private URIRegisterDTO buildURIRegisterDTO() {
- String host = IpUtils.isCompleteHost(this.host) ? this.host :
IpUtils.getHost(this.host);
- return URIRegisterDTO.builder()
- .contextPath(this.contextPath)
- .appName(appName)
- .host(host)
- .port(port)
- .rpcType(RpcTypeEnum.HTTP.getName())
- .build();
- }
-
- // 构建元数据
- private MetaDataRegisterDTO buildMetaDataDTO() {
- String contextPath = this.contextPath;
- String appName = this.appName;
- return MetaDataRegisterDTO.builder()
- .contextPath(contextPath)
- .appName(appName)
- .path(contextPath)
- .rpcType(RpcTypeEnum.HTTP.getName())
- .enabled(true)
- .ruleName(contextPath)
- .build();
- }
-}
-
-```
-
-在构造函数中主要是读取属性配置。
-
-
-
-`onApplicationEvent()`方法是有`Spring`事件发生时会执行,这里的参数是`ContextRefreshedEvent`,表示上下文刷新事件。当`Spring`容器就绪后执行此处逻辑:如果是
`isFull=true`
代表注册整个服务,构建元数据并注册,在前面分析的后置处理器`SpringMvcClientBeanPostProcessor`中没有处理
`isFull=true` 的情况,所以在此处进行了处理。然后再构建`URI`数据并注册。
-
-
-
->
`ContextRefreshedEvent`是`Spring`内置事件。`ApplicationContext`被初始化或刷新时,该事件被触发。这也可以在
`ConfigurableApplicationContext`接口中使用 `refresh()`
方法来发生。此处的初始化是指:所有的`Bean`被成功装载,后处理`Bean`被检测并激活,所有`Singleton Bean`
被预实例化,`ApplicationContext`容器已就绪可用。
-
-
-
-
-
-注册逻辑都是通过
`publisher.publishEvent()`完成。在前面都已经分析过了:向`Disruptor`队列写入数据,再从中消费数据,最后通过`ExecutorSubscriber`去处理。
-
-
-
-- ExecutorSubscriber#executor()
-
-执行器订阅者分为两类,一个是处理元数据,一个是处理`URI`。在客户端和服务端分别有两个,所以一共是四个。
-
-
-
-这里是注册`URI`信息,所以执行类是`ShenyuClientURIExecutorSubscriber`。
-
-
+再来看看 `URI` 数据的处理
- ShenyuClientURIExecutorSubscriber#executor()
@@ -985,11 +1029,11 @@ public class ShenyuClientURIExecutorSubscriber
implements ExecutorTypeSubscriber
-客户端`URI`注册流程的源码分析完成了,流程图如下:
+客户端`元数据`和`URI`注册流程的源码分析完成了,流程图如下:
-
+
@@ -997,7 +1041,7 @@ public class ShenyuClientURIExecutorSubscriber implements
ExecutorTypeSubscriber
-#### 3.1 注册接口ShenyuHttpRegistryController
+#### 3.1 注册接口ShenyuClientHttpRegistryController
从前面的分析可以知道,服务端提供了注册的两个接口:
@@ -1006,26 +1050,31 @@ public class ShenyuClientURIExecutorSubscriber
implements ExecutorTypeSubscriber
-这两个接口位于`ShenyuHttpRegistryController`中,它实现了`ShenyuServerRegisterRepository`接口,是服务端注册的实现类。它用`@Join`标记,表示通过`SPI`进行加载。
+这两个接口位于`ShenyuClientHttpRegistryController`中,它实现了`ShenyuClientServerRegisterRepository`接口,是服务端注册的实现类。它用`@Join`标记,表示通过`SPI`进行加载。
```java
// shenuyu客户端接口
@RequestMapping("/shenyu-client")
@Join
-public class ShenyuHttpRegistryController implements
ShenyuServerRegisterRepository {
+public class ShenyuClientHttpRegistryController implements
ShenyuClientServerRegisterRepository {
- private ShenyuServerRegisterPublisher publisher;
+ private ShenyuClientServerRegisterPublisher publisher;
@Override
- public void init(final ShenyuServerRegisterPublisher publisher, final
ShenyuRegisterCenterConfig config) {
+ public void init(final ShenyuClientServerRegisterPublisher publisher,
final ShenyuRegisterCenterConfig config) {
this.publisher = publisher;
}
+
+ @Override
+ public void close() {
+ publisher.close();
+ }
// 注册元数据
@PostMapping("/register-metadata")
@ResponseBody
public String registerMetadata(@RequestBody final MetaDataRegisterDTO
metaDataRegisterDTO) {
- publish(metaDataRegisterDTO);
+ publisher.publish(metaDataRegisterDTO);
return ShenyuResultMessage.SUCCESS;
}
@@ -1033,33 +1082,28 @@ public class ShenyuHttpRegistryController implements
ShenyuServerRegisterReposit
@PostMapping("/register-uri")
@ResponseBody
public String registerURI(@RequestBody final URIRegisterDTO
uriRegisterDTO) {
- publish(uriRegisterDTO);
+ publisher.publish(uriRegisterDTO);
return ShenyuResultMessage.SUCCESS;
}
-
- // 发布注册事件
- private <T> void publish(final T t) {
- publisher.publish(Collections.singletonList(t));
- }
}
```
-两个注册接口获取到数据好,就调用了`publish()`方法,把数据发布到`Disruptor`队列中。
+两个注册接口获取到数据好,就调用了`publisher.publish()`方法,把数据发布到`Disruptor`队列中。
-- `ShenyuServerRegisterRepository`接口
+- `ShenyuClientServerRegisterRepository`接口
-`ShenyuServerRegisterRepository`接口是服务注册接口,它有五个实现类,表示有五种注册方式:
+`ShenyuClientServerRegisterRepository`接口是服务注册接口,它有五个实现类,表示有五种注册方式:
-- `ConsulServerRegisterRepository`:通过`Consul`实现注册;
-- `EtcdServerRegisterRepository`:通过`Etcd`实现注册;
-- `NacosServerRegisterRepository`:通过`Nacos`实现注册;
-- `ShenyuHttpRegistryController`:通过`Http`实现注册;
-- `ZookeeperServerRegisterRepository`:通过`Zookeeper`实现注册。
+- `ConsulClientServerRegisterRepository`:通过`Consul`实现注册;
+- `EtcdClientServerRegisterRepository`:通过`Etcd`实现注册;
+- `NacosClientServerRegisterRepository`:通过`Nacos`实现注册;
+- `ShenyuClientHttpRegistryController`:通过`Http`实现注册;
+- `ZookeeperClientServerRegisterRepository`:通过`Zookeeper`实现注册。
@@ -1067,13 +1111,13 @@ public class ShenyuHttpRegistryController implements
ShenyuServerRegisterReposit
-在`shenyu-admin`中的`application.yml`文件中配置注册方式,`registerType`指定注册类型,当用`http`进行注册时,`serverLists`不需要填写,更多配置说明可以参考官网
[客户端接入配置](https://shenyu.apache.org/zh/docs/user-guide/register-center-access) 。
+在`shenyu-admin`中的`application.yml`文件中配置注册方式,`registerType`指定注册类型,当用`http`进行注册时,`serverLists`不需要填写,更多配置说明可以参考官网
[客户端接入配置](https://shenyu.apache.org/zh/docs/user-guide/property-config/register-center-access)
。
-```java
+```yaml
shenyu:
register:
registerType: http
- serverLists:
+ serverLists:
```
@@ -1094,14 +1138,14 @@ public class RegisterCenterConfiguration {
}
//创建ShenyuServerRegisterRepository,用于服务端注册
- @Bean
+ @Bean(destroyMethod = "close")
public ShenyuServerRegisterRepository shenyuServerRegisterRepository(final
ShenyuRegisterCenterConfig shenyuRegisterCenterConfig, final
List<ShenyuClientRegisterService> shenyuClientRegisterService) {
// 1.从配置属性中获取注册类型
String registerType = shenyuRegisterCenterConfig.getRegisterType();
// 2.通过注册类型,以SPI的方法加载实现类
- ShenyuServerRegisterRepository registerRepository =
ExtensionLoader.getExtensionLoader(ShenyuServerRegisterRepository.class).getJoin(registerType);
+ ShenyuClientServerRegisterRepository registerRepository =
ExtensionLoader.getExtensionLoader(ShenyuClientServerRegisterRepository.class).getJoin(registerType);
// 3.获取publisher,向Disruptor队列中写数据
- RegisterServerDisruptorPublisher publisher =
RegisterServerDisruptorPublisher.getInstance();
+ RegisterClientServerDisruptorPublisher publisher =
RegisterClientServerDisruptorPublisher.getInstance();
// 4.注册Service, rpcType -> registerService
Map<String, ShenyuClientRegisterService> registerServiceMap =
shenyuClientRegisterService.stream().collect(Collectors.toMap(ShenyuClientRegisterService::rpcType,
e -> e));
// 5.事件发布的准备工作
@@ -1118,14 +1162,14 @@ public class RegisterCenterConfiguration {
- `shenyuRegisterCenterConfig`:读取属性配置;
-- `shenyuServerRegisterRepository`:用于服务端注册。
+- `shenyuClientServerRegisterRepository`:用于服务端注册。
-在创建`shenyuServerRegisterRepository`的过程中,也进行了一系列的准备工作:
+在创建`shenyuClientServerRegisterRepository`的过程中,也进行了一系列的准备工作:
- 1.从配置属性中获取注册类型。
-- 2.通过注册类型,以`SPI`的方法加载实现类:比如指定的类型是`http`,就会加载`ShenyuHttpRegistryController`。
+-
2.通过注册类型,以`SPI`的方法加载实现类:比如指定的类型是`http`,就会加载`ShenyuClientHttpRegistryController`。
- 3.获取`publisher`,向`Disruptor`队列中写数据。
- 4.注册`Service`, `rpcType ->
registerService`:获取注册的`Service`,每种`rpc`都有对应的`Service`。本文的客户端构建是通过`springboot`,属于`http`类型,还有其他客户端类型:`dubbo`,`Spring
Cloud`,`gRPC`等。
- 5.事件发布的准备工作:添加服务端元数据和`URI`订阅器,处理数据。并且启动`Disruptor`队列。
@@ -1138,9 +1182,11 @@ public class RegisterCenterConfiguration {
```java
-public class RegisterServerDisruptorPublisher implements
ShenyuServerRegisterPublisher {
+public class RegisterClientServerDisruptorPublisher implements
ShenyuClientServerRegisterPublisher {
//私有属性
- private static final RegisterServerDisruptorPublisher INSTANCE = new
RegisterServerDisruptorPublisher();
+ private static final RegisterClientServerDisruptorPublisher INSTANCE = new
RegisterClientServerDisruptorPublisher();
+
+ private DisruptorProviderManage<Collection<DataTypeParent>> providerManage;
//公开静态方法获取实例
public static RegisterServerDisruptorPublisher getInstance() {
@@ -1150,7 +1196,7 @@ public class RegisterServerDisruptorPublisher implements
ShenyuServerRegisterPub
//事件发布的准备工作,添加服务端元数据和URI订阅器,处理数据。并且启动Disruptor队列。
public void start(final Map<String, ShenyuClientRegisterService>
shenyuClientRegisterService) {
//服务端注册工厂
- factory = new RegisterServerExecutorFactory();
+ RegisterServerExecutorFactory factory = new
RegisterServerExecutorFactory();
//添加URI数据订阅器
factory.addSubscribers(new
URIRegisterExecutorSubscriber(shenyuClientRegisterService));
//添加元数据订阅器
@@ -1162,9 +1208,16 @@ public class RegisterServerDisruptorPublisher implements
ShenyuServerRegisterPub
// 向队列中写入数据
@Override
- public <T> void publish(final T data) {
- DisruptorProvider<Object> provider = providerManage.getProvider();
- provider.onData(f -> f.setData(data));
+ public void publish(final DataTypeParent data) {
+ DisruptorProvider<Collection<DataTypeParent>> provider =
providerManage.getProvider();
+ provider.onData(Collections.singleton(data));
+ }
+
+ // 批量向队列中写入数据
+ @Override
+ public void publish(final Collection<? extends DataTypeParent> dataList) {
+ DisruptorProvider<Collection<DataTypeParent>> provider =
providerManage.getProvider();
+
provider.onData(dataList.stream().map(DataTypeParent.class::cast).collect(Collectors.toList()));
}
@Override
@@ -1198,7 +1251,7 @@ public class RegisterServerDisruptorPublisher implements
ShenyuServerRegisterPub
package com.lmax.disruptor;
public interface WorkHandler<T> {
- void onEvent(T var1) throws Exception;
+ void onEvent(T event) throws Exception;
}
```
@@ -1216,6 +1269,8 @@ public class QueueConsumer<T> implements
WorkHandler<DataEvent<T>> {
@Override
public void onEvent(final DataEvent<T> t) {
if (t != null) {
+ // 根据事件类型获取相应的线程池
+ ThreadPoolExecutor executor = orderly(t);
// 通过工厂创建队列消费任务
QueueConsumerExecutor<T> queueConsumerExecutor = factory.create();
// 保存数据
@@ -1245,26 +1300,27 @@ public class QueueConsumer<T> implements
WorkHandler<DataEvent<T>> {
```java
-public final class RegisterServerConsumerExecutor extends
QueueConsumerExecutor<List<DataTypeParent>> {
+public final class RegisterServerConsumerExecutor extends
QueueConsumerExecutor<Collection<DataTypeParent>> {
// ...
@Override
public void run() {
//获取从disruptor队列中拿到的数据
- List<DataTypeParent> results = getData();
- // 数据校验
- results = results.stream().filter(data ->
isValidData(data)).collect(Collectors.toList());
+ Collection<DataTypeParent> results = getData()
+ .stream()
+ .filter(this::isValidData)
+ .collect(Collectors.toList());
if (CollectionUtils.isEmpty(results)) {
return;
}
//根据类型执行操作
- getType(results).executor(results);
+ selectExecutor(results).executor(results);
}
-
+
// 根据类型获取订阅者
- private ExecutorSubscriber getType(final List<DataTypeParent> list) {
- DataTypeParent result = list.get(0);
- return subscribers.get(result.getType());
+ private ExecutorSubscriber<DataTypeParent> selectExecutor(final
Collection<DataTypeParent> list) {
+ final Optional<DataTypeParent> first = list.stream().findFirst();
+ return subscribers.get(first.orElseThrow(() -> new
RuntimeException("the data type is not found")).getType());
}
}
@@ -1297,15 +1353,15 @@ public class MetadataExecutorSubscriber implements
ExecutorTypeSubscriber<MetaDa
@Override
public void executor(final Collection<MetaDataRegisterDTO>
metaDataRegisterDTOList) {
// 遍历元数据列表
- for (MetaDataRegisterDTO metaDataRegisterDTO :
metaDataRegisterDTOList) {
- // 根据类型获取注册Service
- ShenyuClientRegisterService shenyuClientRegisterService =
this.shenyuClientRegisterService.get(metaDataRegisterDTO.getRpcType());
- Objects.requireNonNull(shenyuClientRegisterService);
- // 对元数据进行注册,加锁确保顺序执行,防止并发错误
- synchronized (ShenyuClientRegisterService.class) {
- shenyuClientRegisterService.register(metaDataRegisterDTO);
- }
- }
+ metaDataRegisterDTOList.forEach(meta -> {
+
Optional.ofNullable(this.shenyuClientRegisterService.get(meta.getRpcType())) //
根据类型获取注册Service
+ .ifPresent(shenyuClientRegisterService -> {
+ // 对元数据进行注册,加锁确保顺序执行,防止并发错误
+ synchronized (shenyuClientRegisterService) {
+ shenyuClientRegisterService.register(meta);
+ }
+ });
+ });
}
}
```
@@ -1331,16 +1387,21 @@ public class URIRegisterExecutorSubscriber implements
ExecutorTypeSubscriber<URI
if (CollectionUtils.isEmpty(dataList)) {
return;
}
- // 构建URI数据类型,通过registerURI方法实现注册
- findService(dataList).ifPresent(service -> {
- Map<String, List<URIRegisterDTO>> listMap = buildData(dataList);
- listMap.forEach(service::registerURI);
- });
- }
-
- // 根据类型查找Service
- private Optional<ShenyuClientRegisterService> findService(final
Collection<URIRegisterDTO> dataList) {
- return dataList.stream().map(dto ->
shenyuClientRegisterService.get(dto.getRpcType())).findFirst();
+ // 根据rpc调用类型聚集数据
+ final Map<String, List<URIRegisterDTO>> groupByRpcType =
dataList.stream()
+ .filter(data -> StringUtils.isNotBlank(data.getRpcType()))
+ .collect(Collectors.groupingBy(URIRegisterDTO::getRpcType));
+ for (Map.Entry<String, List<URIRegisterDTO>> entry :
groupByRpcType.entrySet()) {
+ final String rpcType = entry.getKey();
+ // 根据类型查找Service
+ Optional.ofNullable(shenyuClientRegisterService.get(rpcType))
+ .ifPresent(service -> {
+ final List<URIRegisterDTO> list = entry.getValue();
+ // 构建URI数据类型,通过registerURI方法实现注册
+ Map<String, List<URIRegisterDTO>> listMap =
buildData(list);
+ listMap.forEach(service::registerURI);
+ });
+ }
}
}
@@ -1365,6 +1426,7 @@ public class URIRegisterExecutorSubscriber implements
ExecutorTypeSubscriber<URI
- `ShenyuClientRegisterSofaServiceImpl`:`Sofa`类,处理`Sofa`注册类型;
-
`ShenyuClientRegisterSpringCloudServiceImpl`:`SpringCloud`类,处理`SpringCloud`注册类型;
- `ShenyuClientRegisterTarsServiceImpl`:`Tars`类,处理`Tars`注册类型;
+- `ShenyuClientRegisterWebSocketServiceImpl`:`Websocket`类,处理`Websocket`注册类型;
@@ -1373,7 +1435,11 @@ public class URIRegisterExecutorSubscriber implements
ExecutorTypeSubscriber<URI
- register(): 注册元数据
```java
-public String register(final MetaDataRegisterDTO dto) {
+public abstract class AbstractShenyuClientRegisterServiceImpl extends
FallbackShenyuClientRegisterService implements ShenyuClientRegisterService {
+
+ //......
+
+ public String register(final MetaDataRegisterDTO dto) {
// 1.注册选择器信息
String selectorHandler = selectorHandler(dto);
String selectorId = selectorService.registerDefault(dto,
PluginNameAdapter.rpcTypeAdapter(rpcType()), selectorHandler);
@@ -1390,6 +1456,7 @@ public String register(final MetaDataRegisterDTO dto) {
}
return ShenyuResultMessage.SUCCESS;
}
+}
```
整个注册逻辑可以分为4个步骤:
@@ -1414,27 +1481,34 @@ public String register(final MetaDataRegisterDTO dto) {
- registerURI(): 注册`URI`数据
```java
-public String registerURI(final String selectorName, final
List<URIRegisterDTO> uriList) {
+public abstract class AbstractShenyuClientRegisterServiceImpl extends
FallbackShenyuClientRegisterService implements ShenyuClientRegisterService {
+
+ //......
+
+ public String doRegisterURI(final String selectorName, final
List<URIRegisterDTO> uriList) {
if (CollectionUtils.isEmpty(uriList)) {
return "";
}
// 对应的选择器是否存在
SelectorDO selectorDO =
selectorService.findByNameAndPluginName(selectorName,
PluginNameAdapter.rpcTypeAdapter(rpcType()));
if (Objects.isNull(selectorDO)) {
- return "";
+ throw new ShenyuException("doRegister Failed to execute,wait to
retry.");
}
+ List<URIRegisterDTO> validUriList = uriList.stream().filter(dto ->
Objects.nonNull(dto.getPort()) &&
StringUtils.isNotBlank(dto.getHost())).collect(Collectors.toList());
// 处理选择器中的handler信息
- String handler = buildHandle(uriList, selectorDO);
- selectorDO.setHandle(handler);
- SelectorData selectorData = selectorService.buildByName(selectorName,
PluginNameAdapter.rpcTypeAdapter(rpcType()));
- selectorData.setHandle(handler);
-
- // 更新数据库中的记录
- selectorService.updateSelective(selectorDO);
- // 发布事件
- eventPublisher.publishEvent(new
DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
Collections.singletonList(selectorData)));
+ String handler = buildHandle(validUriList, selectorDO);
+ if (handler != null) {
+ selectorDO.setHandle(handler);
+ SelectorData selectorData =
selectorService.buildByName(selectorName,
PluginNameAdapter.rpcTypeAdapter(rpcType()));
+ selectorData.setHandle(handler);
+ // 更新数据库中的记录
+ selectorService.updateSelective(selectorDO);
+ // 发布事件
+ eventPublisher.publishEvent(new
DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
Collections.singletonList(selectorData)));
+ }
return ShenyuResultMessage.SUCCESS;
}
+}
```
`admin`拿到`URI`数据后,主要是更新选择器中的`handler`信息,然后写入到数据库,最后发布事件通知网关。通知网关的逻辑是由数据同步操作完成,这在之前的文章中已经分析过了,就不再赘述。
@@ -1460,7 +1534,7 @@ public String registerURI(final String selectorName,
final List<URIRegisterDTO>
- 注册中心是为了将客户端信息注册到`admin`,方便流量筛选;
- `http`注册是将客户端元数据信息和`URI`信息注册到`admin`;
- `http`服务的接入通过注解`@ShenyuSpringMvcClient`标识;
-- 注册信息的构建主要通过`Spring`的后置处理器`BeanPostProcessor`和应用监听器`ApplicationListener`;
+- 注册信息的构建主要通过`Spring`应用监听器`ApplicationListener`;
- 注册类型的加载通过`SPI`完成;
- 引入`Disruptor`队列是为了数据与操作解耦,以及数据缓冲。
- 注册中心的实现采用了面向接口编程,使用模板方法、单例、观察者等设计模式。
diff --git
a/static/img/activities/code-analysis-http-register/client-metadata-uri-register-en.png
b/static/img/activities/code-analysis-http-register/client-metadata-uri-register-en.png
new file mode 100644
index 00000000000..04c45f6a529
Binary files /dev/null and
b/static/img/activities/code-analysis-http-register/client-metadata-uri-register-en.png
differ
diff --git
a/static/img/activities/code-analysis-http-register/client-metadata-uri-register-zh.png
b/static/img/activities/code-analysis-http-register/client-metadata-uri-register-zh.png
new file mode 100644
index 00000000000..d13d260182e
Binary files /dev/null and
b/static/img/activities/code-analysis-http-register/client-metadata-uri-register-zh.png
differ
diff --git
a/static/img/activities/code-analysis-http-register/client-register-init-en.png
b/static/img/activities/code-analysis-http-register/client-register-init-en.png
index 6163b109461..932ea463df1 100644
Binary files
a/static/img/activities/code-analysis-http-register/client-register-init-en.png
and
b/static/img/activities/code-analysis-http-register/client-register-init-en.png
differ
diff --git
a/static/img/activities/code-analysis-http-register/client-register-init-zh.png
b/static/img/activities/code-analysis-http-register/client-register-init-zh.png
index 93242a060b2..4eca5f59074 100644
Binary files
a/static/img/activities/code-analysis-http-register/client-register-init-zh.png
and
b/static/img/activities/code-analysis-http-register/client-register-init-zh.png
differ
diff --git
a/static/img/activities/code-analysis-http-register/client-register-service.png
b/static/img/activities/code-analysis-http-register/client-register-service.png
index b493214a1de..055468b3f72 100644
Binary files
a/static/img/activities/code-analysis-http-register/client-register-service.png
and
b/static/img/activities/code-analysis-http-register/client-register-service.png
differ
diff --git
a/static/img/activities/code-analysis-http-register/shenyu-client-event-listener.png
b/static/img/activities/code-analysis-http-register/shenyu-client-event-listener.png
new file mode 100644
index 00000000000..4323d50584a
Binary files /dev/null and
b/static/img/activities/code-analysis-http-register/shenyu-client-event-listener.png
differ
diff --git
a/static/img/activities/code-analysis-http-register/shenyu-client-register-repository.png
b/static/img/activities/code-analysis-http-register/shenyu-client-register-repository.png
index 03524d13e8c..130e7b493ee 100644
Binary files
a/static/img/activities/code-analysis-http-register/shenyu-client-register-repository.png
and
b/static/img/activities/code-analysis-http-register/shenyu-client-register-repository.png
differ