Merge branch 'master' of github.com:spring-cloud-incubator/spring-cloud-alibaba

# Conflicts:
#	spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/nacos/NacosDiscoveryParameterInitListener.java
#	spring-cloud-alicloud-context/src/test/java/org/springframework/cloud/alicloud/context/nacos/NacosDiscoveryParameterInitListenerTests.java
pull/372/head
xiaolongzuo 6 years ago
commit 2a83f3e84e

@ -88,6 +88,7 @@
<module>spring-cloud-alibaba-dependencies</module>
<module>spring-cloud-alibaba-sentinel</module>
<module>spring-cloud-alibaba-sentinel-datasource</module>
<module>spring-cloud-alibaba-sentinel-zuul</module>
<module>spring-cloud-alibaba-nacos-config</module>
<module>spring-cloud-alibaba-nacos-discovery</module>
<module>spring-cloud-alibaba-fescar</module>

@ -20,7 +20,7 @@
<sentinel.version>1.4.2</sentinel.version>
<oss.version>3.1.0</oss.version>
<fescar.version>0.1.3</fescar.version>
<nacos.client.version>0.8.1</nacos.client.version>
<nacos.client.version>0.8.2</nacos.client.version>
<nacos.config.version>0.8.0</nacos.config.version>
<acm.version>1.0.8</acm.version>
<ans.version>1.0.1</ans.version>
@ -148,6 +148,11 @@
<artifactId>sentinel-web-servlet</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-zuul-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
@ -203,22 +208,6 @@
<version>${fescar.version}</version>
</dependency>
<!-- Aliyun OSS dependencies -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${oss.version}</version>
</dependency>
<!-- Apache Dubbo dependencies-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-dependencies-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
@ -254,6 +243,13 @@
<version>${dubbo-registry-nacos.version}</version>
</dependency>
<!-- Aliyun OSS dependencies -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${oss.version}</version>
</dependency>
<!-- Own dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
@ -265,6 +261,11 @@
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-zuul</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alicloud-oss</artifactId>
@ -453,4 +454,4 @@
</pluginRepositories>
</profile>
</profiles>
</project>
</project>

@ -6,7 +6,7 @@ Spring Cloud AliCloud ACM 是 Config Server 和 Client 的替代方案,客户
=== 如何引入 Spring Cloud AliCloud ACM
Spring Cloud Alibaba 已经发布了 0.2.2.BUILD-SNAPSHOT 版本需要首先导入依赖管理POM。
Spring Cloud Alibaba 已经发布了 0.2.2.BUILD-SNAPSHOT 版本,需要首先导入依赖管理 POM。
[source,xml]
----
@ -23,7 +23,7 @@ Spring Cloud Alibaba 已经发布了 0.2.2.BUILD-SNAPSHOT 版本,需要首先
</dependencyManagement>
----
接下来引入 Spring Cloud AliCloud ACM Starter 即可
并引入 Spring Cloud AliCloud ACM Starter 依赖
[source,xml]
----
@ -53,7 +53,7 @@ public class ProviderApplication {
}
----
既然需要从配置中心服务端获取配置信息,那么肯定需要配置服务端的地址,在 bootstrap.properties 中,还需要配置以下信息。
在从配置中心服务端获取配置信息之前,还需要配置服务端的地址,在 bootstrap.properties 中,还需要配置以下信息。
[source,properties]
----
@ -68,18 +68,18 @@ spring.cloud.alicloud.acm.server-port=8080
NOTE: 此时没有启动配置中心,启动应用会报错,因此在应用启动之前,应当首先启动配置中心。
=== 启动配置中心
==== 启动配置中心
ACM 使用的配置中心有两种,一种是完全免费的轻量版配置中心,主要用于开发和本地调试,一种是云上配置中心ACM。通常情况下可以使用轻量版配置中心作为开发和测试环境使用云上的 ACM 作为灰度和生产环境。
ACM 使用的配置中心有两种,一种是本地运行的轻量版配置中心,主要用于开发和本地调试,一种是阿里云产品 ACM。通常情况下可以使用轻量版配置中心作为开发和测试环境使用云上的 ACM 作为灰度和生产环境。
==== 启动轻量版配置中心
===== 使用轻量版配置中心
轻量版配置中心的下载和启动方式可参考 https://help.aliyun.com/document_detail/44163.html?spm=a2c4g.11186623.6.677.5f206b82Z2mTCF[这里]
轻量版配置中心的下载和启动方式可参考 https://help.aliyun.com/document_detail/44163.html[这里]
NOTE: 只需要进行第1步下载轻量配置中心和第2步启动轻量配置中心即可第3步配置hosts在与 ACM 结合使用时,不需要操作
NOTE: 只需要执行文档中的第1步 (下载轻量配置中心) 和第2步 (启动轻量配置中心)
==== 使用云配置中心
===== 使用阿里云配置中心
使用云上 ACM ,可以省去服务端的维护工作,同时稳定性也会更有保障。当使用云上配置中心时,代码部分和使用轻量配置中心并没有区别,但是配置上会有一些区别。
@ -88,7 +88,7 @@ NOTE: 只需要进行第1步下载轻量配置中心和第2步启动轻
[source,properties]
----
# 应用名会被作为从服务端获取配置 key 的关键词组成部分,因此是必选
spring.application.name=ans-provider
spring.application.name=acm-config
# 端口配置自由配置即可
server.port=18081
# 以下就是配置中心的IP和端口配置
@ -99,9 +99,9 @@ spring.cloud.alicloud.acm.endpoint=acm.aliyun.com
spring.cloud.alicloud.acm.namespace=你的 ACM namespace需要在 ACM 控制台查询
----
NOTE: EDAS 提供应用托管服务,如果你将应用托管到 EDAS那么 EDAS 将会自动为你填充所有配置。
NOTE: EDAS 提供应用托管服务,如果你将应用托管到 EDAS那么 EDAS 将会自动为你填充所有与业务无关的配置。
=== 在配置中心添加配置
==== 在配置中心添加配置
1. 启动好轻量版配置中心之后,在控制台中添加如下的配置。
@ -115,9 +115,9 @@ Content: user.name=james
user.age=18
----
NOTE: DataId 的格式为 `{prefix}. {file-extension}`,prefix 默认从配置 spring.application.name 中取值file-extension 默认的值为 "properties"。
NOTE: DataId 的格式为 `{prefix}.{file-extension}`,prefix 默认从配置 spring.application.name 中取值file-extension 默认的值为 "properties"。
=== 启动应用验证
==== 启动应用验证
启动这个Example可以在控制台看到打印出的值正是我们在轻量版配置中心上预先配置的值。
@ -133,16 +133,16 @@ spring-cloud-starter-alicloud-acm 中 DataId 默认的文件扩展名是 propert
NOTE: 修改文件扩展名后,在配置中心中的 DataID 以及 Content 的格式都必须做相应的修改。
=== 支持配置的动态更新
=== 动态更新
spring-cloud-starter-alicloud-acm 默认支持配置的动态更新,当您在配置中心修改配置的内容时,会触发 Spring 中的 Context Refresh 动作
spring-cloud-starter-alicloud-acm 默认支持配置的动态更新,当您在配置中心修改配置的内容时,会发布 Spring 中的 RefreshEvent 事件
带有 @RefreshScope 和 @ConfigurationProperties 注解的类会自动刷新。
NOTE: 你可以通过配置 spring.cloud.alicloud.acm.refresh.enabled=false 来关闭动态刷新
NOTE: 你可以通过配置 spring.cloud.alicloud.acm.refresh.enabled=false 来关闭动态刷新
=== profile 粒度的配置
=== Profile 粒度的配置
spring-cloud-starter-alicloud-acm 在加载配置的时候,首先会尝试去加载 dataid 为{spring.application.name}.{file-extension}的配置,当设置了 spring.profiles.active 中配置有内容时,还会尝试依次去加载 spring.profile 对应的内容, dataid 的格式为{spring.application.name}-{profile}.{file-extension}的配置,且后者的优先级高于前者。
spring-cloud-starter-alicloud-acm 在加载配置的时候,首先会加载 DataId 为{spring.application.name}.{file-extension}的配置,当 spring.profiles.active 中配置有内容时,还会依次去加载 spring.profile 对应的内容, DataId 的格式为{spring.application.name}-{profile}.{file-extension}的配置,且后者的优先级高于前者。
spring.profiles.active 属于配置的元数据,所以也必须配置在 bootstrap.properties 或 bootstrap.yaml 中。比如可以在 bootstrap.properties 中增加如下内容。
@ -154,8 +154,11 @@ spring.profiles.active={profile-name}
Note: 也可以通过 JVM 参数 -Dspring.profiles.active=develop 或者 --spring.profiles.active=develop 这类优先级更高的方式来配置,只需遵循 Spring Boot 规范即可。
=== 自定义配置中心超时时间
ACM Client 与 Server 通信的超时时间默认是 3000ms可以通过 `spring.cloud.alicloud.acm.timeout` 来修改超时时间,单位为 ms 。
=== 支持自定义 Group 的配置
=== 自定义 Group 的配置
在没有明确指定 `{spring.cloud.alicloud.acm.group}` 配置的情况下, 默认使用的是 DEFAULT_GROUP 。如果需要自定义自己的 Group可以通过以下配置来实现
@ -164,9 +167,9 @@ Note: 也可以通过 JVM 参数 -Dspring.profiles.active=develop 或者 --sprin
spring.cloud.alicloud.acm.group=DEVELOP_GROUP
----
NOTE: 该配置必须放在 bootstrap.properties 文件中。并且在添加配置时 Group 的值一定要和 `spring.cloud.alicloud.acm.group` 的配置值一致。
NOTE: 该配置必须放在 bootstrap.properties 文件中。并且在添加配置时 Group 的值要和 `spring.cloud.alicloud.acm.group` 的配置值一致。
==== 支持共享配置
=== 共享配置
ACM 提供了一种多个应用之间共享配置中心的同一个配置的推荐方式,供多个应用共享一些配置时使用,您在使用的时候需要添加在 bootstrap 中添加一个配置项 `spring.application.group`。
@ -175,11 +178,14 @@ ACM 提供了一种多个应用之间共享配置中心的同一个配置的推
spring.application.group=company.department.team
----
这时你的应用在获取之前提到的自身所独有的配置之前,会先依次从这些 DataId 去获取,分别是 company:application.properties, company.department:application.properties, company.department.team:application.properties。
然后,还会从 {spring.application.group}:{spring.application.name}.{file-extension} 中获取
越往后优先级越高,最高的仍然是应用自身所独有的配置。
这时应用在获取上文提到的自身所独有的配置之前,会先依次从这些 DataId 去获取,分别是 company:application.properties, company.department:application.properties, company.department.team:application.properties。
然后,还会从 {spring.application.group}:{spring.application.name}.{file-extension} 中获取,越往后优先级越高,最高的仍然是应用自身所独有的配置。
NOTE: 共享配置中 DataId 默认后缀为 properties可以通过 spring.cloud.alicloud.acm.file-extension 配置. `{spring.application.group}:{spring.application.name}.{file-extension}` 。
NOTE: 如果设置了 `spring.profiles.active` DataId 的格式还支持 `{spring.application.group}:{spring.application.name}-{spring.profiles.active}.{file-extension}`。优先级高于 `{spring.application.group}:{spring.application.name}.{file-extension}`
=== Actuator 监控
ACM 对应的 Actuator 监控地址为 `/acm`,其中 config 代表了 ACM 元数据配置的信息,`runtime.sources` 对应的是从 ACM 服务端获取的配置的信息及最后刷新时间, `runtime.refreshHistory` 对应的是动态刷新的历史记录。

@ -261,9 +261,9 @@ public class NacosConsumerApp {
public String echoAppName(){
//使用 LoadBalanceClient 和 RestTemolate 结合的方式来访问
ServiceInstance serviceInstance = loadBalancerClient.choose("nacos-provider");
String url = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
System.out.println("request url:"+url);
return restTemplate.getForObject(url,String.class);
String path = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
System.out.println("request path:"+path);
return restTemplate.getForObject(path,String.class);
}
}

@ -6,7 +6,7 @@ Spring Cloud Alibaba Cloud ACM is an alternative solution for Config Server and
=== How to Introduce Spring Cloud Alibaba Cloud ACM
Weve released Spring Cloud Alibaba version 0.2.1. You will need to add dependency management POM first.
Weve released Spring Cloud Alibaba version 0.2.2.BUILD-SNAPSHOT. You will need to add dependency management POM first.
[source,xml]
----
@ -68,18 +68,18 @@ spring.cloud.alicloud.acm.server-port=8080
NOTE: By now the configuration center is not started yet, so you will get an error message if your application is started. Therefore, start the configuration center before you start your application.
=== Start Configuration Center
==== Start Configuration Center
ACM uses two types of configuration centers. One is a lightweight configuration center which is totally free, the other is ACM which is used on Alibaba Cloud. Generally, you can use the lightweight version for application development and local testing, and use ACM for canary deployment or production.
ACM uses two types of configuration centers. One is lightweight configuration center, the other is ACM which is used on Alibaba Cloud. Generally, you can use the lightweight version for application development and local testing, and use ACM for canary deployment or production.
==== Start Lightweight Configuration Center
===== Use Lightweight Configuration Center
Refer to the https://help.aliyun.com/document_detail/44163.html[Configure Lightweight Configuration Center] for details about how to download and install lightweight configuration center.
NOTE: You only need to perform step 1(Download lightweight configuration center) and step 2(Start lightweight configuration center). Step 3(Configure hosts) is not required if you use ACM at the same time.
NOTE: You only need to perform step 1(Download lightweight configuration center) and step 2(Start lightweight configuration center).
==== Use ACM on the Cloud
===== Use ACM on the Alibaba Cloud
Using ACM on the cloud saves you from the tedious work of server maintenance while at the same time provides a better stability. There is no difference at the code level between using ACM on cloud and lightweight configuration center, but there are some differences in configurations.
@ -88,7 +88,7 @@ The following is a simple sample of using ACM. You can view configuration detail
[source,properties]
----
# The application name will be used as part of the keyword to obtain configuration key from the server, and is mandatory.
spring.application.name=ans-provider
spring.application.name=acm-config
# Configure your own port number
server.port=18081
# The following is the IP and port number of the configuration center.
@ -99,9 +99,9 @@ spring.cloud.alicloud.acm.endpoint=acm.aliyun.com
spring.cloud.alicloud.acm.namespace=Your ACM namespace(You can find the namespace on the ACM console)
----
NOTE: EDAS provides application hosting service and will fill in all configurations automatically for the hosted applications.
NOTE: EDAS provides application hosting service and will fill in all configurations about ACM automatically for the hosted applications.
=== Add Configuration in the Configuration Center
==== Add Configuration in the Configuration Center
1. After you start the lightweight configuration center, add the following configuration on the console.
@ -117,7 +117,7 @@ Content: user.name=james
NOTE: The format of dataId is `{prefix}. {file-extension}`. “prefix” is obtained from spring.application.name by default, and the value of “file-extension” is "properties” by default.
=== Start Application Verification
==== Start Application Verification
Start the following example and you can see that the value printed on the console is the value we configured in the lightweight configuration center.
@ -133,16 +133,16 @@ You can set the file extension using spring.cloud.alicloud.acm.file-extension. J
NOTE: After you change the file extension, you need to make corresponding format changes in the DataID and content of the configuration center.
=== Dynamic Configuration Updates
=== Dynamic Configuration Refresh
spring-cloud-starter-alicloud-acm supports dynamic configuration updates. Context Refresh in Spring is triggered when you update configuration in the configuration center.
All classes with @RefreshScope and @ConfigurationProperties annotations will be refershed automatically.
spring-cloud-starter-alicloud-acm supports dynamic configuration updates. RefreshEvent in Spring is published when you update configuration in the configuration center.
All classes with @RefreshScope and @ConfigurationProperties annotations will be refreshed automatically.
NOTE: You can disable automatic refresh by this setting: spring.cloud.alicloud.acm.refresh.enabled=false
=== Configure Profile Granularity
When configuration is loaded by spring-cloud-starter-alicloud-acm, configuration with dataid {spring.application.name}. {file-extension} will be loaded first. If there is content in spring.profiles.active, the content of spring.profile, and configuration with the dataid format of{spring.application.name}-{profile}. {file-extension} will also be loaded in turn, and the latter has higher priority.
When configuration is loaded by spring-cloud-starter-alicloud-acm, configuration with DataId {spring.application.name}. {file-extension} will be loaded first. If there is content in spring.profiles.active, the content of spring.profile, and configuration with the dataid format of{spring.application.name}-{profile}. {file-extension} will also be loaded in turn, and the latter has higher priority.
spring.profiles.active is the configuration metadata, and should also be configured in bootstrap.properties or bootstrap.yaml. For example, you can add the following content in bootstrap.properties.
@ -154,6 +154,10 @@ spring.profiles.active={profile-name}
Note: You can also configure the granularity through JVM parameters such as -Dspring.profiles.active=develop or --spring.profiles.active=develop, which have higher priority. Just follow the specifications of Spring Boot.
=== Support Custom ACM Timeout
the default timeout of ACM client get config from sever is 3000 ms . If you need to define a timeout, set configuration `spring.cloud.alicloud.acm.timeout`,the unit is millisecond.
=== Support Custom Group Configurations
@ -183,3 +187,7 @@ The later in order, the higer the priority, and the unique configuration of the
NOTE: The default suffix of DataId is properties, and you can change it using spring.cloud.alicloud.acm.file-extension. `{spring.application.group}: {spring.application.name}. {file-extension}` 。
NOTE: If you configured `spring.profiles.active` , then the DataId format of `{spring.application.group}: {spring.application.name}-{spring.profiles.active}. {file-extension}` is also supported, and has higher priority than `{spring.application.group}: {spring.application.name}. {file-extension}`
=== Actuator Endpoint
the Actuator endpoint of ACM is `/acm`, `config` represents the ACM metadata configuration information, `runtime.sources` corresponds to the configuration information obtained from the ACM server and the last refresh time, `runtime.refreshHistory` corresponds to the dynamic refresh history.

@ -29,7 +29,7 @@ If you want to use the latest BUILD-SNAPSHOT version, add Spring Snapshot Reposi
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
<path>https://repo.spring.io/snapshot</path>
<snapshots>
<enabled>true</enabled>
</snapshots>

@ -261,9 +261,9 @@ public class NacosConsumerApp {
public String echoAppName(){
//Access through the combination of LoadBalanceClient and RestTemolate
ServiceInstance serviceInstance = loadBalancerClient.choose("nacos-provider");
String url = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
System.out.println("request url:" +url);
return restTemplate.getForObject(url,String.class);
String path = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
System.out.println("request path:" +path);
return restTemplate.getForObject(path,String.class);
}
}

@ -3,8 +3,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba</artifactId>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba</artifactId>
<version>0.2.2.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -13,6 +13,37 @@
<artifactId>spring-cloud-alibaba-dubbo</artifactId>
<name>Spring Cloud Alibaba Dubbo</name>
<properties>
<dubbo.version>2.6.5</dubbo.version>
<dubbo-spring-boot.version>0.2.1.RELEASE</dubbo-spring-boot.version>
<dubbo-registry-nacos.version>0.0.2</dubbo-registry-nacos.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Apache Dubbo dependencies-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-dependencies-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot dependencies -->
@ -170,9 +201,9 @@
<!-- Eureka Service Discovery -->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!--<scope>test</scope>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
</dependencies>

@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.annotation;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.client.RestTemplate;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@link DubboTransported @DubboTransported} annotation indicates that the traditional Spring Cloud Service-to-Service call is transported
* by Dubbo under the hood, there are two main scenarios:
* <ol>
* <li>{@link FeignClient @FeignClient} annotated classes:
* <ul>
* If {@link DubboTransported @DubboTransported} annotated classes, the invocation of all methods of
* {@link FeignClient @FeignClient} annotated classes.
* </ul>
* <ul>
* If {@link DubboTransported @DubboTransported} annotated methods of {@link FeignClient @FeignClient} annotated classes.
* </ul>
* </li>
* <li>{@link LoadBalanced @LoadBalanced} {@link RestTemplate} annotated field, method and parameters</li>
* </ol>
* <p>
*
* @see FeignClient
* @see LoadBalanced
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Documented
public @interface DubboTransported {
/**
* The protocol of Dubbo transport whose value could be used the placeholder "dubbo.transport.protocol"
*
* @return the default protocol is "dubbo"
*/
String protocol() default "${dubbo.transport.protocol:dubbo}";
/**
* The cluster of Dubbo transport whose value could be used the placeholder "dubbo.transport.cluster"
*
* @return the default cluster is "failover"
*/
String cluster() default "${dubbo.transport.cluster:failover}";
}

@ -0,0 +1,167 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.autoconfigure;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import org.springframework.cloud.alibaba.dubbo.client.loadbalancer.DubboMetadataInitializerInterceptor;
import org.springframework.cloud.alibaba.dubbo.client.loadbalancer.DubboTransporterInterceptor;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContextFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.core.type.MethodMetadata;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Dubbo Auto-{@link Configuration} for {@link LoadBalanced @LoadBalanced} {@link RestTemplate}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
public class DubboLoadBalancedRestTemplateAutoConfiguration implements BeanClassLoaderAware {
private static final Class<DubboTransported> DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
private static final String DUBBO_TRANSPORTED_CLASS_NAME = DUBBO_TRANSPORTED_CLASS.getName();
@Autowired
private DubboServiceMetadataRepository repository;
@Autowired
private LoadBalancerInterceptor loadBalancerInterceptor;
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Autowired
private DubboGenericServiceFactory serviceFactory;
@Autowired
private DubboGenericServiceExecutionContextFactory contextFactory;
@Autowired
private Environment environment;
@LoadBalanced
@Autowired(required = false)
private Map<String, RestTemplate> restTemplates = Collections.emptyMap();
private ClassLoader classLoader;
/**
* Adapt the {@link RestTemplate} beans that are annotated {@link LoadBalanced @LoadBalanced} and
* {@link LoadBalanced @LoadBalanced} when Spring Boot application started
* (after the callback of {@link SmartInitializingSingleton} beans or
* {@link RestTemplateCustomizer#customize(RestTemplate) customization})
*/
@EventListener(ApplicationStartedEvent.class)
public void adaptRestTemplates() {
for (Map.Entry<String, RestTemplate> entry : restTemplates.entrySet()) {
String beanName = entry.getKey();
Map<String, Object> dubboTranslatedAttributes = getDubboTranslatedAttributes(beanName);
if (!CollectionUtils.isEmpty(dubboTranslatedAttributes)) {
adaptRestTemplate(entry.getValue(), dubboTranslatedAttributes);
}
}
}
/**
* Gets the annotation attributes {@link RestTemplate} bean being annotated
* {@link DubboTransported @DubboTransported}
*
* @param beanName the bean name of {@link LoadBalanced @LoadBalanced} {@link RestTemplate}
* @return non-null {@link Map}
*/
private Map<String, Object> getDubboTranslatedAttributes(String beanName) {
Map<String, Object> attributes = Collections.emptyMap();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanDefinition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
MethodMetadata factoryMethodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();
attributes = factoryMethodMetadata != null ?
factoryMethodMetadata.getAnnotationAttributes(DUBBO_TRANSPORTED_CLASS_NAME) : Collections.emptyMap();
}
return attributes;
}
/**
* Adapt the instance of {@link DubboTransporterInterceptor} to the {@link LoadBalancerInterceptor} Bean.
*
* @param restTemplate {@link LoadBalanced @LoadBalanced} {@link RestTemplate} Bean
* @param dubboTranslatedAttributes the annotation dubboTranslatedAttributes {@link RestTemplate} bean being annotated
* {@link DubboTransported @DubboTransported}
*/
private void adaptRestTemplate(RestTemplate restTemplate, Map<String, Object> dubboTranslatedAttributes) {
DubboTransportedMetadata dubboTransportedMetadata = buildDubboTransportedMetadata(dubboTranslatedAttributes);
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
int index = interceptors.indexOf(loadBalancerInterceptor);
index = index < 0 ? 0 : index;
// Add ClientHttpRequestInterceptor instances before loadBalancerInterceptor
interceptors.add(index++, new DubboMetadataInitializerInterceptor(repository));
interceptors.add(index++, new DubboTransporterInterceptor(repository, restTemplate.getMessageConverters(),
classLoader, dubboTransportedMetadata, serviceFactory, contextFactory));
restTemplate.setInterceptors(interceptors);
}
private DubboTransportedMetadata buildDubboTransportedMetadata(Map<String, Object> dubboTranslatedAttributes) {
DubboTransportedMetadata dubboTransportedMetadata = new DubboTransportedMetadata();
String protocol = (String) dubboTranslatedAttributes.get("protocol");
String cluster = (String) dubboTranslatedAttributes.get("cluster");
// resolve placeholders
dubboTransportedMetadata.setProtocol(environment.resolvePlaceholders(protocol));
dubboTransportedMetadata.setCluster(environment.resolvePlaceholders(cluster));
return dubboTransportedMetadata;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}

@ -16,15 +16,22 @@
*/
package org.springframework.cloud.alibaba.dubbo.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.metadata.service.MetadataConfigService;
import org.springframework.cloud.alibaba.dubbo.metadata.service.NacosMetadataConfigService;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataConfigServiceProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import java.util.Collection;
import java.util.Iterator;
import static com.alibaba.dubbo.common.Constants.DEFAULT_PROTOCOL;
/**
* Spring Boot Auto-Configuration class for Dubbo Metadata
*
@ -32,12 +39,39 @@ import org.springframework.context.annotation.Import;
*/
@Configuration
@Import(DubboServiceMetadataRepository.class)
@DubboComponentScan(basePackages = "org.springframework.cloud.alibaba.dubbo.service")
public class DubboMetadataAutoConfiguration {
@Bean
@ConditionalOnBean(NacosConfigProperties.class)
public MetadataConfigService metadataConfigService() {
return new NacosMetadataConfigService();
public static final String METADATA_PROTOCOL_BEAN_NAME = "metadata";
/**
* Build an alias Bean for {@link ProtocolConfig}
*
* @param protocols {@link ProtocolConfig} Beans
* @return {@link ProtocolConfig} bean
*/
@Bean(name = METADATA_PROTOCOL_BEAN_NAME)
public ProtocolConfig protocolConfig(Collection<ProtocolConfig> protocols) {
ProtocolConfig protocolConfig = null;
for (ProtocolConfig protocol : protocols) {
String protocolName = protocol.getName();
if (DEFAULT_PROTOCOL.equals(protocolName)) {
protocolConfig = protocol;
break;
}
}
if (protocolConfig == null) { // If The ProtocolConfig bean named "dubbo" is absent, take first one of them
Iterator<ProtocolConfig> iterator = protocols.iterator();
protocolConfig = iterator.hasNext() ? iterator.next() : null;
}
return protocolConfig;
}
@Bean
@ConditionalOnMissingBean
public DubboMetadataConfigServiceProxy dubboMetadataConfigServiceProxy(DubboGenericServiceFactory factory) {
return new DubboMetadataConfigServiceProxy(factory);
}
}

@ -19,17 +19,19 @@ package org.springframework.cloud.alibaba.dubbo.autoconfigure;
import feign.Contract;
import feign.Feign;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.alibaba.dubbo.metadata.resolver.FeignMetadataResolver;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboServiceBeanMetadataResolver;
import org.springframework.cloud.alibaba.dubbo.metadata.resolver.MetadataResolver;
import org.springframework.cloud.alibaba.dubbo.openfeign.DubboFeignClientsConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.alibaba.dubbo.openfeign.TargeterBeanPostProcessor;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContextFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
@ -39,16 +41,22 @@ import org.springframework.context.annotation.Configuration;
*/
@ConditionalOnClass(value = Feign.class)
@AutoConfigureAfter(FeignAutoConfiguration.class)
@EnableFeignClients(defaultConfiguration = DubboFeignClientsConfiguration.class)
@Configuration
public class DubboOpenFeignAutoConfiguration {
@Value("${spring.application.name}")
private String currentApplicationName;
@Bean
@ConditionalOnMissingBean
public MetadataResolver metadataJsonResolver(ObjectProvider<Contract> contract) {
return new FeignMetadataResolver(currentApplicationName, contract);
return new DubboServiceBeanMetadataResolver(contract);
}
@Bean
public TargeterBeanPostProcessor targeterBeanPostProcessor(Environment environment,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
return new TargeterBeanPostProcessor(environment, dubboServiceMetadataRepository,
dubboGenericServiceFactory, contextFactory);
}
}

@ -18,22 +18,18 @@ package org.springframework.cloud.alibaba.dubbo.autoconfigure;
import com.alibaba.dubbo.config.spring.ServiceBean;
import com.alibaba.dubbo.config.spring.context.event.ServiceBeanExportedEvent;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.resolver.MetadataResolver;
import org.springframework.cloud.alibaba.dubbo.metadata.service.MetadataConfigService;
import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent;
import org.springframework.cloud.alibaba.dubbo.service.PublishingDubboMetadataConfigService;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* The Auto-Configuration class for Dubbo REST metadata registration,
* REST metadata that is a part of {@link Registration#getMetadata() Spring Cloud service instances' metadata}
@ -42,45 +38,25 @@ import java.util.Set;
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@ConditionalOnMissingBean(value = {
MetadataResolver.class,
MetadataConfigService.class
@ConditionalOnBean(value = {
MetadataResolver.class
})
@AutoConfigureAfter(value = {DubboMetadataAutoConfiguration.class})
@Configuration
public class DubboRestMetadataRegistrationAutoConfiguration {
/**
* A Map to store REST metadata temporary, its' key is the special service name for a Dubbo service,
* the value is a JSON content of JAX-RS or Spring MVC REST metadata from the annotated methods.
*/
private final Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
@Autowired
private MetadataResolver metadataResolver;
@Autowired
private MetadataConfigService metadataConfigService;
private PublishingDubboMetadataConfigService dubboMetadataConfigService;
@Value("${spring.application.name:application}")
private String currentApplicationName;
@EventListener(ServiceBeanExportedEvent.class)
public void recordRestMetadata(ServiceBeanExportedEvent event) throws JsonProcessingException {
public void recordRestMetadata(ServiceBeanExportedEvent event) {
ServiceBean serviceBean = event.getServiceBean();
serviceRestMetadata.addAll(metadataResolver.resolveServiceRestMetadata(serviceBean));
dubboMetadataConfigService.publishServiceRestMetadata(metadataResolver.resolveServiceRestMetadata(serviceBean));
}
/**
* Pre-handle Spring Cloud application service registered:
* <p>
* Put <code>restMetadata</code> with the JSON format into
* {@link Registration#getMetadata() service instances' metadata}
* <p>
*
* @param event {@link InstancePreRegisteredEvent} instance
*/
@EventListener(InstancePreRegisteredEvent.class)
public void registerRestMetadata(InstancePreRegisteredEvent event) throws Exception {
Registration registration = event.getRegistration();
metadataConfigService.publishServiceRestMetadata(registration.getServiceId(), serviceRestMetadata);
}
}

@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContextFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.alibaba.dubbo.service.parameter.PathVariableServiceParameterResolver;
import org.springframework.cloud.alibaba.dubbo.service.parameter.RequestBodyServiceParameterResolver;
import org.springframework.cloud.alibaba.dubbo.service.parameter.RequestHeaderServiceParameterResolver;
import org.springframework.cloud.alibaba.dubbo.service.parameter.RequestParamServiceParameterResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Spring Boot Auto-Configuration class for Dubbo Service
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
public class DubboServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DubboGenericServiceFactory dubboGenericServiceFactory() {
return new DubboGenericServiceFactory();
}
@Configuration
@Import(value = {
DubboGenericServiceExecutionContextFactory.class,
RequestParamServiceParameterResolver.class,
RequestBodyServiceParameterResolver.class,
RequestHeaderServiceParameterResolver.class,
PathVariableServiceParameterResolver.class
})
static class ParameterResolversConfiguration {
}
}

@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.client.loadbalancer;
import com.alibaba.dubbo.rpc.service.GenericException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
import java.io.InputStream;
/**
* Dubbo {@link ClientHttpResponse} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see DubboTransporterInterceptor
*/
class DubboClientHttpResponse implements ClientHttpResponse {
private final HttpStatus httpStatus;
private final String statusText;
private final HttpHeaders httpHeaders = new HttpHeaders();
private final DubboHttpOutputMessage httpOutputMessage;
public DubboClientHttpResponse(DubboHttpOutputMessage httpOutputMessage, GenericException exception) {
this.httpStatus = exception != null ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.OK;
this.statusText = exception != null ? exception.getExceptionMessage() : httpStatus.getReasonPhrase();
this.httpOutputMessage = httpOutputMessage;
this.httpHeaders.putAll(httpOutputMessage.getHeaders());
}
@Override
public HttpStatus getStatusCode() throws IOException {
return httpStatus;
}
@Override
public int getRawStatusCode() throws IOException {
return httpStatus.value();
}
@Override
public String getStatusText() throws IOException {
return statusText;
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return httpOutputMessage.getBody().getInputStream();
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.client.loadbalancer;
import com.alibaba.dubbo.rpc.service.GenericException;
import org.springframework.cloud.alibaba.dubbo.http.converter.HttpMessageConverterHolder;
import org.springframework.cloud.alibaba.dubbo.http.util.HttpMessageConverterResolver;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageConverter;
import java.io.IOException;
import java.util.List;
/**
* Dubbo {@link ClientHttpResponse} Factory
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class DubboClientHttpResponseFactory {
private final HttpMessageConverterResolver httpMessageConverterResolver;
public DubboClientHttpResponseFactory(List<HttpMessageConverter<?>> messageConverters, ClassLoader classLoader) {
this.httpMessageConverterResolver = new HttpMessageConverterResolver(messageConverters, classLoader);
}
public ClientHttpResponse build(Object result, GenericException exception,
RequestMetadata requestMetadata, RestMethodMetadata restMethodMetadata) {
DubboHttpOutputMessage httpOutputMessage = new DubboHttpOutputMessage();
HttpMessageConverterHolder httpMessageConverterHolder = httpMessageConverterResolver.resolve(requestMetadata, restMethodMetadata);
if (httpMessageConverterHolder != null) {
MediaType mediaType = httpMessageConverterHolder.getMediaType();
HttpMessageConverter converter = httpMessageConverterHolder.getConverter();
try {
converter.write(result, mediaType, httpOutputMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
return new DubboClientHttpResponse(httpOutputMessage, exception);
}
}

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.client.loadbalancer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.util.FastByteArrayOutputStream;
import java.io.IOException;
/**
* Dubbo {@link HttpOutputMessage} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class DubboHttpOutputMessage implements HttpOutputMessage {
private final FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream();
private final HttpHeaders httpHeaders = new HttpHeaders();
@Override
public FastByteArrayOutputStream getBody() throws IOException {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}

@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.client.loadbalancer;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
import java.net.URI;
/**
* Dubbo Metadata {@link ClientHttpRequestInterceptor} Initializing Interceptor executes intercept before
* {@link DubboTransporterInterceptor}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboMetadataInitializerInterceptor implements ClientHttpRequestInterceptor {
private final DubboServiceMetadataRepository repository;
public DubboMetadataInitializerInterceptor(DubboServiceMetadataRepository repository) {
this.repository = repository;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
repository.initialize(serviceName);
// Execute next
return execution.execute(request, body);
}
}

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.client.loadbalancer;
import com.alibaba.dubbo.rpc.service.GenericException;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.springframework.cloud.alibaba.dubbo.http.MutableHttpServerRequest;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboServiceMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContext;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContextFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.util.UriComponents;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import static org.springframework.web.util.UriComponentsBuilder.fromUri;
/**
* Dubbo Transporter {@link ClientHttpRequestInterceptor} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see LoadBalancerInterceptor
*/
public class DubboTransporterInterceptor implements ClientHttpRequestInterceptor {
private final DubboServiceMetadataRepository repository;
private final DubboClientHttpResponseFactory clientHttpResponseFactory;
private final DubboTransportedMetadata dubboTransportedMetadata;
private final DubboGenericServiceFactory serviceFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private final PathMatcher pathMatcher = new AntPathMatcher();
public DubboTransporterInterceptor(DubboServiceMetadataRepository dubboServiceMetadataRepository,
List<HttpMessageConverter<?>> messageConverters,
ClassLoader classLoader,
DubboTransportedMetadata dubboTransportedMetadata,
DubboGenericServiceFactory serviceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.repository = dubboServiceMetadataRepository;
this.dubboTransportedMetadata = dubboTransportedMetadata;
this.clientHttpResponseFactory = new DubboClientHttpResponseFactory(messageConverters, classLoader);
this.serviceFactory = serviceFactory;
this.contextFactory = contextFactory;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
RequestMetadata clientMetadata = buildRequestMetadata(request);
DubboServiceMetadata dubboServiceMetadata = repository.get(serviceName, clientMetadata);
if (dubboServiceMetadata == null) {
// if DubboServiceMetadata is not found, executes next
return execution.execute(request, body);
}
RestMethodMetadata dubboRestMethodMetadata = dubboServiceMetadata.getRestMethodMetadata();
GenericService genericService = serviceFactory.create(dubboServiceMetadata, dubboTransportedMetadata);
MutableHttpServerRequest httpServerRequest = new MutableHttpServerRequest(request, body);
customizeRequest(httpServerRequest, dubboRestMethodMetadata, clientMetadata);
DubboGenericServiceExecutionContext context = contextFactory.create(dubboRestMethodMetadata, httpServerRequest);
Object result = null;
GenericException exception = null;
try {
result = genericService.$invoke(context.getMethodName(), context.getParameterTypes(), context.getParameters());
} catch (GenericException e) {
exception = e;
}
return clientHttpResponseFactory.build(result, exception, clientMetadata, dubboRestMethodMetadata);
}
protected void customizeRequest(MutableHttpServerRequest httpServerRequest,
RestMethodMetadata dubboRestMethodMetadata, RequestMetadata clientMetadata) {
RequestMetadata dubboRequestMetadata = dubboRestMethodMetadata.getRequest();
String pathPattern = dubboRequestMetadata.getPath();
Map<String, String> pathVariables = pathMatcher.extractUriTemplateVariables(pathPattern, httpServerRequest.getPath());
if (!CollectionUtils.isEmpty(pathVariables)) {
// Put path variables Map into query parameters Map
httpServerRequest.params(pathVariables);
}
}
private RequestMetadata buildRequestMetadata(HttpRequest request) {
UriComponents uriComponents = fromUri(request.getURI()).build(true);
RequestMetadata requestMetadata = new RequestMetadata();
requestMetadata.setPath(uriComponents.getPath());
requestMetadata.setMethod(request.getMethod().name());
requestMetadata.setParams(uriComponents.getQueryParams());
requestMetadata.setHeaders(request.getHeaders());
return requestMetadata;
}
}

@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http;
import com.alibaba.dubbo.common.io.UnsafeByteArrayInputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.IOException;
import java.io.InputStream;
/**
* Byte array {@link HttpInputMessage} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class ByteArrayHttpInputMessage implements HttpInputMessage {
private final HttpHeaders httpHeaders;
private final InputStream inputStream;
public ByteArrayHttpInputMessage(byte[] body) {
this(new HttpHeaders(), body);
}
public ByteArrayHttpInputMessage(HttpHeaders httpHeaders, byte[] body) {
this.httpHeaders = httpHeaders;
this.inputStream = new UnsafeByteArrayInputStream(body);
}
@Override
public InputStream getBody() throws IOException {
return inputStream;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}

@ -0,0 +1,130 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.List;
import java.util.Map;
import static org.springframework.web.util.UriComponentsBuilder.fromPath;
/**
* Default {@link HttpRequest} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DefaultHttpRequest implements HttpRequest {
private final String method;
private final URI uri;
private final HttpHeaders headers = new HttpHeaders();
public DefaultHttpRequest(String method, String path, Map<String, List<String>> params,
Map<String, List<String>> headers) {
this.method = method == null ? HttpMethod.GET.name() : method.toUpperCase();
this.uri = buildURI(path, params);
this.headers.putAll(headers);
}
private URI buildURI(String path, Map<String, List<String>> params) {
UriComponentsBuilder builder = fromPath(path)
.queryParams(new LinkedMultiValueMap<>(params));
return builder.build().toUri();
}
@Override
public HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
}
public String getMethodValue() {
return method;
}
@Override
public URI getURI() {
return uri;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
public static Builder builder() {
return new Builder();
}
/**
* {@link HttpRequest} Builder
*/
public static class Builder {
String method;
String path;
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
public Builder method(String method) {
this.method = method;
return this;
}
public Builder path(String path) {
this.path = path;
return this;
}
public Builder param(String name, String value) {
this.params.add(name, value);
return this;
}
public Builder header(String name, String value) {
this.headers.add(name, value);
return this;
}
public Builder params(Map<String, List<String>> params) {
this.params.putAll(params);
return this;
}
public Builder headers(Map<String, List<String>> headers) {
this.headers.putAll(headers);
return this;
}
public HttpRequest build() {
return new DefaultHttpRequest(method, path, params, headers);
}
}
}

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpRequest;
import org.springframework.util.MultiValueMap;
/**
* HTTP Server Request
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public interface HttpServerRequest extends HttpRequest, HttpInputMessage {
/**
* Return a path of current HTTP request
*
* @return
*/
String getPath();
/**
* Return a map with parsed and decoded query parameter values.
*/
MultiValueMap<String, String> getQueryParams();
}

@ -0,0 +1,99 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.MultiValueMap;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Map;
import static org.springframework.cloud.alibaba.dubbo.http.util.HttpUtils.getParameters;
/**
* Mutable {@link HttpServerRequest} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class MutableHttpServerRequest implements HttpServerRequest {
private final HttpMethod httpMethod;
private final URI uri;
private final String path;
private final MultiValueMap<String, String> queryParams;
private final HttpHeaders httpHeaders;
private final HttpInputMessage httpInputMessage;
public MutableHttpServerRequest(HttpRequest httpRequest, byte[] body) {
this.httpMethod = httpRequest.getMethod();
this.uri = httpRequest.getURI();
this.path = uri.getPath();
this.httpHeaders = httpRequest.getHeaders();
this.queryParams = getParameters(httpRequest);
this.httpInputMessage = new ByteArrayHttpInputMessage(body);
}
public MutableHttpServerRequest params(Map<String, String> params) {
queryParams.setAll(params);
return this;
}
@Override
public InputStream getBody() throws IOException {
return httpInputMessage.getBody();
}
@Override
public HttpMethod getMethod() {
return httpMethod;
}
// Override method since Spring Framework 5.0
public String getMethodValue() {
return httpMethod.name();
}
@Override
public URI getURI() {
return uri;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
@Override
public String getPath() {
return path;
}
@Override
public MultiValueMap<String, String> getQueryParams() {
return queryParams;
}
}

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.converter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
/**
* {@link HttpMessageConverter} Holder with {@link MediaType}.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpMessageConverterHolder {
private final MediaType mediaType;
private final HttpMessageConverter<?> converter;
public HttpMessageConverterHolder(MediaType mediaType, HttpMessageConverter<?> converter) {
this.mediaType = mediaType;
this.converter = converter;
}
public MediaType getMediaType() {
return mediaType;
}
public HttpMessageConverter<?> getConverter() {
return converter;
}
}

@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import java.util.Collection;
import java.util.Iterator;
/**
* Abstract {@link HttpRequestMatcher} implementation
*
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractHttpRequestMatcher implements HttpRequestMatcher {
/**
* Return the discrete items a request condition is composed of.
* <p>For example URL patterns, HTTP request methods, param expressions, etc.
*
* @return a collection of objects, never {@code null}
*/
protected abstract Collection<?> getContent();
/**
* The notation to use when printing discrete items of content.
* <p>For example {@code " || "} for URL patterns or {@code " && "}
* for param expressions.
*/
protected abstract String getToStringInfix();
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
return getContent().equals(((AbstractHttpRequestMatcher) other).getContent());
}
@Override
public int hashCode() {
return getContent().hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (Iterator<?> iterator = getContent().iterator(); iterator.hasNext(); ) {
Object expression = iterator.next();
builder.append(expression.toString());
if (iterator.hasNext()) {
builder.append(getToStringInfix());
}
}
builder.append("]");
return builder.toString();
}
}

@ -0,0 +1,91 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.MediaType;
/**
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.AbstractMediaTypeExpression
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class AbstractMediaTypeExpression implements MediaTypeExpression, Comparable<AbstractMediaTypeExpression> {
private final MediaType mediaType;
private final boolean negated;
AbstractMediaTypeExpression(String expression) {
if (expression.startsWith("!")) {
this.negated = true;
expression = expression.substring(1);
} else {
this.negated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}
AbstractMediaTypeExpression(MediaType mediaType, boolean negated) {
this.mediaType = mediaType;
this.negated = negated;
}
@Override
public MediaType getMediaType() {
return this.mediaType;
}
@Override
public boolean isNegated() {
return this.negated;
}
@Override
public int compareTo(AbstractMediaTypeExpression other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
AbstractMediaTypeExpression otherExpr = (AbstractMediaTypeExpression) other;
return (this.mediaType.equals(otherExpr.mediaType) && this.negated == otherExpr.negated);
}
@Override
public int hashCode() {
return this.mediaType.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (this.negated) {
builder.append('!');
}
builder.append(this.mediaType.toString());
return builder.toString();
}
}

@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.springframework.util.StringUtils.trimWhitespace;
/**
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.AbstractNameValueExpression
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
abstract class AbstractNameValueExpression<T> implements NameValueExpression<T> {
protected final String name;
protected final T value;
protected final boolean negated;
AbstractNameValueExpression(String expression) {
int separator = expression.indexOf('=');
if (separator == -1) {
this.negated = expression.startsWith("!");
this.name = trimWhitespace((this.negated ? expression.substring(1) : expression));
this.value = null;
} else {
this.negated = (separator > 0) && (expression.charAt(separator - 1) == '!');
this.name = trimWhitespace((this.negated ? expression.substring(0, separator - 1)
: expression.substring(0, separator)));
String valueExpression = getValueExpression(expression, separator);
this.value = isExcludedValue(valueExpression) ? null : parseValue(valueExpression);
}
}
private String getValueExpression(String expression, int separator) {
return trimWhitespace(expression.substring(separator + 1));
}
/**
* Exclude the pattern value Expression: "{value}", subclass could override this method.
*
* @param valueExpression
* @return
*/
protected boolean isExcludedValue(String valueExpression) {
return StringUtils.hasText(valueExpression) &&
valueExpression.startsWith("{")
&& valueExpression.endsWith("}");
}
@Override
public String getName() {
return this.name;
}
@Override
public T getValue() {
return this.value;
}
@Override
public boolean isNegated() {
return this.negated;
}
public final boolean match(HttpRequest request) {
boolean isMatch;
if (this.value != null) {
isMatch = matchValue(request);
} else {
isMatch = matchName(request);
}
return (this.negated ? !isMatch : isMatch);
}
protected abstract boolean isCaseSensitiveName();
protected abstract T parseValue(String valueExpression);
protected abstract boolean matchName(HttpRequest request);
protected abstract boolean matchValue(HttpRequest request);
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
AbstractNameValueExpression<?> that = (AbstractNameValueExpression<?>) other;
return ((isCaseSensitiveName() ? this.name.equals(that.name) : this.name.equalsIgnoreCase(that.name)) &&
ObjectUtils.nullSafeEquals(this.value, that.value) && this.negated == that.negated);
}
@Override
public int hashCode() {
int result = (isCaseSensitiveName() ? this.name.hashCode() : this.name.toLowerCase().hashCode());
result = 31 * result + (this.value != null ? this.value.hashCode() : 0);
result = 31 * result + (this.negated ? 1 : 0);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (this.value != null) {
builder.append(this.name);
if (this.negated) {
builder.append('!');
}
builder.append('=');
builder.append(this.value);
} else {
if (this.negated) {
builder.append('!');
}
builder.append(this.name);
}
return builder.toString();
}
}

@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Composite {@link HttpRequestMatcher} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class CompositeHttpRequestMatcher extends AbstractHttpRequestMatcher {
private final List<HttpRequestMatcher> matchers = new LinkedList<>();
public CompositeHttpRequestMatcher(HttpRequestMatcher... matchers) {
this.matchers.addAll(Arrays.asList(matchers));
}
public CompositeHttpRequestMatcher and(HttpRequestMatcher matcher) {
this.matchers.add(matcher);
return this;
}
@Override
public boolean match(HttpRequest request) {
for (HttpRequestMatcher matcher : matchers) {
if (!matcher.match(request)) {
return false;
}
}
return true;
}
protected List<HttpRequestMatcher> getMatchers() {
return this.matchers;
}
@Override
protected Collection<?> getContent() {
List<Object> content = new LinkedList<>();
for (HttpRequestMatcher matcher : getMatchers()) {
if (matcher instanceof AbstractHttpRequestMatcher) {
content.addAll(((AbstractHttpRequestMatcher) matcher).getContent());
}
}
return content;
}
@Override
protected String getToStringInfix() {
return " && ";
}
}

@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.MediaType;
/**
* Parses and matches a single media type expression to a request's 'Content-Type' header.
* <p>
* The source code is scratched from
* org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition.ConsumeMediaTypeExpression
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
*/
class ConsumeMediaTypeExpression extends AbstractMediaTypeExpression {
ConsumeMediaTypeExpression(String expression) {
super(expression);
}
ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
public final boolean match(MediaType contentType) {
boolean match = getMediaType().includes(contentType);
return (!isNegated() ? match : !match);
}
}

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.util.ObjectUtils;
/**
* Parses and matches a single header expression to a request.
* <p>
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class HeaderExpression extends AbstractNameValueExpression<String> {
HeaderExpression(String expression) {
super(expression);
}
@Override
protected boolean isCaseSensitiveName() {
return false;
}
@Override
protected String parseValue(String valueExpression) {
return valueExpression;
}
@Override
protected boolean matchName(HttpRequest request) {
HttpHeaders httpHeaders = request.getHeaders();
return httpHeaders.containsKey(this.name);
}
@Override
protected boolean matchValue(HttpRequest request) {
HttpHeaders httpHeaders = request.getHeaders();
String headerValue = httpHeaders.getFirst(this.name);
return ObjectUtils.nullSafeEquals(this.value, headerValue);
}
}

@ -0,0 +1,123 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* {@link HttpRequest} 'Content-Type' header {@link HttpRequestMatcher matcher}
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestConsumersMatcher extends AbstractHttpRequestMatcher {
private final List<ConsumeMediaTypeExpression> expressions;
/**
* Creates a new instance from 0 or more "consumes" expressions.
*
* @param consumes consumes expressions if 0 expressions are provided,
* the condition will match to every request
*/
public HttpRequestConsumersMatcher(String... consumes) {
this(consumes, null);
}
/**
* Creates a new instance with "consumes" and "header" expressions.
* "Header" expressions where the header name is not 'Content-Type' or have
* no header value defined are ignored. If 0 expressions are provided in
* total, the condition will match to every request
*
* @param consumes consumes expressions
* @param headers headers expressions
*/
public HttpRequestConsumersMatcher(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private HttpRequestConsumersMatcher(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
}
@Override
public boolean match(HttpRequest request) {
if (expressions.isEmpty()) {
return true;
}
HttpHeaders httpHeaders = request.getHeaders();
MediaType contentType = httpHeaders.getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
for (ConsumeMediaTypeExpression expression : expressions) {
if (!expression.match(contentType)) {
return false;
}
}
return true;
}
private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, String[] headers) {
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Content-Type".equalsIgnoreCase(expr.name) && expr.value != null) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ConsumeMediaTypeExpression(mediaType, expr.negated));
}
}
}
}
for (String consume : consumes) {
result.add(new ConsumeMediaTypeExpression(consume));
}
return result;
}
@Override
protected Collection<ConsumeMediaTypeExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* {@link HttpRequest} headers {@link HttpRequestMatcher matcher}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestHeadersMatcher extends AbstractHttpRequestMatcher {
private final Set<HeaderExpression> expressions;
public HttpRequestHeadersMatcher(String... headers) {
this.expressions = parseExpressions(headers);
}
private static Set<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<>();
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name) ||
HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(expr.name)) {
continue;
}
expressions.add(expr);
}
return expressions;
}
@Override
public boolean match(HttpRequest request) {
for (HeaderExpression expression : this.expressions) {
if (!expression.match(request)) {
return false;
}
}
return true;
}
@Override
protected Collection<HeaderExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " && ";
}
}

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
/**
* {@link HttpRequest} Matcher
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public interface HttpRequestMatcher {
/**
* Match {@link HttpRequest} or not
*
* @param request The {@link HttpRequest} instance
* @return if matched, return <code>true</code>, or <code>false</code>.
*/
boolean match(HttpRequest request);
}

@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.springframework.http.HttpMethod.resolve;
/**
* {@link HttpRequest} {@link HttpMethod methods} {@link HttpRequestMatcher matcher}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestMethodsMatcher extends AbstractHttpRequestMatcher {
private final Set<HttpMethod> methods;
public HttpRequestMethodsMatcher(String... methods) {
this.methods = resolveHttpMethods(methods);
}
private Set<HttpMethod> resolveHttpMethods(String[] methods) {
Set<HttpMethod> httpMethods = new LinkedHashSet<>(methods.length);
for (String method : methods) {
if (!StringUtils.hasText(method)) {
continue;
}
HttpMethod httpMethod = resolve(method);
httpMethods.add(httpMethod);
}
return httpMethods;
}
public Set<HttpMethod> getMethods() {
return methods;
}
@Override
public boolean match(HttpRequest request) {
boolean matched = false;
HttpMethod httpMethod = request.getMethod();
if (httpMethod != null) {
for (HttpMethod method : getMethods()) {
if (httpMethod.equals(method)) {
matched = true;
break;
}
}
}
return matched;
}
@Override
protected Collection<HttpMethod> getContent() {
return methods;
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* {@link HttpRequest} parameters {@link HttpRequestMatcher matcher}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestParamsMatcher extends AbstractHttpRequestMatcher {
private final Set<ParamExpression> expressions;
/**
* @param params The pattern of params :
* <ul>
* <li>name=value</li>
* <li>name</li>
* </ul>
*/
public HttpRequestParamsMatcher(String... params) {
this.expressions = parseExpressions(params);
}
@Override
public boolean match(HttpRequest request) {
for (ParamExpression paramExpression : expressions) {
if (paramExpression.match(request)) {
return true;
}
}
return false;
}
private static Set<ParamExpression> parseExpressions(String... params) {
Set<ParamExpression> expressions = new LinkedHashSet<>();
for (String param : params) {
expressions.add(new ParamExpression(param));
}
return expressions;
}
@Override
protected Collection<ParamExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " && ";
}
}

@ -0,0 +1,117 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* {@link HttpRequest} {@link URI} {@link HttpRequestMatcher matcher}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestPathMatcher extends AbstractHttpRequestMatcher {
private final Set<String> patterns;
private final PathMatcher pathMatcher;
public HttpRequestPathMatcher(String... patterns) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.pathMatcher = new AntPathMatcher();
}
@Override
public boolean match(HttpRequest request) {
List<String> matches = getMatchingPatterns(request);
return !matches.isEmpty();
}
public List<String> getMatchingPatterns(HttpRequest request) {
String path = getPath(request);
List<String> matches = getMatchingPatterns(path);
return matches;
}
public List<String> getMatchingPatterns(String lookupPath) {
List<String> matches = new ArrayList<>();
for (String pattern : this.patterns) {
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches.add(match);
}
}
if (matches.size() > 1) {
matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
}
return matches;
}
private String getMatchingPattern(String pattern, String lookupPath) {
if (pattern.equals(lookupPath)) {
return pattern;
}
boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
return pattern + "/";
}
return null;
}
private String getPath(HttpRequest request) {
URI uri = request.getURI();
return uri.getPath();
}
private static Set<String> prependLeadingSlash(String[] patterns) {
Set<String> result = new LinkedHashSet<>(patterns.length);
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
@Override
protected Collection<String> getContent() {
return this.patterns;
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -0,0 +1,119 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* {@link HttpRequest} 'Accept' header {@link HttpRequestMatcher matcher}
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestProducesMatcher extends AbstractHttpRequestMatcher {
private final List<ProduceMediaTypeExpression> expressions;
/**
* Creates a new instance from "produces" expressions. If 0 expressions
* are provided in total, this condition will match to any request.
*
* @param produces produces expressions
*/
public HttpRequestProducesMatcher(String... produces) {
this(produces, null);
}
/**
* Creates a new instance with "produces" and "header" expressions. "Header"
* expressions where the header name is not 'Accept' or have no header value
* defined are ignored. If 0 expressions are provided in total, this condition
* will match to any request.
*
* @param produces produces expressions
* @param headers headers expressions
*/
public HttpRequestProducesMatcher(String[] produces, String[] headers) {
this(parseExpressions(produces, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private HttpRequestProducesMatcher(Collection<ProduceMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
}
@Override
public boolean match(HttpRequest request) {
if (expressions.isEmpty()) {
return true;
}
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptedMediaTypes = httpHeaders.getAccept();
for (ProduceMediaTypeExpression expression : expressions) {
if (!expression.match(acceptedMediaTypes)) {
return false;
}
}
return true;
}
private static Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, String[] headers) {
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name) && expr.value != null) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ProduceMediaTypeExpression(mediaType, expr.negated));
}
}
}
}
for (String produce : produces) {
result.add(new ProduceMediaTypeExpression(produce));
}
return result;
}
@Override
protected Collection<ProduceMediaTypeExpression> getContent() {
return expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.MediaType;
/**
* A contract for media type expressions (e.g. "text/plain", "!text/plain") as
* defined in the for "consumes" and "produces".
* <p>
* The source code is scratched from org.springframework.web.servlet.mvc.condition.MediaTypeExpression
*
* @author Rossen Stoyanchev
*/
interface MediaTypeExpression {
MediaType getMediaType();
boolean isNegated();
}

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
/**
* A contract for {@code "name!=value"} style expression used to specify request
* parameters and request header in HTTP request
* <p>
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.NameValueExpression
*
* @param <T> the value type
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
interface NameValueExpression<T> {
String getName();
T getValue();
boolean isNegated();
}

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import static org.springframework.cloud.alibaba.dubbo.http.util.HttpUtils.getParameters;
/**
* Parses and matches a single param expression to a request.
* <p>
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.ParamsRequestCondition.ParamExpression
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class ParamExpression extends AbstractNameValueExpression<String> {
ParamExpression(String expression) {
super(expression);
}
@Override
protected boolean isCaseSensitiveName() {
return true;
}
@Override
protected String parseValue(String valueExpression) {
return valueExpression;
}
@Override
protected boolean matchName(HttpRequest request) {
MultiValueMap<String, String> parametersMap = getParameters(request);
return parametersMap.containsKey(this.name);
}
@Override
protected boolean matchValue(HttpRequest request) {
MultiValueMap<String, String> parametersMap = getParameters(request);
String parameterValue = parametersMap.getFirst(this.name);
return ObjectUtils.nullSafeEquals(this.value, parameterValue);
}
}

@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.http.MediaType;
import java.util.List;
/**
* Parses and matches a single media type expression to a request's 'Accept' header.
* <p>
* The source code is scratched from
* org.springframework.web.servlet.mvc.condition.ProducesRequestCondition.ProduceMediaTypeExpression
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
*/
class ProduceMediaTypeExpression extends AbstractMediaTypeExpression {
ProduceMediaTypeExpression(String expression) {
super(expression);
}
ProduceMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
public final boolean match(List<MediaType> acceptedMediaTypes) {
boolean match = matchMediaType(acceptedMediaTypes);
return (!isNegated() ? match : !match);
}
private boolean matchMediaType(List<MediaType> acceptedMediaTypes) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
}
}
return false;
}
}

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import static org.springframework.cloud.alibaba.dubbo.http.util.HttpUtils.toNameAndValues;
/**
* {@link RequestMetadata} {@link HttpRequestMatcher} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestMetadataMatcher extends CompositeHttpRequestMatcher {
public RequestMetadataMatcher(RequestMetadata metadata) {
super(
// method
new HttpRequestMethodsMatcher(metadata.getMethod()),
// url
new HttpRequestPathMatcher(metadata.getPath()),
// params
new HttpRequestParamsMatcher(toNameAndValues(metadata.getParams())),
// headers
new HttpRequestHeadersMatcher(toNameAndValues(metadata.getHeaders())),
// consumes
new HttpRequestConsumersMatcher(metadata.getConsumes().toArray(new String[0])),
// produces
new HttpRequestProducesMatcher(metadata.getProduces().toArray(new String[0]))
);
}
}

@ -0,0 +1,228 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.util;
import org.springframework.cloud.alibaba.dubbo.http.converter.HttpMessageConverterHolder;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static java.util.Collections.unmodifiableList;
/**
* {@link HttpMessageConverter} Resolver
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpMessageConverterResolver {
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
private final List<HttpMessageConverter<?>> messageConverters;
private final List<MediaType> allSupportedMediaTypes;
private final ClassLoader classLoader;
public HttpMessageConverterResolver(List<HttpMessageConverter<?>> messageConverters, ClassLoader classLoader) {
this.messageConverters = messageConverters;
this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters);
this.classLoader = classLoader;
}
public HttpMessageConverterHolder resolve(HttpRequest request, Class<?> parameterType) {
HttpMessageConverterHolder httpMessageConverterHolder = null;
HttpHeaders httpHeaders = request.getHeaders();
MediaType contentType = httpHeaders.getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
if (genericConverter.canRead(parameterType, parameterType, contentType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(contentType, converter);
break;
}
} else {
if (converter.canRead(parameterType, contentType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(contentType, converter);
break;
}
}
}
return httpMessageConverterHolder;
}
/**
* Resolve the most match {@link HttpMessageConverter} from {@link RequestMetadata}
*
* @param requestMetadata {@link RequestMetadata}
* @param restMethodMetadata {@link RestMethodMetadata}
* @return
*/
public HttpMessageConverterHolder resolve(RequestMetadata requestMetadata, RestMethodMetadata
restMethodMetadata) {
HttpMessageConverterHolder httpMessageConverterHolder = null;
Class<?> returnValueClass = resolveReturnValueClass(restMethodMetadata);
/**
* @see AbstractMessageConverterMethodProcessor#writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)
*/
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(requestMetadata);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(restMethodMetadata, returnValueClass);
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
return httpMessageConverterHolder;
}
List<MediaType> mediaTypes = new ArrayList<>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(selectedMediaType, messageConverter);
break;
}
}
}
return httpMessageConverterHolder;
}
public List<MediaType> getAllSupportedMediaTypes() {
return unmodifiableList(allSupportedMediaTypes);
}
private Class<?> resolveReturnValueClass(RestMethodMetadata restMethodMetadata) {
String returnClassName = restMethodMetadata.getMethod().getReturnType();
return ClassUtils.resolveClassName(returnClassName, classLoader);
}
/**
* Resolve the {@link MediaType media-types}
*
* @param requestMetadata {@link RequestMetadata} from client side
* @return non-null {@link List}
*/
private List<MediaType> getAcceptableMediaTypes(RequestMetadata requestMetadata) {
return requestMetadata.getProduceMediaTypes();
}
/**
* Returns
* the media types that can be produced: <ul> <li>The producible media types specified in the request mappings, or
* <li>Media types of configured converters that can write the specific return value, or <li>{@link MediaType#ALL}
* </ul>
*
* @param restMethodMetadata {@link RestMethodMetadata} from server side
* @param returnValueClass the class of return value
* @return non-null {@link List}
*/
private List<MediaType> getProducibleMediaTypes(RestMethodMetadata restMethodMetadata, Class<?>
returnValueClass) {
RequestMetadata serverRequestMetadata = restMethodMetadata.getRequest();
List<MediaType> mediaTypes = serverRequestMetadata.getProduceMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) { // Empty
return mediaTypes;
} else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
} else {
return Collections.singletonList(MediaType.ALL);
}
}
/**
* Return the media types
* supported by all provided message converters sorted by specificity via {@link
* MediaType#sortBySpecificity(List)}.
*
* @param messageConverters
* @return
*/
private List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes);
MediaType.sortBySpecificity(result);
return unmodifiableList(result);
}
/**
* Return the more specific of the acceptable and the producible media types
* with the q-value of the former.
*/
private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produceType) {
MediaType produceTypeToUse = produceType.copyQualityValue(acceptType);
return (MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceTypeToUse) <= 0 ? acceptType : produceTypeToUse);
}
}

@ -0,0 +1,239 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.util;
import org.springframework.http.HttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.springframework.util.StringUtils.delimitedListToStringArray;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.trimAllWhitespace;
/**
* Http Utilities class
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class HttpUtils {
private static final String UTF_8 = "UTF-8";
private static final String EQUAL = "=";
private static final String AND = "&";
private static final String SEMICOLON = ";";
private static final String QUESTION_MASK = "?";
/**
* The empty value
*/
private static final String EMPTY_VALUE = "";
/**
* Normalize path:
* <ol>
* <li>To remove query string if presents</li>
* <li>To remove duplicated slash("/") if exists</li>
* </ol>
*
* @param path path to be normalized
* @return a normalized path if required
*/
public static String normalizePath(String path) {
if (!hasText(path)) {
return path;
}
String normalizedPath = path;
int index = normalizedPath.indexOf(QUESTION_MASK);
if (index > -1) {
normalizedPath = normalizedPath.substring(0, index);
}
return StringUtils.replace(normalizedPath, "//", "/");
}
/**
* Get Parameters from the specified {@link HttpRequest request}
*
* @param request the specified {@link HttpRequest request}
* @return
*/
public static MultiValueMap<String, String> getParameters(HttpRequest request) {
URI uri = request.getURI();
return getParameters(uri.getQuery());
}
/**
* Get Parameters from the specified query string.
* <p>
*
* @param queryString The query string
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(String queryString) {
return getParameters(delimitedListToStringArray(queryString, AND));
}
/**
* Get Parameters from the specified pairs of name-value.
* <p>
*
* @param pairs The pairs of name-value
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(Iterable<String> pairs) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
if (pairs != null) {
for (String pair : pairs) {
String[] nameAndValue = delimitedListToStringArray(pair, EQUAL);
String name = decode(nameAndValue[0]);
String value = nameAndValue.length < 2 ? null : nameAndValue[1];
value = decode(value);
addParam(parameters, name, value);
}
}
return parameters;
}
/**
* Get Parameters from the specified pairs of name-value.
* <p>
*
* @param pairs The pairs of name-value
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(String... pairs) {
return getParameters(Arrays.asList(pairs));
}
// /**
// * Parse a read-only {@link MultiValueMap} of {@link HttpCookie} from {@link HttpHeaders}
// *
// * @param httpHeaders {@link HttpHeaders}
// * @return non-null, the key is a cookie name , the value is {@link HttpCookie}
// */
// public static MultiValueMap<String, HttpCookie> parseCookies(HttpHeaders httpHeaders) {
//
// String cookie = httpHeaders.getFirst(COOKIE);
//
// String[] cookieNameAndValues = StringUtils.delimitedListToStringArray(cookie, SEMICOLON);
//
// MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>(cookieNameAndValues.length);
//
// for (String cookeNameAndValue : cookieNameAndValues) {
// String[] nameAndValue = delimitedListToStringArray(trimWhitespace(cookeNameAndValue), EQUAL);
// String name = nameAndValue[0];
// String value = nameAndValue.length < 2 ? null : nameAndValue[1];
// HttpCookie httpCookie = new HttpCookie(name, value);
// cookies.add(name, httpCookie);
// }
//
// return cookies;
// }
/**
* To the name and value line sets
*
* @param nameAndValuesMap {@link MultiValueMap} the map of name and values
* @return non-null
*/
public static Set<String> toNameAndValuesSet(Map<String, List<String>> nameAndValuesMap) {
Set<String> nameAndValues = new LinkedHashSet<>();
for (Map.Entry<String, List<String>> entry : nameAndValuesMap.entrySet()) {
String name = entry.getKey();
List<String> values = entry.getValue();
for (String value : values) {
String nameAndValue = name + EQUAL + value;
nameAndValues.add(nameAndValue);
}
}
return nameAndValues;
}
public static String[] toNameAndValues(Map<String, List<String>> nameAndValuesMap) {
return toNameAndValuesSet(nameAndValuesMap).toArray(new String[0]);
}
/**
* Generate a string of query string from the specified request parameters {@link Map}
*
* @param params the specified request parameters {@link Map}
* @return non-null
*/
public static String toQueryString(Map<String, List<String>> params) {
StringBuilder builder = new StringBuilder();
for (String line : toNameAndValuesSet(params)) {
builder.append(line).append(AND);
}
return builder.toString();
}
/**
* Decode value
*
* @param value the value requires to decode
* @return the decoded value
*/
public static String decode(String value) {
if (value == null) {
return value;
}
String decodedValue = value;
try {
decodedValue = URLDecoder.decode(value, UTF_8);
} catch (UnsupportedEncodingException ex) {
}
return decodedValue;
}
/**
* encode value
*
* @param value the value requires to encode
* @return the encoded value
*/
public static String encode(String value) {
String encodedValue = value;
try {
encodedValue = URLEncoder.encode(value, UTF_8);
} catch (UnsupportedEncodingException ex) {
}
return encodedValue;
}
private static void addParam(MultiValueMap<String, String> paramsMap, String name, String value) {
String paramValue = trimAllWhitespace(value);
if (!StringUtils.hasText(paramValue)) {
paramValue = EMPTY_VALUE;
}
paramsMap.add(trimAllWhitespace(name), paramValue);
}
}

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import java.util.Objects;
/**
* Dubbo Service Metadata
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboServiceMetadata {
private final ServiceRestMetadata serviceRestMetadata;
private final RestMethodMetadata restMethodMetadata;
public DubboServiceMetadata(ServiceRestMetadata serviceRestMetadata, RestMethodMetadata restMethodMetadata) {
this.serviceRestMetadata = serviceRestMetadata;
this.restMethodMetadata = restMethodMetadata;
}
public ServiceRestMetadata getServiceRestMetadata() {
return serviceRestMetadata;
}
public RestMethodMetadata getRestMethodMetadata() {
return restMethodMetadata;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DubboServiceMetadata)) return false;
DubboServiceMetadata that = (DubboServiceMetadata) o;
return Objects.equals(serviceRestMetadata, that.serviceRestMetadata) &&
Objects.equals(restMethodMetadata, that.restMethodMetadata);
}
@Override
public int hashCode() {
return Objects.hash(serviceRestMetadata, restMethodMetadata);
}
}

@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import java.util.Objects;
/**
* {@link DubboTransported @DubboTransported} Metadata
*/
public class DubboTransportedMetadata {
private String protocol;
private String cluster;
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getCluster() {
return cluster;
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DubboTransportedMetadata)) return false;
DubboTransportedMetadata that = (DubboTransportedMetadata) o;
return Objects.equals(protocol, that.protocol) &&
Objects.equals(cluster, that.cluster);
}
@Override
public int hashCode() {
return Objects.hash(protocol, cluster);
}
}

@ -0,0 +1,106 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
/**
* {@link MethodMetadata} annotated {@link DubboTransported @DubboTransported}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboTransportedMethodMetadata {
private final DubboTransportedMetadata dubboTransportedMetadata;
private final MethodMetadata methodMetadata;
public DubboTransportedMethodMetadata(Method method) {
this.methodMetadata = new MethodMetadata(method);
this.dubboTransportedMetadata = new DubboTransportedMetadata();
}
public String getProtocol() {
return dubboTransportedMetadata.getProtocol();
}
public void setProtocol(String protocol) {
dubboTransportedMetadata.setProtocol(protocol);
}
public String getCluster() {
return dubboTransportedMetadata.getCluster();
}
public void setCluster(String cluster) {
dubboTransportedMetadata.setCluster(cluster);
}
public String getName() {
return methodMetadata.getName();
}
public void setName(String name) {
methodMetadata.setName(name);
}
public String getReturnType() {
return methodMetadata.getReturnType();
}
public void setReturnType(String returnType) {
methodMetadata.setReturnType(returnType);
}
public List<MethodParameterMetadata> getParams() {
return methodMetadata.getParams();
}
public void setParams(List<MethodParameterMetadata> params) {
methodMetadata.setParams(params);
}
public Method getMethod() {
return methodMetadata.getMethod();
}
public DubboTransportedMetadata getDubboTransportedMetadata() {
return dubboTransportedMetadata;
}
public MethodMetadata getMethodMetadata() {
return methodMetadata;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DubboTransportedMethodMetadata)) return false;
DubboTransportedMethodMetadata that = (DubboTransportedMethodMetadata) o;
return Objects.equals(dubboTransportedMetadata, that.dubboTransportedMetadata) &&
Objects.equals(methodMetadata, that.methodMetadata);
}
@Override
public int hashCode() {
return Objects.hash(dubboTransportedMetadata, methodMetadata);
}
}

@ -17,7 +17,8 @@
package org.springframework.cloud.alibaba.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.apache.commons.lang3.ClassUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@ -32,10 +33,12 @@ import java.util.Objects;
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MethodMetadata {
private String name;
@JsonProperty("return-type")
private String returnType;
private List<MethodParameterMetadata> params;
@ -49,7 +52,7 @@ public class MethodMetadata {
public MethodMetadata(Method method) {
this.name = method.getName();
this.returnType = ClassUtils.getName(method.getReturnType());
this.returnType = method.getReturnType().getName();
this.params = initParameters(method);
this.method = method;
}
@ -119,4 +122,14 @@ public class MethodMetadata {
public int hashCode() {
return Objects.hash(name, returnType, params);
}
@Override
public String toString() {
return "MethodMetadata{" +
"name='" + name + '\'' +
", returnType='" + returnType + '\'' +
", params=" + params +
", method=" + method +
'}';
}
}

@ -16,6 +16,8 @@
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.lang.reflect.Method;
import java.util.Objects;
@ -24,6 +26,7 @@ import java.util.Objects;
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MethodParameterMetadata {
private int index;
@ -70,4 +73,13 @@ public class MethodParameterMetadata {
public int hashCode() {
return Objects.hash(index, name, type);
}
@Override
public String toString() {
return "MethodParameterMetadata{" +
"index=" + index +
", name='" + name + '\'' +
", type='" + type + '\'' +
'}';
}
}

@ -16,11 +16,29 @@
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import feign.RequestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import static org.springframework.cloud.alibaba.dubbo.http.util.HttpUtils.normalizePath;
import static org.springframework.http.MediaType.parseMediaTypes;
/**
* Request Metadata
@ -31,20 +49,26 @@ public class RequestMetadata {
private String method;
private String url;
private String path;
@JsonProperty("params")
private MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
private Map<String, Collection<String>> queries;
@JsonProperty("headers")
private HttpHeaders headers = new HttpHeaders();
private Map<String, Collection<String>> headers;
private Set<String> consumes = new LinkedHashSet<>();
private Set<String> produces = new LinkedHashSet<>();
public RequestMetadata() {
}
public RequestMetadata(RequestTemplate requestTemplate) {
this.method = requestTemplate.method();
this.url = requestTemplate.url();
this.queries = requestTemplate.queries();
this.headers = requestTemplate.headers();
setMethod(requestTemplate.method());
setPath(requestTemplate.url());
params(requestTemplate.queries());
headers(requestTemplate.headers());
}
public String getMethod() {
@ -52,46 +76,193 @@ public class RequestMetadata {
}
public void setMethod(String method) {
this.method = method;
this.method = method.toUpperCase();
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = normalizePath(path);
}
public String getUrl() {
return url;
public MultiValueMap<String, String> getParams() {
return params;
}
public void setUrl(String url) {
this.url = url;
public void setParams(Map<String, List<String>> params) {
params(params);
}
public Map<String, Collection<String>> getHeaders() {
public Map<String, List<String>> getHeaders() {
return headers;
}
public void setHeaders(Map<String, Collection<String>> headers) {
this.headers = headers;
public void setHeaders(Map<String, List<String>> headers) {
headers(headers);
}
public Set<String> getConsumes() {
return consumes;
}
public void setConsumes(Set<String> consumes) {
this.consumes = consumes;
}
public Set<String> getProduces() {
return produces;
}
public Map<String, Collection<String>> getQueries() {
return queries;
public void setProduces(Set<String> produces) {
this.produces = produces;
}
public void setQueries(Map<String, Collection<String>> queries) {
this.queries = queries;
// @JsonIgnore properties
@JsonIgnore
public Set<String> getParamNames() {
return params.keySet();
}
@JsonIgnore
public Set<String> getHeaderNames() {
return headers.keySet();
}
@JsonIgnore
public List<MediaType> getConsumeMediaTypes() {
return toMediaTypes(consumes);
}
@JsonIgnore
public List<MediaType> getProduceMediaTypes() {
return toMediaTypes(produces);
}
public String getParameter(String name) {
return this.params.getFirst(name);
}
public String getHeader(String name) {
return this.headers.getFirst(name);
}
public RequestMetadata addParam(String name, String value) {
add(name, value, this.params);
return this;
}
public RequestMetadata addHeader(String name, String value) {
add(name, value, this.headers);
return this;
}
private <T extends Collection<String>> RequestMetadata params(Map<String, T> params) {
addAll(params, this.params);
return this;
}
private <T extends Collection<String>> RequestMetadata headers(Map<String, T> headers) {
if (!CollectionUtils.isEmpty(headers)) {
HttpHeaders httpHeaders = new HttpHeaders();
// Add all headers
addAll(headers, httpHeaders);
// Handles "Content-Type" and "Accept" headers if present
mediaTypes(httpHeaders, HttpHeaders.CONTENT_TYPE, this.consumes);
mediaTypes(httpHeaders, HttpHeaders.ACCEPT, this.produces);
this.headers.putAll(httpHeaders);
}
return this;
}
/**
* Get the best matched {@link RequestMetadata} via specified {@link RequestMetadata}
*
* @param requestMetadataMap the source of {@link NavigableMap}
* @param requestMetadata the match object
* @return if not matched, return <code>null</code>
*/
public static RequestMetadata getBestMatch(NavigableMap<RequestMetadata, RequestMetadata> requestMetadataMap,
RequestMetadata requestMetadata) {
RequestMetadata key = requestMetadata;
RequestMetadata result = requestMetadataMap.get(key);
if (result == null) {
SortedMap<RequestMetadata, RequestMetadata> headMap = requestMetadataMap.headMap(key, true);
result = headMap.isEmpty() ? null : requestMetadataMap.get(headMap.lastKey());
}
return result;
}
private static void add(String key, String value, MultiValueMap<String, String> destination) {
destination.add(key, value);
}
private static <T extends Collection<String>> void addAll(Map<String, T> source,
MultiValueMap<String, String> destination) {
for (Map.Entry<String, T> entry : source.entrySet()) {
String key = entry.getKey();
for (String value : entry.getValue()) {
add(key, value, destination);
}
}
}
private static void mediaTypes(HttpHeaders httpHeaders, String headerName, Collection<String> destination) {
List<String> value = httpHeaders.get(headerName);
List<MediaType> mediaTypes = parseMediaTypes(value);
destination.addAll(toMediaTypeValues(mediaTypes));
}
private static List<String> toMediaTypeValues(List<MediaType> mediaTypes) {
List<String> list = new ArrayList<>(mediaTypes.size());
for (MediaType mediaType : mediaTypes) {
list.add(mediaType.toString());
}
return list;
}
private static List<MediaType> toMediaTypes(Collection<String> mediaTypeValues) {
if (mediaTypeValues.isEmpty()) {
return Collections.singletonList(MediaType.ALL);
}
return parseMediaTypes(new LinkedList<>(mediaTypeValues));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!(o instanceof RequestMetadata)) return false;
RequestMetadata that = (RequestMetadata) o;
return Objects.equals(method, that.method) &&
Objects.equals(url, that.url) &&
Objects.equals(queries, that.queries) &&
Objects.equals(headers, that.headers);
Objects.equals(path, that.path) &&
Objects.equals(consumes, that.consumes) &&
Objects.equals(produces, that.produces) &&
// Metadata should not compare the values
Objects.equals(getParamNames(), that.getParamNames()) &&
Objects.equals(getHeaderNames(), that.getHeaderNames());
}
@Override
public int hashCode() {
return Objects.hash(method, url, queries, headers);
// The values of metadata should not use for the hashCode() method
return Objects.hash(method, path, consumes, produces, getParamNames(), getHeaderNames());
}
@Override
public String toString() {
return "RequestMetadata{" +
"method='" + method + '\'' +
", path='" + path + '\'' +
", params=" + params +
", headers=" + headers +
", consumes=" + consumes +
", produces=" + produces +
'}';
}
}

@ -16,21 +16,75 @@
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.core.ResolvableType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Method Request Metadata
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RestMethodMetadata {
private MethodMetadata method;
private RequestMetadata request;
@JsonProperty("url-index")
private Integer urlIndex;
@JsonProperty("setBody-index")
private Integer bodyIndex;
@JsonProperty("header-map-index")
private Integer headerMapIndex;
@JsonProperty("query-map-index")
private Integer queryMapIndex;
@JsonProperty("query-map-encoded")
private boolean queryMapEncoded;
@JsonProperty("return-type")
private String returnType;
@JsonProperty("setBody-type")
private String bodyType;
@JsonProperty("index-to-name")
private Map<Integer, Collection<String>> indexToName;
@JsonProperty("form-params")
private List<String> formParams;
@JsonProperty("index-to-encoded")
private Map<Integer, Boolean> indexToEncoded;
public RestMethodMetadata() {
}
public RestMethodMetadata(feign.MethodMetadata methodMetadata) {
this.request = new RequestMetadata(methodMetadata.template());
this.urlIndex = methodMetadata.urlIndex();
this.bodyIndex = methodMetadata.bodyIndex();
this.headerMapIndex = methodMetadata.headerMapIndex();
this.queryMapEncoded = methodMetadata.queryMapEncoded();
this.queryMapEncoded = methodMetadata.queryMapEncoded();
this.returnType = getClassName(methodMetadata.returnType());
this.bodyType = getClassName(methodMetadata.bodyType());
this.indexToName = methodMetadata.indexToName();
this.formParams = methodMetadata.formParams();
this.indexToEncoded = methodMetadata.indexToEncoded();
}
public MethodMetadata getMethod() {
return method;
}
@ -55,18 +109,126 @@ public class RestMethodMetadata {
this.indexToName = indexToName;
}
public Integer getUrlIndex() {
return urlIndex;
}
public void setUrlIndex(Integer urlIndex) {
this.urlIndex = urlIndex;
}
public Integer getBodyIndex() {
return bodyIndex;
}
public void setBodyIndex(Integer bodyIndex) {
this.bodyIndex = bodyIndex;
}
public Integer getHeaderMapIndex() {
return headerMapIndex;
}
public void setHeaderMapIndex(Integer headerMapIndex) {
this.headerMapIndex = headerMapIndex;
}
public Integer getQueryMapIndex() {
return queryMapIndex;
}
public void setQueryMapIndex(Integer queryMapIndex) {
this.queryMapIndex = queryMapIndex;
}
public boolean isQueryMapEncoded() {
return queryMapEncoded;
}
public void setQueryMapEncoded(boolean queryMapEncoded) {
this.queryMapEncoded = queryMapEncoded;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public String getBodyType() {
return bodyType;
}
public void setBodyType(String bodyType) {
this.bodyType = bodyType;
}
public List<String> getFormParams() {
return formParams;
}
public void setFormParams(List<String> formParams) {
this.formParams = formParams;
}
public Map<Integer, Boolean> getIndexToEncoded() {
return indexToEncoded;
}
public void setIndexToEncoded(Map<Integer, Boolean> indexToEncoded) {
this.indexToEncoded = indexToEncoded;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!(o instanceof RestMethodMetadata)) return false;
RestMethodMetadata that = (RestMethodMetadata) o;
return Objects.equals(method, that.method) &&
return queryMapEncoded == that.queryMapEncoded &&
Objects.equals(method, that.method) &&
Objects.equals(request, that.request) &&
Objects.equals(indexToName, that.indexToName);
Objects.equals(urlIndex, that.urlIndex) &&
Objects.equals(bodyIndex, that.bodyIndex) &&
Objects.equals(headerMapIndex, that.headerMapIndex) &&
Objects.equals(queryMapIndex, that.queryMapIndex) &&
Objects.equals(returnType, that.returnType) &&
Objects.equals(bodyType, that.bodyType) &&
Objects.equals(indexToName, that.indexToName) &&
Objects.equals(formParams, that.formParams) &&
Objects.equals(indexToEncoded, that.indexToEncoded);
}
@Override
public int hashCode() {
return Objects.hash(method, request, indexToName);
return Objects.hash(method, request, urlIndex, bodyIndex, headerMapIndex, queryMapIndex, queryMapEncoded,
returnType, bodyType, indexToName, formParams, indexToEncoded);
}
private String getClassName(Type type) {
if (type == null) {
return null;
}
ResolvableType resolvableType = ResolvableType.forType(type);
return resolvableType.resolve().getName();
}
@Override
public String toString() {
return "RestMethodMetadata{" +
"method=" + method +
", request=" + request +
", urlIndex=" + urlIndex +
", bodyIndex=" + bodyIndex +
", headerMapIndex=" + headerMapIndex +
", queryMapIndex=" + queryMapIndex +
", queryMapEncoded=" + queryMapEncoded +
", returnType='" + returnType + '\'' +
", bodyType='" + bodyType + '\'' +
", indexToName=" + indexToName +
", formParams=" + formParams +
", indexToEncoded=" + indexToEncoded +
'}';
}
}

@ -16,6 +16,9 @@
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Objects;
import java.util.Set;
/**
@ -24,6 +27,7 @@ import java.util.Set;
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see RestMethodMetadata
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ServiceRestMetadata {
private String name;
@ -49,18 +53,14 @@ public class ServiceRestMetadata {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!(o instanceof ServiceRestMetadata)) return false;
ServiceRestMetadata that = (ServiceRestMetadata) o;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return meta != null ? meta.equals(that.meta) : that.meta == null;
return Objects.equals(name, that.name) &&
Objects.equals(meta, that.meta);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (meta != null ? meta.hashCode() : 0);
return result;
return Objects.hash(name, meta);
}
}

@ -16,25 +16,28 @@
*/
package org.springframework.cloud.alibaba.dubbo.metadata.repository;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.rpc.service.GenericService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
import org.springframework.cloud.alibaba.dubbo.http.matcher.RequestMetadataMatcher;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboServiceMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.service.MetadataConfigService;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataConfigService;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataConfigServiceProxy;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Repository;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceGroup;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceInterface;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceSegments;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceVersion;
import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder;
import static org.springframework.util.CollectionUtils.isEmpty;
/**
* Dubbo Service Metadata {@link Repository}
@ -44,75 +47,143 @@ import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegist
@Repository
public class DubboServiceMetadataRepository {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* Key is application name
* Value is Map<RequestMetadata, GenericService>
* Value is Map<RequestMetadata, DubboServiceMetadata>
*/
private Map<String, Map<RequestMetadata, GenericService>> genericServicesRepository = new HashMap<>();
private Map<String, Map<RequestMetadata, MethodMetadata>> methodMetadataRepository = new HashMap<>();
private Map<String, Map<RequestMetadataMatcher, DubboServiceMetadata>> repository = newHashMap();
@Autowired
private MetadataConfigService metadataConfigService;
private DubboMetadataConfigServiceProxy dubboMetadataConfigServiceProxy;
@Value("${dubbo.target.protocol:dubbo}")
private String targetProtocol;
@Value("${dubbo.target.cluster:failover}")
private String targetCluster;
/**
* Initialize the specified service's Dubbo Service Metadata
*
* @param serviceName the service name
*/
public void initialize(String serviceName) {
public void updateMetadata(String serviceName) {
if (repository.containsKey(serviceName)) {
return;
}
Map<RequestMetadata, GenericService> genericServicesMap = genericServicesRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
Set<ServiceRestMetadata> serviceRestMetadataSet = getServiceRestMetadataSet(serviceName);
Map<RequestMetadata, MethodMetadata> methodMetadataMap = methodMetadataRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
if (isEmpty(serviceRestMetadataSet)) {
if (logger.isWarnEnabled()) {
logger.warn("The Spring application[name : {}] does not expose The REST metadata in the Dubbo services."
, serviceName);
}
return;
}
Set<ServiceRestMetadata> serviceRestMetadataSet = metadataConfigService.getServiceRestMetadata(serviceName);
Map<RequestMetadataMatcher, DubboServiceMetadata> metadataMap = getMetadataMap(serviceName);
for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) {
ReferenceBean<GenericService> referenceBean = adaptReferenceBean(serviceRestMetadata);
serviceRestMetadata.getMeta().forEach(restMethodMetadata -> {
RequestMetadata requestMetadata = restMethodMetadata.getRequest();
genericServicesMap.put(requestMetadata, referenceBean.get());
methodMetadataMap.put(requestMetadata, restMethodMetadata.getMethod());
RequestMetadataMatcher matcher = new RequestMetadataMatcher(requestMetadata);
DubboServiceMetadata metadata = new DubboServiceMetadata(serviceRestMetadata, restMethodMetadata);
metadataMap.put(matcher, metadata);
});
}
if (logger.isInfoEnabled()) {
logger.info("The REST metadata in the dubbo services has been loaded in the Spring application[name : {}]", serviceName);
}
}
public GenericService getGenericService(String serviceName, RequestMetadata requestMetadata) {
return getGenericServicesMap(serviceName).get(requestMetadata);
/**
* Get a {@link DubboServiceMetadata} by the specified service name if {@link RequestMetadata} matched
*
* @param serviceName service name
* @param requestMetadata {@link RequestMetadata} to be matched
* @return {@link DubboServiceMetadata} if matched, or <code>null</code>
*/
public DubboServiceMetadata get(String serviceName, RequestMetadata requestMetadata) {
return match(repository, serviceName, requestMetadata);
}
public MethodMetadata getMethodMetadata(String serviceName, RequestMetadata requestMetadata) {
return getMethodMetadataMap(serviceName).get(requestMetadata);
private <T> T match(Map<String, Map<RequestMetadataMatcher, T>> repository, String serviceName,
RequestMetadata requestMetadata) {
Map<RequestMetadataMatcher, T> map = repository.get(serviceName);
T object = null;
if (!isEmpty(map)) {
RequestMetadataMatcher matcher = new RequestMetadataMatcher(requestMetadata);
object = map.get(matcher);
if (object == null) { // Can't match exactly
// Require to match one by one
HttpRequest request = builder()
.method(requestMetadata.getMethod())
.path(requestMetadata.getPath())
.params(requestMetadata.getParams())
.headers(requestMetadata.getHeaders())
.build();
for (Map.Entry<RequestMetadataMatcher, T> entry : map.entrySet()) {
RequestMetadataMatcher possibleMatcher = entry.getKey();
if (possibleMatcher.match(request)) {
object = entry.getValue();
break;
}
}
}
}
if (object == null) {
if (logger.isWarnEnabled()) {
logger.warn("DubboServiceMetadata can't be found in the Spring application [%s] and %s",
serviceName, requestMetadata);
}
}
return object;
}
private ReferenceBean<GenericService> adaptReferenceBean(ServiceRestMetadata serviceRestMetadata) {
String dubboServiceName = serviceRestMetadata.getName();
String[] segments = getServiceSegments(dubboServiceName);
String interfaceName = getServiceInterface(segments);
String version = getServiceVersion(segments);
String group = getServiceGroup(segments);
ReferenceBean<GenericService> referenceBean = new ReferenceBean<GenericService>();
referenceBean.setGeneric(true);
referenceBean.setInterface(interfaceName);
referenceBean.setVersion(version);
referenceBean.setGroup(group);
referenceBean.setProtocol(targetProtocol);
referenceBean.setCluster(targetCluster);
return referenceBean;
private Map<RequestMetadataMatcher, DubboServiceMetadata> getMetadataMap(String serviceName) {
return getMap(repository, serviceName);
}
private Map<RequestMetadata, GenericService> getGenericServicesMap(String serviceName) {
return genericServicesRepository.getOrDefault(serviceName, Collections.emptyMap());
private Set<ServiceRestMetadata> getServiceRestMetadataSet(String serviceName) {
DubboMetadataConfigService dubboMetadataConfigService = dubboMetadataConfigServiceProxy.newProxy(serviceName);
String serviceRestMetadataJsonConfig = dubboMetadataConfigService.getServiceRestMetadata();
Set<ServiceRestMetadata> metadata;
try {
metadata = objectMapper.readValue(serviceRestMetadataJsonConfig,
TypeFactory.defaultInstance().constructCollectionType(LinkedHashSet.class, ServiceRestMetadata.class));
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
metadata = Collections.emptySet();
}
return metadata;
}
private static <K, V> Map<K, V> getMap(Map<String, Map<K, V>> repository, String key) {
return getOrDefault(repository, key, newHashMap());
}
private static <K, V> V getOrDefault(Map<K, V> source, K key, V defaultValue) {
V value = source.get(key);
if (value == null) {
value = defaultValue;
source.put(key, value);
}
return value;
}
private Map<RequestMetadata, MethodMetadata> getMethodMetadataMap(String serviceName) {
return methodMetadataRepository.getOrDefault(serviceName, Collections.emptyMap());
private static <K, V> Map<K, V> newHashMap() {
return new LinkedHashMap<>();
}
}

@ -18,6 +18,7 @@ package org.springframework.cloud.alibaba.dubbo.metadata.resolver;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.config.spring.ServiceBean;
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
@ -26,7 +27,6 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry;
@ -44,20 +44,19 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* The metadata resolver for {@link Feign}
* The metadata resolver for {@link Feign} for {@link ServiceBean Dubbo Service Bean} in the provider side.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class FeignMetadataResolver implements BeanClassLoaderAware, SmartInitializingSingleton, MetadataResolver {
public class DubboServiceBeanMetadataResolver implements BeanClassLoaderAware, SmartInitializingSingleton,
MetadataResolver {
private static final String[] CONTRACT_CLASS_NAMES = {
"feign.jaxrs2.JAXRS2Contract",
"org.springframework.cloud.openfeign.support.SpringMvcContract",
};
private final String currentApplicationName;
private final ObjectProvider<Contract> contract;
private final ObjectProvider<Contract> contractObjectProvider;
private ClassLoader classLoader;
@ -66,9 +65,8 @@ public class FeignMetadataResolver implements BeanClassLoaderAware, SmartInitial
*/
private Collection<Contract> contracts;
public FeignMetadataResolver(String currentApplicationName, ObjectProvider<Contract> contract) {
this.currentApplicationName = currentApplicationName;
this.contract = contract;
public DubboServiceBeanMetadataResolver(ObjectProvider<Contract> contractObjectProvider) {
this.contractObjectProvider = contractObjectProvider;
}
@Override
@ -77,7 +75,11 @@ public class FeignMetadataResolver implements BeanClassLoaderAware, SmartInitial
LinkedList<Contract> contracts = new LinkedList<>();
// Add injected Contract if available, for example SpringMvcContract Bean under Spring Cloud Open Feign
contract.ifAvailable(contracts::add);
Contract contract = contractObjectProvider.getIfAvailable();
if (contract != null) {
contracts.add(contract);
}
Stream.of(CONTRACT_CLASS_NAMES)
.filter(this::isClassPresent) // filter the existed classes
@ -129,12 +131,22 @@ public class FeignMetadataResolver implements BeanClassLoaderAware, SmartInitial
public Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType) {
List<Method> feignContractMethods = selectFeignContractMethods(targetType);
return contracts.stream()
.map(contract -> contract.parseAndValidatateMetadata(targetType))
.map(contract -> parseAndValidateMetadata(contract, targetType))
.flatMap(v -> v.stream())
.map(methodMetadata -> resolveMethodRestMetadata(methodMetadata, targetType, feignContractMethods))
.collect(Collectors.toSet());
}
private List<MethodMetadata> parseAndValidateMetadata(Contract contract, Class<?> targetType) {
List<MethodMetadata> methodMetadataList = Collections.emptyList();
try {
methodMetadataList = contract.parseAndValidatateMetadata(targetType);
} catch (Throwable ignored) {
// ignore
}
return methodMetadataList;
}
/**
* Select feign contract methods
* <p>
@ -160,13 +172,8 @@ public class FeignMetadataResolver implements BeanClassLoaderAware, SmartInitial
List<Method> feignContractMethods) {
String configKey = methodMetadata.configKey();
Method feignContractMethod = getMatchedFeignContractMethod(targetType, feignContractMethods, configKey);
RestMethodMetadata metadata = new RestMethodMetadata();
metadata.setRequest(new RequestMetadata(methodMetadata.template()));
RestMethodMetadata metadata = new RestMethodMetadata(methodMetadata);
metadata.setMethod(new org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata(feignContractMethod));
metadata.setIndexToName(methodMetadata.indexToName());
return metadata;
}

@ -0,0 +1,112 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata.resolver;
import feign.Contract;
import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.PropertyResolver;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static feign.Feign.configKey;
/**
* {@link MethodMetadata} Resolver for the {@link DubboTransported} annotated classes or methods in client side.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see DubboTransportedMethodMetadata
*/
public class DubboTransportedMethodMetadataResolver {
private static final Class<DubboTransported> DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
private final PropertyResolver propertyResolver;
private final Contract contract;
public DubboTransportedMethodMetadataResolver(PropertyResolver propertyResolver, Contract contract) {
this.propertyResolver = propertyResolver;
this.contract = contract;
}
public Map<DubboTransportedMethodMetadata, RestMethodMetadata> resolve(Class<?> targetType) {
Set<DubboTransportedMethodMetadata> dubboTransportedMethodMetadataSet =
resolveDubboTransportedMethodMetadataSet(targetType);
Map<String, RestMethodMetadata> restMethodMetadataMap = resolveRestRequestMetadataMap(targetType);
return dubboTransportedMethodMetadataSet
.stream()
.collect(Collectors.toMap(methodMetadata -> methodMetadata, methodMetadata -> {
RestMethodMetadata restMethodMetadata = restMethodMetadataMap.get(configKey(targetType, methodMetadata.getMethod()));
restMethodMetadata.setMethod(methodMetadata.getMethodMetadata());
return restMethodMetadata;
}
));
}
protected Set<DubboTransportedMethodMetadata> resolveDubboTransportedMethodMetadataSet(Class<?> targetType) {
// The public methods of target interface
Method[] methods = targetType.getMethods();
Set<DubboTransportedMethodMetadata> methodMetadataSet = new LinkedHashSet<>();
for (Method method : methods) {
DubboTransported dubboTransported = resolveDubboTransported(method);
if (dubboTransported != null) {
DubboTransportedMethodMetadata methodMetadata = createDubboTransportedMethodMetadata(method, dubboTransported);
methodMetadataSet.add(methodMetadata);
}
}
return methodMetadataSet;
}
private Map<String, RestMethodMetadata> resolveRestRequestMetadataMap(Class<?> targetType) {
return contract.parseAndValidatateMetadata(targetType)
.stream().collect(Collectors.toMap(feign.MethodMetadata::configKey, this::restMethodMetadata));
}
private RestMethodMetadata restMethodMetadata(feign.MethodMetadata methodMetadata) {
return new RestMethodMetadata(methodMetadata);
}
private DubboTransportedMethodMetadata createDubboTransportedMethodMetadata(Method method,
DubboTransported dubboTransported) {
DubboTransportedMethodMetadata methodMetadata = new DubboTransportedMethodMetadata(method);
String protocol = propertyResolver.resolvePlaceholders(dubboTransported.protocol());
String cluster = propertyResolver.resolvePlaceholders(dubboTransported.cluster());
methodMetadata.setProtocol(protocol);
methodMetadata.setCluster(cluster);
return methodMetadata;
}
private DubboTransported resolveDubboTransported(Method method) {
DubboTransported dubboTransported = AnnotationUtils.findAnnotation(method, DUBBO_TRANSPORTED_CLASS);
if (dubboTransported == null) { // Attempt to find @DubboTransported in the declaring class
Class<?> declaringClass = method.getDeclaringClass();
dubboTransported = AnnotationUtils.findAnnotation(declaringClass, DUBBO_TRANSPORTED_CLASS);
}
return dubboTransported;
}
}

@ -17,6 +17,7 @@
package org.springframework.cloud.alibaba.dubbo.metadata.resolver;
import com.alibaba.dubbo.config.spring.ServiceBean;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;

@ -1,97 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata.service;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import static com.alibaba.nacos.api.common.Constants.DEFAULT_GROUP;
/**
* Nacos {@link MetadataConfigService}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class NacosMetadataConfigService implements MetadataConfigService {
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private NacosConfigProperties nacosConfigProperties;
private ConfigService configService;
@PostConstruct
public void init() {
this.configService = nacosConfigProperties.configServiceInstance();
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}
/**
* Get the data Id of service rest metadata
*/
private static String getServiceRestMetadataDataId(String serviceName) {
return "metadata:rest:" + serviceName + ".json";
}
@Override
public void publishServiceRestMetadata(String serviceName, Set<ServiceRestMetadata> serviceRestMetadata) {
String dataId = getServiceRestMetadataDataId(serviceName);
String json = writeValueAsString(serviceRestMetadata);
try {
configService.publishConfig(dataId, DEFAULT_GROUP, json);
} catch (NacosException e) {
throw new RuntimeException(e);
}
}
@Override
public Set<ServiceRestMetadata> getServiceRestMetadata(String serviceName) {
Set<ServiceRestMetadata> metadata = Collections.emptySet();
String dataId = getServiceRestMetadataDataId(serviceName);
try {
String json = configService.getConfig(dataId, DEFAULT_GROUP, 1000 * 3);
metadata = objectMapper.readValue(json,
TypeFactory.defaultInstance().constructCollectionType(LinkedHashSet.class, ServiceRestMetadata.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
return metadata;
}
private String writeValueAsString(Object object) {
String content = null;
try {
content = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
return content;
}
}

@ -1,65 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.openfeign;
import feign.Contract;
import feign.Feign;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* Dubbo {@link Configuration} for {@link FeignClient FeignClients}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see DubboOpenFeignAutoConfiguration
* @see org.springframework.cloud.openfeign.FeignContext#setConfigurations(List)
* @see FeignClientsConfiguration
*/
@Configuration
public class DubboFeignClientsConfiguration {
@Autowired
private Contract contract;
@Autowired
private DubboServiceMetadataRepository dubboServiceRepository;
@Bean
public BeanPostProcessor beanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Feign.Builder) {
Feign.Builder builder = (Feign.Builder) bean;
builder.invocationHandlerFactory(new DubboInvocationHandlerFactory(contract, dubboServiceRepository));
}
return bean;
}
};
}
}

@ -17,8 +17,10 @@
package org.springframework.cloud.alibaba.dubbo.openfeign;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContext;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContextFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@ -31,39 +33,39 @@ import java.util.Map;
*/
public class DubboInvocationHandler implements InvocationHandler {
private final Map<Method, GenericService> genericServicesMap;
private final Map<Method, MethodMetadata> methodMetadata;
private final Map<Method, FeignMethodMetadata> feignMethodMetadataMap;
private final InvocationHandler defaultInvocationHandler;
public DubboInvocationHandler(Map<Method, GenericService> genericServicesMap,
Map<Method, MethodMetadata> methodMetadata,
InvocationHandler defaultInvocationHandler) {
this.genericServicesMap = genericServicesMap;
this.methodMetadata = methodMetadata;
private final DubboGenericServiceExecutionContextFactory contextFactory;
public DubboInvocationHandler(Map<Method, FeignMethodMetadata> feignMethodMetadataMap,
InvocationHandler defaultInvocationHandler,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.feignMethodMetadataMap = feignMethodMetadataMap;
this.defaultInvocationHandler = defaultInvocationHandler;
this.contextFactory = contextFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
public Object invoke(Object proxy, Method feignMethod, Object[] args) throws Throwable {
GenericService genericService = genericServicesMap.get(method);
FeignMethodMetadata feignMethodMetadata = feignMethodMetadataMap.get(feignMethod);
MethodMetadata methodMetadata = this.methodMetadata.get(method);
if (genericService == null || methodMetadata == null) {
return defaultInvocationHandler.invoke(proxy, method, args);
if (feignMethodMetadata == null) {
return defaultInvocationHandler.invoke(proxy, feignMethod, args);
}
String methodName = methodMetadata.getName();
GenericService dubboGenericService = feignMethodMetadata.getDubboGenericService();
RestMethodMetadata dubboRestMethodMetadata = feignMethodMetadata.getDubboRestMethodMetadata();
RestMethodMetadata feignRestMethodMetadata = feignMethodMetadata.getFeignMethodMetadata();
DubboGenericServiceExecutionContext context = contextFactory.create(dubboRestMethodMetadata, feignRestMethodMetadata, args);
String[] parameterTypes = methodMetadata
.getParams()
.stream()
.map(MethodParameterMetadata::getType)
.toArray(String[]::new);
String methodName = context.getMethodName();
String[] parameterTypes = context.getParameterTypes();
Object[] parameters = context.getParameters();
return genericService.$invoke(methodName, parameterTypes, args);
return dubboGenericService.$invoke(methodName, parameterTypes, parameters);
}
}

@ -1,101 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.openfeign;
import com.alibaba.dubbo.rpc.service.GenericService;
import feign.Contract;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static feign.Feign.configKey;
/**
* Dubbo {@link InvocationHandlerFactory}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboInvocationHandlerFactory implements InvocationHandlerFactory {
private final static InvocationHandlerFactory DEFAULT_INVOCATION_HANDLER_FACTORY =
new InvocationHandlerFactory.Default();
private final Contract contract;
private final DubboServiceMetadataRepository dubboServiceRepository;
public DubboInvocationHandlerFactory(Contract contract, DubboServiceMetadataRepository dubboServiceRepository) {
this.contract = contract;
this.dubboServiceRepository = dubboServiceRepository;
}
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// The target class annotated @FeignClient
Class<?> targetType = target.type();
// Resolve metadata from current @FeignClient type
Map<Method, RequestMetadata> methodRequestMetadataMap = resolveMethodRequestMetadataMap(targetType, dispatch.keySet());
// @FeignClient specifies the service name
String serviceName = target.name();
// Update specified metadata
dubboServiceRepository.updateMetadata(serviceName);
Map<Method, GenericService> genericServicesMap = new HashMap<>();
Map<Method, org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata> methodMetadataMap = new HashMap<>();
methodRequestMetadataMap.forEach((method, requestMetadata) -> {
GenericService genericService = dubboServiceRepository.getGenericService(serviceName, requestMetadata);
org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata =
dubboServiceRepository.getMethodMetadata(serviceName, requestMetadata);
genericServicesMap.put(method, genericService);
methodMetadataMap.put(method, methodMetadata);
});
InvocationHandler defaultInvocationHandler = DEFAULT_INVOCATION_HANDLER_FACTORY.create(target, dispatch);
DubboInvocationHandler invocationHandler = new DubboInvocationHandler(genericServicesMap, methodMetadataMap,
defaultInvocationHandler);
return invocationHandler;
}
private Map<Method, RequestMetadata> resolveMethodRequestMetadataMap(Class<?> targetType, Set<Method> methods) {
Map<String, RequestMetadata> requestMetadataMap = resolveRequestMetadataMap(targetType);
return methods.stream().collect(Collectors.toMap(method -> method, method ->
requestMetadataMap.get(configKey(targetType, method))
));
}
private Map<String, RequestMetadata> resolveRequestMetadataMap(Class<?> targetType) {
return contract.parseAndValidatateMetadata(targetType)
.stream().collect(Collectors.toMap(MethodMetadata::configKey, this::requestMetadata));
}
private RequestMetadata requestMetadata(MethodMetadata methodMetadata) {
return new RequestMetadata(methodMetadata.template());
}
}

@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.openfeign;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import java.lang.reflect.Method;
/**
* Feign {@link Method} Metadata
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class FeignMethodMetadata {
private final GenericService dubboGenericService;
private final RestMethodMetadata dubboRestMethodMetadata;
private final RestMethodMetadata feignMethodMetadata;
FeignMethodMetadata(GenericService dubboGenericService, RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata feignMethodMetadata) {
this.dubboGenericService = dubboGenericService;
this.dubboRestMethodMetadata = dubboRestMethodMetadata;
this.feignMethodMetadata = feignMethodMetadata;
}
GenericService getDubboGenericService() {
return dubboGenericService;
}
RestMethodMetadata getDubboRestMethodMetadata() {
return dubboRestMethodMetadata;
}
RestMethodMetadata getFeignMethodMetadata() {
return feignMethodMetadata;
}
}

@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.openfeign;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContextFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.core.env.Environment;
import static java.lang.reflect.Proxy.newProxyInstance;
import static org.springframework.util.ClassUtils.getUserClass;
import static org.springframework.util.ClassUtils.resolveClassName;
/**
* org.springframework.cloud.openfeign.Targeter {@link BeanPostProcessor}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class TargeterBeanPostProcessor implements BeanPostProcessor, BeanClassLoaderAware {
private static final String TARGETER_CLASS_NAME = "org.springframework.cloud.openfeign.Targeter";
private final Environment environment;
private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private ClassLoader classLoader;
public TargeterBeanPostProcessor(Environment environment,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.environment = environment;
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
this.contextFactory = contextFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
Class<?> beanClass = getUserClass(bean.getClass());
Class<?> targetClass = resolveClassName(TARGETER_CLASS_NAME, classLoader);
if (targetClass.isAssignableFrom(beanClass)) {
return newProxyInstance(classLoader, new Class[]{targetClass},
new TargeterInvocationHandler(bean, environment, dubboServiceMetadataRepository,
dubboGenericServiceFactory,contextFactory));
}
return bean;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}

@ -0,0 +1,171 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.openfeign;
import com.alibaba.dubbo.rpc.service.GenericService;
import feign.Contract;
import feign.Target;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboServiceMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboTransportedMethodMetadataResolver;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceExecutionContextFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.core.env.Environment;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import static java.lang.reflect.Proxy.newProxyInstance;
/**
* org.springframework.cloud.openfeign.Targeter {@link InvocationHandler}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class TargeterInvocationHandler implements InvocationHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Object bean;
private final Environment environment;
private final DubboServiceMetadataRepository repository;
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
TargeterInvocationHandler(Object bean, Environment environment, DubboServiceMetadataRepository repository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.bean = bean;
this.environment = environment;
this.repository = repository;
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
this.contextFactory = contextFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* args[0]: FeignClientFactoryBean factory
* args[1]: Feign.Builder feign
* args[2]: FeignContext context
* args[3]: Target.HardCodedTarget<T> target
*/
FeignContext feignContext = cast(args[2]);
Target.HardCodedTarget<?> target = cast(args[3]);
// Execute Targeter#target method first
method.setAccessible(true);
// Get the default proxy object
Object defaultProxy = method.invoke(bean, args);
// Create Dubbo Proxy if required
return createDubboProxyIfRequired(feignContext, target, defaultProxy);
}
private Object createDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) {
DubboInvocationHandler dubboInvocationHandler = createDubboInvocationHandler(feignContext, target, defaultProxy);
if (dubboInvocationHandler == null) {
return defaultProxy;
}
return newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, dubboInvocationHandler);
}
private DubboInvocationHandler createDubboInvocationHandler(FeignContext feignContext, Target target,
Object defaultFeignClientProxy) {
// Service name equals @FeignClient.name()
String serviceName = target.name();
Class<?> targetType = target.type();
// Get Contract Bean from FeignContext
Contract contract = feignContext.getInstance(serviceName, Contract.class);
DubboTransportedMethodMetadataResolver resolver =
new DubboTransportedMethodMetadataResolver(environment, contract);
Map<DubboTransportedMethodMetadata, RestMethodMetadata> feignRestMethodMetadataMap = resolver.resolve(targetType);
if (feignRestMethodMetadataMap.isEmpty()) { // @DubboTransported method was not found from the Client interface
if (logger.isDebugEnabled()) {
logger.debug("@{} method was not found in the Feign target type[{}]",
DubboTransported.class.getSimpleName(), targetType.getName());
}
return null;
}
// Update Metadata
repository.initialize(serviceName);
Map<Method, FeignMethodMetadata> feignMethodMetadataMap = getFeignMethodMetadataMap(serviceName, feignRestMethodMetadataMap);
InvocationHandler defaultFeignClientInvocationHandler = Proxy.getInvocationHandler(defaultFeignClientProxy);
DubboInvocationHandler dubboInvocationHandler = new DubboInvocationHandler(feignMethodMetadataMap,
defaultFeignClientInvocationHandler, contextFactory);
return dubboInvocationHandler;
}
private Map<Method, FeignMethodMetadata> getFeignMethodMetadataMap(String serviceName,
Map<DubboTransportedMethodMetadata, RestMethodMetadata>
feignRestMethodMetadataMap) {
Map<Method, FeignMethodMetadata> feignMethodMetadataMap = new HashMap<>();
for (Map.Entry<DubboTransportedMethodMetadata, RestMethodMetadata> entry : feignRestMethodMetadataMap.entrySet()) {
RestMethodMetadata feignRestMethodMetadata = entry.getValue();
RequestMetadata feignRequestMetadata = feignRestMethodMetadata.getRequest();
DubboServiceMetadata dubboServiceMetadata = repository.get(serviceName, feignRequestMetadata);
if (dubboServiceMetadata != null) {
DubboTransportedMethodMetadata dubboTransportedMethodMetadata = entry.getKey();
DubboTransportedMetadata dubboTransportedMetadata = dubboTransportedMethodMetadata.getDubboTransportedMetadata();
Method method = dubboTransportedMethodMetadata.getMethod();
GenericService dubboGenericService = dubboGenericServiceFactory.create(dubboServiceMetadata, dubboTransportedMetadata);
RestMethodMetadata dubboRestMethodMetadata = dubboServiceMetadata.getRestMethodMetadata();
MethodMetadata methodMetadata = dubboTransportedMethodMetadata.getMethodMetadata();
FeignMethodMetadata feignMethodMetadata = new FeignMethodMetadata(dubboGenericService,
dubboRestMethodMetadata, feignRestMethodMetadata);
feignMethodMetadataMap.put(method, feignMethodMetadata);
}
}
return feignMethodMetadataMap;
}
private static <T> T cast(Object object) {
return (T) object;
}
}

@ -23,6 +23,7 @@ import com.alibaba.dubbo.common.utils.UrlUtils;
import com.alibaba.dubbo.registry.NotifyListener;
import com.alibaba.dubbo.registry.RegistryFactory;
import com.alibaba.dubbo.registry.support.FailbackRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
@ -47,7 +48,6 @@ import java.util.concurrent.TimeUnit;
import static com.alibaba.dubbo.common.Constants.CONFIGURATORS_CATEGORY;
import static com.alibaba.dubbo.common.Constants.CONSUMERS_CATEGORY;
import static com.alibaba.dubbo.common.Constants.PROTOCOL_KEY;
import static com.alibaba.dubbo.common.Constants.PROVIDERS_CATEGORY;
import static com.alibaba.dubbo.common.Constants.ROUTERS_CATEGORY;
@ -70,9 +70,11 @@ public class SpringCloudRegistry extends FailbackRegistry {
private static final int CATEGORY_INDEX = 0;
private static final int PROTOCOL_INDEX = CATEGORY_INDEX + 1;
// private static final int PROTOCOL_INDEX = CATEGORY_INDEX + 1;
// private static final int SERVICE_INTERFACE_INDEX = PROTOCOL_INDEX + 1;
private static final int SERVICE_INTERFACE_INDEX = PROTOCOL_INDEX + 1;
private static final int SERVICE_INTERFACE_INDEX = CATEGORY_INDEX + 1;
private static final int SERVICE_VERSION_INDEX = SERVICE_INTERFACE_INDEX + 1;
@ -169,7 +171,6 @@ public class SpringCloudRegistry extends FailbackRegistry {
private static String getServiceName(URL url, String category) {
StringBuilder serviceNameBuilder = new StringBuilder(category);
appendIfPresent(serviceNameBuilder, url.getParameter(PROTOCOL_KEY, url.getProtocol()));
appendIfPresent(serviceNameBuilder, url, Constants.INTERFACE_KEY);
appendIfPresent(serviceNameBuilder, url, Constants.VERSION_KEY);
appendIfPresent(serviceNameBuilder, url, Constants.GROUP_KEY);
@ -203,12 +204,11 @@ public class SpringCloudRegistry extends FailbackRegistry {
// split service name to segments
// (required) segments[0] = category
// (required) segments[1] = serviceInterface
// (required) segments[2] = protocol
// (required) segments[3] = version
// (optional) segments[4] = group
// (required) segments[2] = version
// (optional) segments[3] = group
String[] segments = getServiceSegments(serviceName);
int length = segments.length;
if (length < 4) { // must present 4 segments or more
if (length < SERVICE_GROUP_INDEX) { // must present 4 segments or more
return false;
}
@ -217,11 +217,6 @@ public class SpringCloudRegistry extends FailbackRegistry {
return false;
}
String protocol = getProtocol(segments);
if (StringUtils.hasText(protocol)) {
return false;
}
String serviceInterface = getServiceInterface(segments);
if (!WILDCARD.equals(targetServiceInterface) &&
!Objects.equals(targetServiceInterface, serviceInterface)) { // no match service interface
@ -253,9 +248,9 @@ public class SpringCloudRegistry extends FailbackRegistry {
return segments[CATEGORY_INDEX];
}
public static String getProtocol(String[] segments) {
return segments[PROTOCOL_INDEX];
}
// public static String getProtocol(String[] segments) {
// return segments[PROTOCOL_INDEX];
// }
public static String getServiceInterface(String[] segments) {
return segments[SERVICE_INTERFACE_INDEX];
@ -266,7 +261,7 @@ public class SpringCloudRegistry extends FailbackRegistry {
}
public static String getServiceGroup(String[] segments) {
return segments.length > 4 ? segments[SERVICE_GROUP_INDEX] : null;
return segments.length > SERVICE_GROUP_INDEX ? segments[SERVICE_GROUP_INDEX] : null;
}
/**

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import com.alibaba.dubbo.rpc.service.GenericService;
/**
* Dubbo {@link GenericService} execution context
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboGenericServiceExecutionContext {
private final String methodName;
private final String[] parameterTypes;
private final Object[] parameters;
public DubboGenericServiceExecutionContext(String methodName, String[] parameterTypes, Object[] parameters) {
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameters = parameters;
}
public String getMethodName() {
return methodName;
}
public String[] getParameterTypes() {
return parameterTypes;
}
public Object[] getParameters() {
return parameters;
}
}

@ -0,0 +1,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.dubbo.http.HttpServerRequest;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.service.parameter.DubboGenericServiceParameterResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* {@link DubboGenericServiceExecutionContext} Factory
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see DubboGenericServiceParameterResolver
*/
public class DubboGenericServiceExecutionContextFactory {
@Autowired(required = false)
private final List<DubboGenericServiceParameterResolver> resolvers = Collections.emptyList();
@PostConstruct
public void init() {
AnnotationAwareOrderComparator.sort(resolvers);
}
public DubboGenericServiceExecutionContext create(RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata clientMethodMetadata, Object[] arguments) {
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
String methodName = dubboMethodMetadata.getName();
String[] parameterTypes = resolveParameterTypes(dubboMethodMetadata);
Object[] parameters = resolveParameters(dubboRestMethodMetadata, clientMethodMetadata, arguments);
return new DubboGenericServiceExecutionContext(methodName, parameterTypes, parameters);
}
public DubboGenericServiceExecutionContext create(RestMethodMetadata dubboRestMethodMetadata,
HttpServerRequest request) {
MethodMetadata methodMetadata = dubboRestMethodMetadata.getMethod();
String methodName = methodMetadata.getName();
String[] parameterTypes = resolveParameterTypes(methodMetadata);
Object[] parameters = resolveParameters(dubboRestMethodMetadata, request);
return new DubboGenericServiceExecutionContext(methodName, parameterTypes, parameters);
}
private Map<String, Integer> buildParamNameToIndex(List<MethodParameterMetadata> params) {
Map<String, Integer> paramNameToIndex = new LinkedHashMap<>();
for (MethodParameterMetadata param : params) {
paramNameToIndex.put(param.getName(), param.getIndex());
}
return paramNameToIndex;
}
protected String[] resolveParameterTypes(MethodMetadata methodMetadata) {
List<MethodParameterMetadata> params = methodMetadata.getParams();
String[] parameterTypes = new String[params.size()];
for (MethodParameterMetadata parameterMetadata : params) {
int index = parameterMetadata.getIndex();
String parameterType = parameterMetadata.getType();
parameterTypes[index] = parameterType;
}
return parameterTypes;
}
protected Object[] resolveParameters(RestMethodMetadata dubboRestMethodMetadata, HttpServerRequest request) {
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
List<MethodParameterMetadata> params = dubboMethodMetadata.getParams();
Object[] parameters = new Object[params.size()];
for (MethodParameterMetadata parameterMetadata : params) {
int index = parameterMetadata.getIndex();
for (DubboGenericServiceParameterResolver resolver : resolvers) {
Object parameter = resolver.resolve(dubboRestMethodMetadata, parameterMetadata, request);
if (parameter != null) {
parameters[index] = parameter;
break;
}
}
}
return parameters;
}
protected Object[] resolveParameters(RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
List<MethodParameterMetadata> params = dubboMethodMetadata.getParams();
Object[] parameters = new Object[params.size()];
for (MethodParameterMetadata parameterMetadata : params) {
int index = parameterMetadata.getIndex();
for (DubboGenericServiceParameterResolver resolver : resolvers) {
Object parameter = resolver.resolve(dubboRestMethodMetadata, parameterMetadata, clientRestMethodMetadata, arguments);
if (parameter != null) {
parameters[index] = parameter;
break;
}
}
}
return parameters;
}
}

@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboServiceMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import javax.annotation.PreDestroy;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static com.alibaba.dubbo.common.Constants.DEFAULT_CLUSTER;
import static com.alibaba.dubbo.common.Constants.DEFAULT_PROTOCOL;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceGroup;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceInterface;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceSegments;
import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceVersion;
/**
* Dubbo {@link GenericService} Factory
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboGenericServiceFactory {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ConcurrentMap<Integer, ReferenceBean<GenericService>> cache = new ConcurrentHashMap<>();
public GenericService create(DubboServiceMetadata dubboServiceMetadata,
DubboTransportedMetadata dubboTransportedMetadata) {
ReferenceBean<GenericService> referenceBean = build(dubboServiceMetadata.getServiceRestMetadata(), dubboTransportedMetadata);
return referenceBean == null ? null : referenceBean.get();
}
public GenericService create(String serviceName, Class<?> serviceClass) {
String interfaceName = serviceClass.getName();
ReferenceBean<GenericService> referenceBean = build(interfaceName, serviceName, null,
DEFAULT_PROTOCOL, DEFAULT_CLUSTER);
return referenceBean.get();
}
private ReferenceBean<GenericService> build(ServiceRestMetadata serviceRestMetadata,
DubboTransportedMetadata dubboTransportedMetadata) {
String dubboServiceName = serviceRestMetadata.getName();
String[] segments = getServiceSegments(dubboServiceName);
String interfaceName = getServiceInterface(segments);
String version = getServiceVersion(segments);
String group = getServiceGroup(segments);
String protocol = dubboTransportedMetadata.getProtocol();
String cluster = dubboTransportedMetadata.getCluster();
return build(interfaceName, version, group, protocol, cluster);
}
private ReferenceBean<GenericService> build(String interfaceName, String version, String group, String protocol,
String cluster) {
Integer key = Objects.hash(interfaceName, version, group, protocol, cluster);
ReferenceBean<GenericService> referenceBean = cache.get(key);
if (referenceBean == null) {
referenceBean = new ReferenceBean<>();
referenceBean.setGeneric(true);
referenceBean.setInterface(interfaceName);
referenceBean.setVersion(version);
referenceBean.setGroup(group);
referenceBean.setProtocol(protocol);
referenceBean.setCluster(cluster);
}
return referenceBean;
}
@PreDestroy
public void destroy() {
destroyReferenceBeans();
cache.values();
}
private void destroyReferenceBeans() {
Collection<ReferenceBean<GenericService>> referenceBeans = cache.values();
if (logger.isInfoEnabled()) {
logger.info("The Dubbo GenericService ReferenceBeans are destroying...");
}
for (ReferenceBean referenceBean : referenceBeans) {
referenceBean.destroy(); // destroy ReferenceBean
if (logger.isInfoEnabled()) {
logger.info("Destroyed the ReferenceBean : {} ", referenceBean);
}
}
}
}

@ -14,20 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata.service;
package org.springframework.cloud.alibaba.dubbo.service;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import java.util.Set;
/**
* Config Service for Metadata
* Dubbo Metadata Configuration Service
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public interface MetadataConfigService {
public interface DubboMetadataConfigService {
void publishServiceRestMetadata(String serviceName, Set<ServiceRestMetadata> serviceRestMetadata);
Set<ServiceRestMetadata> getServiceRestMetadata(String serviceName);
/**
* Get The json content of {@link ServiceRestMetadata} {@link Set}
*
* @return the non-null String
*/
String getServiceRestMetadata();
}

@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import com.alibaba.dubbo.rpc.service.GenericService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* {@link DubboMetadataConfigService} {@link InvocationHandler}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class DubboMetadataConfigServiceInvocationHandler implements InvocationHandler {
/**
* The method name of {@link DubboMetadataConfigService#getServiceRestMetadata()}
*/
private static final String METHOD_NAME = "getServiceRestMetadata";
private static final String[] PARAMETER_TYPES = new String[0];
private static final String[] PARAMETER_VALUES = new String[0];
private final GenericService genericService;
public DubboMetadataConfigServiceInvocationHandler(String serviceName, DubboGenericServiceFactory dubboGenericServiceFactory) {
this.genericService = dubboGenericServiceFactory.create(serviceName, DubboMetadataConfigService.class);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (METHOD_NAME.equals(methodName)) {
return genericService.$invoke(methodName, PARAMETER_TYPES, PARAMETER_VALUES);
}
return method.invoke(proxy, args);
}
}

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import org.springframework.beans.factory.BeanClassLoaderAware;
import static java.lang.reflect.Proxy.newProxyInstance;
/**
* The proxy of {@link DubboMetadataConfigService}
*/
public class DubboMetadataConfigServiceProxy implements BeanClassLoaderAware {
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private ClassLoader classLoader;
public DubboMetadataConfigServiceProxy(DubboGenericServiceFactory dubboGenericServiceFactory) {
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
}
/**
* New proxy instance of {@link DubboMetadataConfigService} via the specified service name
*
* @param serviceName the service name
* @return a {@link DubboMetadataConfigService} proxy
*/
public DubboMetadataConfigService newProxy(String serviceName) {
return (DubboMetadataConfigService) newProxyInstance(classLoader, new Class[]{DubboMetadataConfigService.class},
new DubboMetadataConfigServiceInvocationHandler(serviceName, dubboGenericServiceFactory));
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboMetadataAutoConfiguration.METADATA_PROTOCOL_BEAN_NAME;
/**
* Publishing {@link DubboMetadataConfigService} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Service(version = "${spring.application.name}", protocol = METADATA_PROTOCOL_BEAN_NAME)
// Use current Spring application name as the Dubbo Service version
public class PublishingDubboMetadataConfigService implements DubboMetadataConfigService {
/**
* A Map to store REST metadata temporary, its' key is the special service name for a Dubbo service,
* the value is a JSON content of JAX-RS or Spring MVC REST metadata from the annotated methods.
*/
private final Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
private final ObjectMapper objectMapper = new ObjectMapper();
@PostConstruct
public void init() {
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}
/**
* Publish the {@link Set} of {@link ServiceRestMetadata}
*
* @param serviceRestMetadataSet the {@link Set} of {@link ServiceRestMetadata}
*/
public void publishServiceRestMetadata(Set<ServiceRestMetadata> serviceRestMetadataSet) {
for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) {
if (!CollectionUtils.isEmpty(serviceRestMetadata.getMeta())) {
this.serviceRestMetadata.add(serviceRestMetadata);
}
}
}
@Override
public String getServiceRestMetadata() {
String serviceRestMetadataJsonConfig = null;
try {
serviceRestMetadataJsonConfig = objectMapper.writeValueAsString(serviceRestMetadata);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return serviceRestMetadataJsonConfig;
}
}

@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service.parameter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.FormattingConversionService;
import static org.springframework.context.ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME;
import static org.springframework.util.ClassUtils.resolveClassName;
/**
* Abstract {@link DubboGenericServiceParameterResolver} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractDubboGenericServiceParameterResolver implements DubboGenericServiceParameterResolver,
BeanClassLoaderAware {
private int order;
@Autowired(required = false)
@Qualifier(CONVERSION_SERVICE_BEAN_NAME)
private ConversionService conversionService = new FormattingConversionService();
private ClassLoader classLoader;
public ConversionService getConversionService() {
return conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return order;
}
protected Class<?> resolveClass(String className) {
return resolveClassName(className, classLoader);
}
protected Object resolveValue(Object parameterValue, String parameterType) {
Class<?> targetType = resolveClass(parameterType);
return resolveValue(parameterValue, targetType);
}
protected Object resolveValue(Object parameterValue, Class<?> parameterType) {
return conversionService.convert(parameterValue, parameterType);
}
}

@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service.parameter;
import org.springframework.cloud.alibaba.dubbo.http.HttpServerRequest;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.springframework.util.ObjectUtils.isEmpty;
/**
* Abstract HTTP Names Value {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractNamedValueServiceParameterResolver extends AbstractDubboGenericServiceParameterResolver {
/**
* Get the {@link MultiValueMap} of names and values
*
* @param request
* @return
*/
protected abstract MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request);
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
HttpServerRequest request) {
Collection<String> names = getNames(restMethodMetadata, methodParameterMetadata);
if (isEmpty(names)) { // index can't match
return null;
}
MultiValueMap<String, String> nameAndValues = getNameAndValuesMap(request);
String targetName = null;
for (String name : names) {
if (nameAndValues.containsKey(name)) {
targetName = name;
break;
}
}
if (targetName == null) { // request parameter is abstract
return null;
}
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
Object paramValue = null;
if (parameterType.isArray()) { // Array type
paramValue = nameAndValues.get(targetName);
} else {
paramValue = nameAndValues.getFirst(targetName);
}
return resolveValue(paramValue, parameterType);
}
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
Collection<String> names = getNames(restMethodMetadata, methodParameterMetadata);
if (isEmpty(names)) { // index can't match
return null;
}
Integer index = null;
Map<Integer, Collection<String>> clientIndexToName = clientRestMethodMetadata.getIndexToName();
for (Map.Entry<Integer, Collection<String>> entry : clientIndexToName.entrySet()) {
Collection<String> clientParamNames = entry.getValue();
if (CollectionUtils.containsAny(names, clientParamNames)) {
index = entry.getKey();
break;
}
}
return index > -1 ? arguments[index] : null;
}
protected Collection<String> getNames(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata) {
Map<Integer, Collection<String>> indexToName = restMethodMetadata.getIndexToName();
int index = methodParameterMetadata.getIndex();
Collection<String> paramNames = indexToName.get(index);
return paramNames == null ? Collections.emptyList() : paramNames;
}
}

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service.parameter;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.springframework.cloud.alibaba.dubbo.http.HttpServerRequest;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.core.Ordered;
/**
* Dubbo {@link GenericService} Parameter Resolver
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public interface DubboGenericServiceParameterResolver extends Ordered {
/**
* Resolves a method parameter into an argument value from a given request.
*
* @return
*/
Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
HttpServerRequest request);
Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments);
}

@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service.parameter;
import org.springframework.cloud.alibaba.dubbo.http.HttpServerRequest;
import org.springframework.util.MultiValueMap;
/**
* HTTP Request Path Variable {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class PathVariableServiceParameterResolver extends AbstractNamedValueServiceParameterResolver {
public static final int DEFAULT_ORDER = 3;
public PathVariableServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request) {
return request.getQueryParams();
}
}

@ -0,0 +1,117 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service.parameter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.alibaba.dubbo.http.HttpServerRequest;
import org.springframework.cloud.alibaba.dubbo.http.converter.HttpMessageConverterHolder;
import org.springframework.cloud.alibaba.dubbo.http.util.HttpMessageConverterResolver;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
/**
* HTTP Request Body {@link DubboGenericServiceParameterResolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestBodyServiceParameterResolver extends AbstractDubboGenericServiceParameterResolver {
public static final int DEFAULT_ORDER = 7;
@Autowired
private ObjectProvider<HttpMessageConverters> httpMessageConverters;
private HttpMessageConverterResolver httpMessageConverterResolver;
public RequestBodyServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@PostConstruct
public void init() {
HttpMessageConverters httpMessageConverters = this.httpMessageConverters.getIfAvailable();
httpMessageConverterResolver = new HttpMessageConverterResolver(httpMessageConverters == null ?
Collections.emptyList() : httpMessageConverters.getConverters(),
getClassLoader());
}
private boolean supportParameter(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata) {
Integer index = methodParameterMetadata.getIndex();
Integer bodyIndex = restMethodMetadata.getBodyIndex();
if (!Objects.equals(index, bodyIndex)) {
return false;
}
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
Class<?> bodyType = resolveClass(restMethodMetadata.getBodyType());
return Objects.equals(parameterType, bodyType);
}
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
HttpServerRequest request) {
if (!supportParameter(restMethodMetadata, methodParameterMetadata)) {
return null;
}
Object result = null;
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
HttpMessageConverterHolder holder = httpMessageConverterResolver.resolve(request, parameterType);
if (holder != null) {
HttpMessageConverter converter = holder.getConverter();
try {
result = converter.read(parameterType, request);
} catch (IOException e) {
throw new HttpMessageNotReadableException("I/O error while reading input message", e);
}
}
return result;
}
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
if (!supportParameter(restMethodMetadata, methodParameterMetadata)) {
return null;
}
Integer clientBodyIndex = clientRestMethodMetadata.getBodyIndex();
return arguments[clientBodyIndex];
}
}

@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service.parameter;
import org.springframework.cloud.alibaba.dubbo.http.HttpServerRequest;
import org.springframework.util.MultiValueMap;
/**
* HTTP Request Header {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestHeaderServiceParameterResolver extends AbstractNamedValueServiceParameterResolver {
public static final int DEFAULT_ORDER = 9;
public RequestHeaderServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request) {
return request.getHeaders();
}
}

@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service.parameter;
import org.springframework.cloud.alibaba.dubbo.http.HttpServerRequest;
import org.springframework.util.MultiValueMap;
/**
* HTTP Request Parameter {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestParamServiceParameterResolver extends AbstractNamedValueServiceParameterResolver {
public static final int DEFAULT_ORDER = 1;
public RequestParamServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request) {
return request.getQueryParams();
}
}

@ -1,7 +1,9 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboMetadataAutoConfiguration,\
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration,\
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboRestMetadataRegistrationAutoConfiguration
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboRestMetadataRegistrationAutoConfiguration,\
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboLoadBalancedRestTemplateAutoConfiguration,\
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
org.springframework.cloud.alibaba.dubbo.context.DubboServiceRegistrationApplicationContextInitializer
org.springframework.cloud.alibaba.dubbo.context.DubboServiceRegistrationApplicationContextInitializer

@ -17,19 +17,35 @@
package org.springframework.cloud.alibaba.dubbo.bootstrap;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.alibaba.dubbo.service.EchoService;
import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import org.springframework.cloud.alibaba.dubbo.service.RestService;
import org.springframework.cloud.alibaba.dubbo.service.User;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
/**
* Dubbo Spring Cloud Bootstrap
@ -41,34 +57,161 @@ import org.springframework.web.bind.annotation.RestController;
public class DubboSpringCloudBootstrap {
@Reference(version = "1.0.0")
private EchoService echoService;
private RestService restService;
@Autowired
@Lazy
private FeignRestService feignRestService;
@Autowired
@Lazy
private FeignEchoService feignEchoService;
private DubboFeignRestService dubboFeignRestService;
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
@FeignClient("spring-cloud-alibaba-dubbo")
public interface FeignRestService {
@GetMapping(value = "/param")
String param(@RequestParam("param") String param);
@GetMapping(value = "/call/echo")
public String echo(@RequestParam("message") String message) {
return feignEchoService.echo(message);
@PostMapping("/params")
public String params(@RequestParam("b") String b, @RequestParam("a") int a);
@PostMapping(value = "/request/body/map", produces = APPLICATION_JSON_UTF8_VALUE)
User requestBody(@RequestParam("param") String param, @RequestBody Map<String, Object> data);
@GetMapping("/headers")
@Path("/headers")
@GET
public String headers(@RequestHeader("h2") String header2,
@RequestHeader("h") String header,
@RequestParam("v") Integer value);
@GetMapping("/path-variables/{p1}/{p2}")
public String pathVariables(@PathVariable("p2") String path2,
@PathVariable("p1") String path1,
@RequestParam("v") String param);
}
@FeignClient("spring-cloud-alibaba-dubbo")
public interface FeignEchoService {
@DubboTransported
public interface DubboFeignRestService {
@GetMapping(value = "/param")
String param(@RequestParam("param") String param);
@PostMapping("/params")
String params(@RequestParam("b") String paramB, @RequestParam("a") int paramA);
@PostMapping(value = "/request/body/map", produces = APPLICATION_JSON_UTF8_VALUE)
User requestBody(@RequestParam("param") String param, @RequestBody Map<String, Object> data);
@GetMapping(value = "/echo")
String echo(@RequestParam("message") String message);
@GetMapping("/headers")
@Path("/headers")
@GET
public String headers(@RequestHeader("h2") String header2,
@RequestParam("v") Integer value,
@RequestHeader("h") String header);
@GetMapping("/path-variables/{p1}/{p2}")
public String pathVariables(@RequestParam("v") String param,
@PathVariable("p2") String path2,
@PathVariable("p1") String path1);
}
@Bean
public ApplicationRunner applicationRunner() {
public ApplicationRunner paramRunner() {
return arguments -> {
// Dubbo Service call
System.out.println(echoService.echo("mercyblitz"));
// Spring Cloud Open Feign REST Call
System.out.println(feignEchoService.echo("mercyblitz"));
// To call /path-variables
callPathVariables();
// To call /headers
callHeaders();
// To call /param
callParam();
// To call /params
callParams();
// To call /request/body/map
callRequestBodyMap();
};
}
private void callPathVariables() {
// Dubbo Service call
System.out.println(restService.pathVariables("a", "b", "c"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.pathVariables("c", "b", "a"));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.pathVariables("b", "a", "c"));
// RestTemplate call
System.out.println(restTemplate.getForEntity("http://spring-cloud-alibaba-dubbo//path-variables/{p1}/{p2}?v=c", String.class, "a", "b"));
}
private void callHeaders() {
// Dubbo Service call
System.out.println(restService.headers("a", "b", 10));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.headers("b", 10, "a"));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.headers("b", "a", 10));
}
private void callParam() {
// Dubbo Service call
System.out.println(restService.param("mercyblitz"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.param("mercyblitz"));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.param("mercyblitz"));
}
private void callParams() {
// Dubbo Service call
System.out.println(restService.params(1, "1"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.params("1", 1));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.params("1", 1));
// RestTemplate call
System.out.println(restTemplate.getForEntity("http://spring-cloud-alibaba-dubbo/param?param=小马哥", String.class));
}
private void callRequestBodyMap() {
Map<String, Object> data = new HashMap<>();
data.put("id", 1);
data.put("name", "小马哥");
data.put("age", 33);
// Dubbo Service call
System.out.println(restService.requestBodyMap(data, "Hello,World"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
// System.out.println(dubboFeignRestService.requestBody("Hello,World", data));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.requestBody("Hello,World", data));
// RestTemplate call
System.out.println(restTemplate.postForObject("http://spring-cloud-alibaba-dubbo/request/body/map?param=小马哥", data, User.class));
}
@Bean
@LoadBalanced
@DubboTransported
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(DubboSpringCloudBootstrap.class)
.run(args);

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Test;
/**
* {@link AbstractHttpRequestMatcher} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractHttpRequestMatcherTest<T extends AbstractHttpRequestMatcher> {
@Test
public abstract void testEqualsAndHashCode();
@Test
public abstract void testGetContent();
@Test
public abstract void testGetToStringInfix();
}

@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import java.lang.reflect.Constructor;
/**
* {@link AbstractMediaTypeExpression} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractMediaTypeExpressionTest<T extends AbstractMediaTypeExpression> {
protected T createExpression(String expression) {
ResolvableType resolvableType = ResolvableType.forType(getClass().getGenericSuperclass());
Class<T> firstGenericType = (Class<T>) resolvableType.resolveGeneric(0);
Constructor<T> constructor = null;
try {
constructor = firstGenericType.getDeclaredConstructor(String.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return BeanUtils.instantiateClass(constructor, expression);
}
@Test
public void testGetMediaTypeAndNegated() {
// Normal
AbstractMediaTypeExpression expression = createExpression(MediaType.APPLICATION_JSON_VALUE);
Assert.assertEquals(MediaType.APPLICATION_JSON, expression.getMediaType());
Assert.assertFalse(expression.isNegated());
// Negated
expression = createExpression("!" + MediaType.APPLICATION_JSON_VALUE);
Assert.assertEquals(MediaType.APPLICATION_JSON, expression.getMediaType());
Assert.assertTrue(expression.isNegated());
}
@Test
public void testEqualsAndHashCode() {
Assert.assertEquals(createExpression(MediaType.APPLICATION_JSON_VALUE), createExpression(MediaType.APPLICATION_JSON_VALUE));
Assert.assertEquals(createExpression(MediaType.APPLICATION_JSON_VALUE).hashCode(),
createExpression(MediaType.APPLICATION_JSON_VALUE).hashCode());
}
@Test
public void testCompareTo() {
Assert.assertEquals(0,
createExpression(MediaType.APPLICATION_JSON_VALUE).compareTo(createExpression(MediaType.APPLICATION_JSON_VALUE)));
}
}

@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import java.lang.reflect.Constructor;
/**
* {@link AbstractNameValueExpression} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractNameValueExpressionTest<T extends AbstractNameValueExpression> {
protected T createExpression(String expression) {
ResolvableType resolvableType = ResolvableType.forType(getClass().getGenericSuperclass());
Class<T> firstGenericType = (Class<T>) resolvableType.resolveGeneric(0);
Constructor<T> constructor = null;
try {
constructor = firstGenericType.getDeclaredConstructor(String.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return BeanUtils.instantiateClass(constructor, expression);
}
@Test
public void testGetNameAndValue() {
// Normal Name and value
AbstractNameValueExpression expression = createExpression("a=1");
Assert.assertEquals("a", expression.getName());
Assert.assertFalse(expression.isNegated());
expression = createExpression("a=1");
Assert.assertEquals("a", expression.getName());
Assert.assertEquals("1", expression.getValue());
Assert.assertFalse(expression.isNegated());
// Negated Name
expression = createExpression("!a");
Assert.assertEquals("a", expression.getName());
Assert.assertTrue(expression.isNegated());
expression = createExpression("a!=1");
Assert.assertEquals("a", expression.getName());
Assert.assertEquals("1", expression.getValue());
Assert.assertTrue(expression.isNegated());
}
@Test
public void testEqualsAndHashCode() {
Assert.assertEquals(createExpression("a"), createExpression("a"));
Assert.assertEquals(createExpression("a").hashCode(), createExpression("a").hashCode());
Assert.assertEquals(createExpression("a=1"), createExpression("a = 1 "));
Assert.assertEquals(createExpression("a=1").hashCode(), createExpression("a = 1 ").hashCode());
Assert.assertNotEquals(createExpression("a"), createExpression("b"));
Assert.assertNotEquals(createExpression("a").hashCode(), createExpression("b").hashCode());
}
}

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.http.MediaType;
/**
* {@link ConsumeMediaTypeExpression} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ConsumeMediaTypeExpressionTest extends AbstractMediaTypeExpressionTest<ConsumeMediaTypeExpression> {
@Test
public void testMatch() {
ConsumeMediaTypeExpression expression = createExpression(MediaType.ALL_VALUE);
Assert.assertTrue(expression.match(MediaType.APPLICATION_JSON_UTF8));
expression = createExpression(MediaType.APPLICATION_JSON_VALUE);
Assert.assertTrue(expression.match(MediaType.APPLICATION_JSON_UTF8));
expression = createExpression(MediaType.APPLICATION_JSON_VALUE + ";q=0.7");
Assert.assertTrue(expression.match(MediaType.APPLICATION_JSON_UTF8));
expression = createExpression(MediaType.TEXT_HTML_VALUE);
Assert.assertFalse(expression.match(MediaType.APPLICATION_JSON_UTF8));
}
}

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.http.HttpRequest;
import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder;
/**
* {@link HeaderExpression} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HeaderExpressionTest extends AbstractNameValueExpressionTest<HeaderExpression> {
@Test
public void testIsCaseSensitiveName() {
Assert.assertFalse(createExpression("a=1").isCaseSensitiveName());
Assert.assertFalse(createExpression("a=!1").isCaseSensitiveName());
Assert.assertFalse(createExpression("b=1").isCaseSensitiveName());
}
@Test
public void testMatch() {
HeaderExpression expression = createExpression("a=1");
HttpRequest request = builder().build();
Assert.assertFalse(expression.match(request));
request = builder().header("a", "").build();
Assert.assertFalse(expression.match(request));
request = builder().header("a", "2").build();
Assert.assertFalse(expression.match(request));
request = builder().header("", "1").build();
Assert.assertFalse(expression.match(request));
request = builder().header("a", "1").build();
Assert.assertTrue(expression.match(request));
}
}

@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.springframework.http.HttpMethod;
import java.util.Arrays;
import java.util.HashSet;
/**
* {@link HttpRequestMethodsMatcher} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestMethodsMatcherTest extends AbstractHttpRequestMatcherTest<HttpRequestMethodsMatcher> {
HttpRequestMethodsMatcher matcher = new HttpRequestMethodsMatcher("GET");
@Override
public void testEqualsAndHashCode() {
Assert.assertEquals(new HashSet<>(Arrays.asList(HttpMethod.GET)), matcher.getMethods());
}
@Override
public void testGetContent() {
Assert.assertEquals(new HashSet<>(Arrays.asList(HttpMethod.GET)), matcher.getContent());
}
@Override
public void testGetToStringInfix() {
Assert.assertEquals(" || ", matcher.getToStringInfix());
}
}

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.mock.http.client.MockClientHttpRequest;
import java.net.URI;
import java.util.List;
import java.util.Map;
/**
* {@link HttpRequestParamsMatcher} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpRequestParamsMatcherTest {
// @Test
// public void testGetParams() {
//
// HttpRequestParamsMatcher matcher = new HttpRequestParamsMatcher(
// "a ",
// "a =1",
// "b = 2",
// "b = 3 ",
// " c = 4 ",
// " d"
// );
//
// Map<String, List<String>> params = matcher.getParams();
// Assert.assertEquals(4, params.size());
// Assert.assertTrue(params.containsKey("a"));
// Assert.assertTrue(params.containsKey("b"));
// Assert.assertTrue(params.containsKey("c"));
// Assert.assertTrue(params.containsKey("d"));
// Assert.assertFalse(params.containsKey("e"));
//
// List<String> values = params.get("a");
// Assert.assertEquals(2, values.size());
// Assert.assertEquals("", values.get(0));
// Assert.assertEquals("1", values.get(1));
//
// values = params.get("b");
// Assert.assertEquals(2, values.size());
// Assert.assertEquals("2", values.get(0));
// Assert.assertEquals("3", values.get(1));
//
// values = params.get("c");
// Assert.assertEquals(1, values.size());
// Assert.assertEquals("4", values.get(0));
//
// values = params.get("d");
// Assert.assertEquals(1, values.size());
// Assert.assertEquals("", values.get(0));
// }
@Test
public void testEquals() {
HttpRequestParamsMatcher matcher = new HttpRequestParamsMatcher("a ", "a = 1");
MockClientHttpRequest request = new MockClientHttpRequest();
request.setURI(URI.create("http://dummy/?a"));
Assert.assertTrue(matcher.match(request));
request.setURI(URI.create("http://dummy/?a&a=1"));
Assert.assertTrue(matcher.match(request));
matcher = new HttpRequestParamsMatcher("a ", "a =1", "b", "b=2");
request.setURI(URI.create("http://dummy/?a&a=1&b"));
Assert.assertTrue(matcher.match(request));
request.setURI(URI.create("http://dummy/?a&a=1&b&b=2"));
Assert.assertTrue(matcher.match(request));
matcher = new HttpRequestParamsMatcher("a ", "a =1", "b", "b=2", "b = 3 ");
request.setURI(URI.create("http://dummy/?a&a=1&b&b=2&b=3"));
Assert.assertTrue(matcher.match(request));
request.setURI(URI.create("http://dummy/?d=1"));
Assert.assertFalse(matcher.match(request));
}
}

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.http.HttpRequest;
import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder;
/**
* {@link ParamExpression} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ParamExpressionTest extends AbstractNameValueExpressionTest<ParamExpression> {
@Test
public void testIsCaseSensitiveName() {
Assert.assertTrue(createExpression("a=1").isCaseSensitiveName());
Assert.assertTrue(createExpression("a=!1").isCaseSensitiveName());
Assert.assertTrue(createExpression("b=1").isCaseSensitiveName());
}
@Test
public void testMatch() {
ParamExpression expression = createExpression("a=1");
HttpRequest request = builder().build();
Assert.assertFalse(expression.match(request));
request = builder().param("a", "").build();
Assert.assertFalse(expression.match(request));
request = builder().param("a", "2").build();
Assert.assertFalse(expression.match(request));
request = builder().param("", "1").build();
Assert.assertFalse(expression.match(request));
request = builder().param("a", "1").build();
Assert.assertTrue(expression.match(request));
}
}

@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.matcher;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.http.MediaType;
import java.util.Arrays;
/**
* {@link ProduceMediaTypeExpression} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ProduceMediaTypeExpressionTest extends AbstractMediaTypeExpressionTest<ProduceMediaTypeExpression> {
@Test
public void testMatch() {
ProduceMediaTypeExpression expression = createExpression(MediaType.APPLICATION_JSON_VALUE);
Assert.assertTrue(expression.match(Arrays.asList(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON)));
expression = createExpression(MediaType.APPLICATION_JSON_VALUE);
Assert.assertFalse(expression.match(Arrays.asList(MediaType.APPLICATION_XML)));
}
}

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.http.util;
import org.junit.Assert;
import org.junit.Test;
/**
* {@link HttpUtils} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpUtilsTest {
@Test
public void testEncodeAndDecode() {
String whitespace = " ";
String encodedValue = HttpUtils.encode(" ");
String decodedValue = HttpUtils.decode(encodedValue);
Assert.assertEquals(whitespace, decodedValue);
}
}

@ -0,0 +1,136 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* {@link RequestMetadata} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestMetadataTest {
private String method = "GET";
private String url = "/param";
private Set<String> paramNames = new LinkedHashSet<>(Arrays.asList("a", "b", "c"));
private Set<String> headerNames = new LinkedHashSet<>(Arrays.asList("d", "e", "f"));
@Test
public void testEqualsAndHashCodeAndCompareTo() {
RequestMetadata metadata = new RequestMetadata();
RequestMetadata metadata2 = new RequestMetadata();
Assert.assertEquals(metadata, metadata2);
Assert.assertEquals(metadata.hashCode(), metadata2.hashCode());
metadata.setMethod(method);
metadata2.setMethod(method);
Assert.assertEquals(metadata, metadata2);
Assert.assertEquals(metadata.hashCode(), metadata2.hashCode());
metadata.setPath(url);
metadata2.setPath(url);
Assert.assertEquals(metadata, metadata2);
Assert.assertEquals(metadata.hashCode(), metadata2.hashCode());
metadata.addParam("a", "1").addParam("b", "2").addParam("c", "3");
metadata2.addParam("a", "1a").addParam("b", "2b").addParam("c", "3c");
Assert.assertEquals(metadata, metadata2);
Assert.assertEquals(metadata.hashCode(), metadata2.hashCode());
metadata.addHeader("d", "1").addHeader("e", "2").addHeader("f", "3");
metadata2.addHeader("d", "1").addHeader("e", "2");
Assert.assertNotEquals(metadata, metadata2);
Assert.assertNotEquals(metadata.hashCode(), metadata2.hashCode());
}
// @Test
// public void testBestMatch() {
//
// NavigableMap<RequestMetadata, RequestMetadata> requestMetadataMap = new TreeMap<>();
//
// RequestMetadata metadata = new RequestMetadata();
// metadata.setMethod(method);
//
// RequestMetadata metadata1 = new RequestMetadata();
// metadata1.setMethod(method);
// metadata1.setPath(url);
//
// RequestMetadata metadata2 = new RequestMetadata();
// metadata2.setMethod(method);
// metadata2.setPath(url);
// metadata2.setParams(paramNames);
//
// RequestMetadata metadata3 = new RequestMetadata();
// metadata3.setMethod(method);
// metadata3.setPath(url);
// metadata3.setParams(paramNames);
// metadata3.setHeaders(headerNames);
//
// requestMetadataMap.put(metadata, metadata);
// requestMetadataMap.put(metadata1, metadata1);
// requestMetadataMap.put(metadata2, metadata2);
// requestMetadataMap.put(metadata3, metadata3);
//
// RequestMetadata result = getBestMatch(requestMetadataMap, metadata);
// Assert.assertEquals(result, metadata);
//
// result = getBestMatch(requestMetadataMap, metadata1);
// Assert.assertEquals(result, metadata1);
//
// result = getBestMatch(requestMetadataMap, metadata2);
// Assert.assertEquals(result, metadata2);
//
// result = getBestMatch(requestMetadataMap, metadata3);
// Assert.assertEquals(result, metadata3);
//
// // REDO
// requestMetadataMap.clear();
//
// requestMetadataMap.put(metadata1, metadata1);
//
// result = getBestMatch(requestMetadataMap, metadata2);
// Assert.assertEquals(metadata1, result);
//
// requestMetadataMap.put(metadata2, metadata2);
//
// result = getBestMatch(requestMetadataMap, metadata3);
// Assert.assertEquals(metadata2, result);
//
// result = getBestMatch(requestMetadataMap, new RequestMetadata());
// Assert.assertNull(result);
//
// result = getBestMatch(requestMetadataMap, metadata);
// Assert.assertNull(result);
//
// }
}

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.metadata.resolver;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.mock.env.MockEnvironment;
import java.util.Set;
/**
* {@link DubboTransportedMethodMetadataResolver} Test
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboTransportedMethodMetadataResolverTest {
private DubboTransportedMethodMetadataResolver resolver;
private MockEnvironment environment;
@Before
public void init() {
environment = new MockEnvironment();
resolver = new DubboTransportedMethodMetadataResolver(environment, new SpringMvcContract());
}
@Test
public void testResolve() {
Set<DubboTransportedMethodMetadata> metadataSet = resolver.resolveDubboTransportedMethodMetadataSet(TestDefaultService.class);
Assert.assertEquals(1, metadataSet.size());
}
@DubboTransported
interface TestDefaultService {
String test(String message);
}
}

@ -1,65 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.rpc.RpcContext;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
/**
* Default {@link EchoService}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Service(version = "1.0.0", protocol = {"dubbo", "rest"})
@RestController
@Path("/")
public class DefaultEchoService implements EchoService {
@Override
@GetMapping(value = "/echo"
// consumes = MediaType.APPLICATION_JSON_VALUE,
// produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
@Path("/echo")
@GET
// @Consumes("application/json")
// @Produces("application/json;charset=UTF-8")
public String echo(@RequestParam @QueryParam("message") String message) {
return RpcContext.getContext().getUrl() + " [echo] : " + message;
}
@Override
@PostMapping("/plus")
@Path("/plus")
@POST
public String plus(@RequestParam @QueryParam("a") int a, @RequestParam @QueryParam("b") int b) {
return null;
}
}

@ -16,15 +16,27 @@
*/
package org.springframework.cloud.alibaba.dubbo.service;
import java.util.Map;
/**
* Echo Service
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public interface EchoService {
public interface RestService {
String param(String param);
String params(int a, String b);
String headers(String header, String header2, Integer param);
String pathVariables(String path1, String path2, String param);
String form(String form);
String echo(String message);
User requestBodyMap(Map<String, Object> data, String param);
String plus(int a, int b);
Map<String, Object> requestBodyUser(User user);
}

@ -0,0 +1,149 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import com.alibaba.dubbo.rpc.RpcContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE;
/**
* Default {@link RestService}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@com.alibaba.dubbo.config.annotation.Service(version = "1.0.0", protocol = {"dubbo", "rest"})
@RestController
@Path("/")
public class StandardRestService implements RestService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
@GetMapping(value = "/param")
@Path("/param")
@GET
public String param(@RequestParam @QueryParam("param") String param) {
log("/param", param);
return param;
}
@Override
@PostMapping("/params")
@Path("/params")
@POST
public String params(@RequestParam @QueryParam("a") int a, @RequestParam @QueryParam("b") String b) {
log("/params", a + b);
return a + b;
}
@Override
@GetMapping("/headers")
@Path("/headers")
@GET
public String headers(@RequestHeader("h") @HeaderParam("h") String header,
@RequestHeader("h2") @HeaderParam("h2") String header2,
@RequestParam("v") @QueryParam("v") Integer param) {
String result = header + " , " + header2 + " , " + param;
log("/headers", result);
return result;
}
@Override
@GetMapping("/path-variables/{p1}/{p2}")
@Path("/path-variables/{p1}/{p2}")
@GET
public String pathVariables(@PathVariable("p1") @PathParam("p1") String path1,
@PathVariable("p2") @PathParam("p2") String path2,
@RequestParam("v") @QueryParam("v") String param) {
String result = path1 + " , " + path2 + " , " + param;
log("/path-variables", result);
return result;
}
// @CookieParam does not support : https://github.com/OpenFeign/feign/issues/913
// @CookieValue also does not support
@Override
@PostMapping("/form")
@Path("/form")
@POST
public String form(@RequestParam("f") @FormParam("f") String form) {
return String.valueOf(form);
}
@Override
@PostMapping(value = "/request/body/map", produces = APPLICATION_JSON_UTF8_VALUE)
@Path("/request/body/map")
@POST
@Produces(APPLICATION_JSON_VALUE)
public User requestBodyMap(@RequestBody Map<String, Object> data, @RequestParam("param") @QueryParam("param") String param) {
User user = new User();
user.setId(((Integer) data.get("id")).longValue());
user.setName((String) data.get("name"));
user.setAge((Integer) data.get("age"));
log("/request/body/map", param);
return user;
}
@PostMapping(value = "/request/body/user", consumes = APPLICATION_JSON_UTF8_VALUE)
@Path("/request/body/user")
@POST
@Override
@Consumes(APPLICATION_JSON_UTF8_VALUE)
public Map<String, Object> requestBodyUser(@RequestBody User user) {
Map<String, Object> map = new HashMap<>();
map.put("id", user.getId());
map.put("name", user.getName());
map.put("age", user.getAge());
return map;
}
private void log(String url, Object result) {
String message = String.format("The client[%s] uses '%s' protocol to call %s : %s",
RpcContext.getContext().getRemoteHostName(),
RpcContext.getContext().getUrl() == null ? "N/A" : RpcContext.getContext().getUrl().getProtocol(),
url,
result
);
if (logger.isInfoEnabled()) {
logger.info(message);
}
}
}

@ -0,0 +1,68 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.dubbo.service;
import javax.ws.rs.FormParam;
import java.io.Serializable;
/**
* User Entity
*/
public class User implements Serializable {
@FormParam("id")
private Long id;
@FormParam("name")
private String name;
@FormParam("age")
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}

@ -12,5 +12,9 @@ dubbo:
registry:
address: spring-cloud://nacos
feign:
hystrix:
enabled: true
server:
port: 8080

@ -7,6 +7,8 @@ spring:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
main:
allow-bean-definition-overriding: true
eureka:
client:

@ -44,6 +44,7 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>

@ -112,7 +112,7 @@ Consumer端在服务调用之前先定义限流规则。
根据Provider端中发布的定义使用Dubbo的@Reference注解注入服务对应的Bean
@Reference(version = "${foo.service.version}", application = "${dubbo.application.id}",
url = "dubbo://localhost:12345", timeout = 30000)
path = "dubbo://localhost:12345", timeout = 30000)
private FooService fooService;
由于设置的qps是10。调用15次查看是否被限流

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save