Dubbo 2 基于 Spring 与 SpringMVC 支持 REST 协议

大纲

前言

随着微服务的流行以及多语言互操作诉求日益增多,在 Dubbo 中暴露 REST 服务变成了一个不容忽视的诉求。为了在 Dubbo 中暴露 REST 服务,通常有两种做法,一种是直接依赖 Spring REST 或者其他 REST 框架来直接暴露,另一种是通过 Dubbo 框架内置的 REST 能力暴露。两种做法各有优缺点,主要体现在前者与微服务体系中的服务发现组件能够更好地工作,而后者可以无缝的享受到 Dubbo 体系中的服务发现以及服务治理的能力。本文将介绍如何通过 Dubbo 框架内置的 REST 能力来暴露 REST 服务。

提示

  • 自 Dubbo 2.6.0 版本开始,Dubbo 合并了当当网捐献的 DubboX 4 中的主要特性,其中就包括了基于 RESTeasy 3.0.19.Final 的 REST 支持,具备 JAX-RS 2.0 规范中所有的能力。
  • 自 Dubbo 3.3 版本开始,原有的 REST 协议实现已被移除,由 Triple 协议提供更全面的 REST 支持。使用 Triple 协议发布 REST 风格的服务非常简单,只需要在服务接口上添加相应的注解即可,支持三种注解方式:内置注解、Spring Web 注解、JAX-RS 注解。

学习资源

版本对应关系

Spring 与 Dubbo 的版本必须互相匹配,否则不同版本之间可能会存在兼容性问题,最终导致服务无法正常运行。两者的版本对应关系如下:

Dubbo 版本适配 Spring 版本核心特性 / 注意事项
‌Dubbo 3.x‌ (3.3.0+)✅ Spring 6.x
✅ Spring 5.3+
(1) 原生支持 Spring 6 注解驱动
(2) 依赖 Jakarta EE 9+(需 JDK 17+)
(3) 默认集成 Spring Boot 3.x
‌Dubbo 3.0.x‌ (3.0.0 - 3.2.x)✅ Spring 5.3+
⚠️ Spring 6.x(需手动适配)
(1) 需手动排除旧版 Jakarta API(jakarta.*
(2) 建议搭配 Spring Boot 2.7+
‌Dubbo 2.7.x✅ Spring 5.2+
❌ Spring 6.x
(1) 仅支持 Java EE(javax.*
(2) 官方已停止维护,仅建议旧项目使用

Dubbo 整合案例

本节将整合 Spring、SpringMVC 与 Dubbo,并配置 Dubbo 支持使用 REST 协议,使用的注册中心是 ZooKeeper。

项目结构

版本说明

组件版本说明
Spring5.2.25.RELEASE
Dubbo2.7.23
Zookeeper Server3.5.7ZooKeeper 服务器,作为服务注册中心
Curator Recipes5.5.0用于 Dubbo 连接 ZooKeeper 注册中心,依赖 Curator Client 5.5.0 和 ZooKeeper Client 3.7.1
JDK1.8支持 JDK 1.8 及以上版本

模块说明

  • api:抽取出来的公共模块,存放公用的实体类和接口
  • provider:服务提供者,实现 api 模块中的接口
  • customer:服务消费者,调用服务提供者中的接口

案例代码

API 模块

  • 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<properties>
<dubbo.version>2.7.23</dubbo.version>
<lombok.version>1.18.16</lombok.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
  • 实体类,需要实现 Serializable 接口,并提供默认构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

private Long id;

private String name;

private Integer age;

}
  • API 接口,需要在接口中标记 JAX-RS 注解(或者在接口的实现类中标记注解),JAX-RS 注解的使用说明请看 这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/user")
public interface UserService {

@POST
@Path("/add")
@Consumes(MediaType.APPLICATION_JSON) // 接收 JSON 格式的数据
@Produces({MediaType.APPLICATION_JSON}) // 返回 JSON 格式的数据
Boolean add(User user);

@GET
@Path("/getById/{id}")
@Produces({MediaType.APPLICATION_JSON}) // 返回 JSON 格式的数据
User getById(@PathParam("id") Long id);

}

提示

  • JAX-RS 支持在类级别上定义 @Consumers@Produces 注解来规定参数以及返回值的类型。在类级别上统一定义之后,就可以不用在方法级别上进一步单独定义。

Provider 模块

  • 引入依赖,由于需要 Dubbo 支持 REST 协议,因此这里额外引入了 dubbo-rpc-rest 依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<properties>
<javax.servlet.version>3.1.0</javax.servlet.version>
<spring.version>5.2.25.RELEASE</spring.version>
<dubbo.version>2.7.23</dubbo.version>
<curator.version>5.5.0</curator.version>
<commons.version>3.4</commons.version>
<slf4j.version>1.7.21</slf4j.version>
</properties>

<dependencies>
<!-- API -->
<dependency>
<groupId>com.clay.dubbo</groupId>
<artifactId>dubbo-lesson-07-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
<scope>provided</scope>
</dependency>
<!--Dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--Dubbo 支持 REST 协议-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--Curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<!--Commons-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.version}</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
  • 接口实现类,@DubboService 注解主要用于暴露服务,使其能够被 Dubbo 框架识别并注册到服务注册中心,这里需要通过 protocol 属性来指定 Dubbo 使用 REST 协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(protocol = "rest")
public class UserServiceImpl implements UserService {

@Override
public Boolean add(User user) {
System.out.println(user);
return true;
}

@Override
public User getById(Long id) {
return new User(id, "Peter", 18);
}

}
  • Spring 的 XML 配置文件(application-context.xml

重要提示

  • 在 Spring 的 XML 配置文件中,<dubbo:protocol name="rest" port="8080" server="netty"/> 的作用是指定 Dubbo 使用 REST 协议,并使用基于 Netty 框架的 REST Server。
  • 如果 Spring 应用最终是以 War 包的方式独立部署在外部(单独安装)的 Tomcat 中,并使用了 Netty 框架的 REST Server,那么 <dubbo:protocol> 中配置的端口不能与外部 Tomcat 的端口一样,否则会出现端口冲突问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 扫描并注册 Spring 组件 -->
<context:component-scan base-package="com.clay.dubbo.producer"/>

<!-- 配置服务应用名 -->
<dubbo:application name="dubbo-provider-application">
<dubbo:parameter key="qos.enable" value="true"/>
<dubbo:parameter key="qos.port" value="22222"/>
<dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
</dubbo:application>

<!-- 注册中心配置 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="5000"/>

<!-- 元数据中心配置 -->
<dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>

<!-- 配置服务协议 -->
<dubbo:protocol name="rest" port="8080" server="netty"/>

<!-- 开启 Dubbo 的注解扫描 -->
<dubbo:annotation package="com.clay.dubbo.producer"/>

</beans>
  • SpringMVC 的 XML 配置文件(application-mvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 启用 SpringMVC 的注解驱动功能 -->
<mvc:annotation-driven/>

<!-- 扫描 Web 控制器 -->
<context:component-scan base-package="com.clay.dubbo.producer.controller"/>

</beans>
  • Java EE 的 XML 配置文件(web.xml),存放在项目中的 /src/main/webapp/WEB-INF 目录下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/Java EE"
xsi:schemaLocation="http://java.sun.com/xml/ns/Java EE http://java.sun.com/xml/ns/Java EE/web-app_2_5.xsd"
version="2.5">

<!-- Spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/application-context.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- SpringMVC -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/application-mvc.xml</param-value>
</init-param>
</servlet>

<!-- Servlet -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>
  • Log4j 的配置文件(log4j.properties
1
2
3
4
5
6
7
8
log4j.rootLogger=INFO, stdout, file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=log/app.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n

Consumer 模块

  • 引入依赖,由于需要 Dubbo 支持 REST 协议,因此这里额外引入了 dubbo-rpc-rest 依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<properties>
<javax.servlet.version>3.1.0</javax.servlet.version>
<spring.version>5.2.25.RELEASE</spring.version>
<dubbo.version>2.7.23</dubbo.version>
<curator.version>5.5.0</curator.version>
<commons.version>3.4</commons.version>
<slf4j.version>1.7.21</slf4j.version>
</properties>

<dependencies>
<!-- API -->
<dependency>
<groupId>com.clay.dubbo</groupId>
<artifactId>dubbo-lesson-07-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
<scope>provided</scope>
</dependency>
<!--Dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--Dubbo 支持 REST 协议-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--Curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<!--Commons-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.version}</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
  • 业务测试类,通过 @DubboReference 注解在服务消费者端引用远程服务提供者的服务,并设置 protocol 属性来指定 Dubbo 使用 REST 协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/system")
public class SystemController {

/**
* 引用 Dubbo 服务
*/
@DubboReference(protocol = "rest")
private UserService userService;

@GetMapping("/getUser/{id}")
public User getUser(@PathVariable("id") Long id) {
return userService.getById(id);
}

@PostMapping("/addUser")
public Boolean addUser(@RequestBody User user) {
return userService.add(user);
}

}
  • Spring 的 XML 配置文件(application-context.xml
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 扫描并注册 Spring 组件 -->
<context:component-scan base-package="com.clay.dubbo.consumer"/>

</beans>
  • SpringMVC 的 XML 配置文件(application-mvc.xml

特别注意

在服务消费者模块中,必须先扫描 Dubbo 的注解(如 @DubboReference,为了引用远程服务),然后再扫描 Spring 的注解(如 @Controller),否则会出现 @DubboReference 注解失效的问题,详细分析说明请看这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 启用 SpringMVC 的注解驱动功能 -->
<mvc:annotation-driven/>

<!-- 配置服务应用名 -->
<dubbo:application name="dubbo-consumer-application">
<dubbo:parameter key="qos.enable" value="true"/>
<dubbo:parameter key="qos.port" value="22223"/>
<dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
</dubbo:application>

<!-- 注册中心配置 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="5000"/>

<!-- 元数据中心配置 -->
<dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>

<!-- 特别注意,在消费者模块中,必须先扫描 Dubbo 注解,然后再扫描 Web 控制器,否则 Web 控制器中的 @DubboReference 注解将无法实现注入 -->

<!-- 开启 Dubbo 注解扫描-->
<dubbo:annotation package="com.clay.dubbo.consumer"/>

<!-- 扫描 Web 控制器 -->
<context:component-scan base-package="com.clay.dubbo.consumer.controller"/>

</beans>
  • Java EE 的 XML 配置文件(web.xml),存放在项目中的 /src/main/webapp/WEB-INF 目录下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/Java EE"
xsi:schemaLocation="http://java.sun.com/xml/ns/Java EE http://java.sun.com/xml/ns/Java EE/web-app_2_5.xsd"
version="2.5">

<!-- Spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/application-context.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- SpringMVC -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/application-mvc.xml</param-value>
</init-param>
</servlet>

<!-- Servlet -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>
  • Log4j 的配置文件(log4j.properties
1
2
3
4
5
6
7
8
log4j.rootLogger=INFO, stdout, file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=log/app.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n

测试代码

  • (1) 在 IDEA 开发工具内,将 Provider 和 Consumer 模块以 War 包的方式按顺序分别单独部署到外部(本地单独安装)的 Tomcat 中,如下图所示:

  • (2) 使用 Postman 等工具访问 http://127.0.0.1:8080/user/getById/1001 接口,若接口可以返回正确的 JSON 数据结果,则说明 Provider 模块中的 Dubbo 服务可以支持 REST 协议。

  • (3) 使用 Postman 等工具访问 http://127.0.0.1:9095/system/getUser/1001 接口,若接口可以返回正确的 JSON 数据结果,则说明 Consumer 模块可以通过 REST 协议远程调用 Provider 模块提供的服务。

下载代码

  • 完整的案例代码可以直接从 GitHub 下载对应章节 dubbo-lesson-07

Dubbo 其他配置

JAX-RS 注解使用

JAX-RS 和 Spring MVC 核心注解的对比

功能 JAX-RS 注解 Spring MVC 注解说明
处理 GET 请求@GET@RequestMapping(method = RequestMethod.GET)处理 HTTP GET 请求
处理 POST 请求@POST@RequestMapping(method = RequestMethod.POST)处理 HTTP POST 请求
指定 URL 路径@Path("/user")@RequestMapping("/user")定义请求路径
指定请求参数@QueryParam("name")@RequestParam("name")绑定 URL 查询参数,如 /users?name=Tom
指定路径参数@PathParam("id")@PathVariable("id")绑定 URL 路径变量,如 /users/123
指定请求体格式@Consumes(MediaType.APPLICATION_JSON)@RequestBody指定方法接收的 Content-Type,JAX-RS 需搭配 @POST@PUT 注解使用
指定响应体格式@Produces(MediaType.APPLICATION_JSON)@ResponseBody指定方法返回的 Content-Type
指定请求头参数@HeaderParam("token")@RequestHeader("token")绑定请求头参数

JAX-RS 注解标记在接口和实现类中的区别

  • 标记在接口中

    • 优点:
      • @Path@Consumes@Produces 直接写在接口中时,所有实现类都会继承这些 REST 规则。
      • 让接口本身成为 RESTful 规范的一部分,方便生成 API 文档(如 OpenAPI / Swagger)。
      • 适用于 Dubbo + REST 这种分布式场景,客户端可以直接引用接口,不需要依赖具体实现。
      • 在多实现类的情况下,接口可以保证所有实现类都遵循相同的 REST 规则。
    • 适用场景:
      • 适用于 Dubbo + REST 场景,因为 Dubbo 主要通过接口暴露服务。
  • 标记在实现类中

    • 优点:
      • @Path@Consumes@Produces 只作用于实现类,而接口本身不受影响。
      • 更符合面向实现的 REST 风格,不会污染接口,使接口可以用于其他用途(比如 RPC)。
      • 如果有多个实现类,可以为不同的实现类定义不同的 REST 规则。
      • 在 Spring + JAX-RS 组合的场景下,更容易与 Spring 组件(如 @Service)集成。
    • 适用场景:
      • 适用于 Spring 生态,因为 Spring 推荐在实现类中标注注解。

总结

  • JAX-RS 主要用于 Java EE / Jakarta EE,适用于 Dubbo + REST 方案。
  • Spring MVC 的 @RequestMapping 及其变体(@GetMapping@PostMapping 等)整合度更高,而 JAX-RS 依赖 @Path@GET / @POST 等注解组合使用。
  • JAX-RS 支持在类级别上定义 @Consumers@Produces 注解来规定参数以及返回值的类型。在类级别上统一定义之后,就可以不用在方法级别上进一步定义。

同时支持多种协议

配置说明

提示

  • 在生产环境中,往往需要内部系统使用 Dubbo 协议来调用服务,而外部系统则使用 REST 协议(HTTP)来调用服务。

Dubbo 可以同时支持多种协议(可能需要额外引入相应的 Maven 依赖),也就是在服务提供者的 Spring XML 配置文件中,可以同时配置多种协议,但不同协议必须使用不同的端口,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- Dubbo 协议 -->
<dubbo:protocol name="dubbo" port="20880"/>

<!-- REST 协议 -->
<dubbo:protocol name="rest" port="7777" server="tomcat"/>

<!-- Hessian 协议 -->
<dubbo:protocol name="hessian" port="8888" server="tomcat"/>

<!-- 这个接口仅支持 Dubbo 协议 -->
<dubbo:service interface="com.example.api.OrderService" ref="orderServiceImpl" protocol="dubbo"/>

<!-- 这个接口仅支持 REST 协议 -->
<dubbo:service interface="com.example.api.RoleService" ref="roleServiceImpl" protocol="rest"/>

<!-- 这个接口仅支持 Hessian 协议 -->
<dubbo:service interface="com.example.api.UserService" ref="userServiceImpl" protocol="hessian"/>

<!-- 这个接口支持 Dubbo 协议和 REST 协议(多种协议使用逗号隔开) -->
<dubbo:service interface="com.example.api.SystemService" ref="systemServiceImpl" protocol="dubbo,rest"/>

当服务消费者调用远程服务时,只需要在服务提供者的 Spring XML 配置文件中,直接指定对应的协议即可,如下所示:

1
2
3
4
5
6
7
8
<!-- 这个接口使用 Dubbo 协议 -->
<dubbo:reference id="orderService" interface="com.example.api.OrderService" protocol="dubbo"/>

<!-- 这个接口使用 REST 协议 -->
<dubbo:reference id="roleService" interface="com.example.api.RoleService" protocol="rest"/>

<!-- 这个接口使用 Dubbo 协议和 REST 协议(多种协议使用逗号隔开),消费者默认会按 Dubbo -> REST 的协议顺序尝试调用 -->
<dubbo:reference id="systemService" interface="com.example.api.SystemService" protocol="dubbo,rest"/>

配置案例

在上面案例代码的基础上,为了让 Provider 模块支持多种协议(比如 Dubbo 和 REST 协议),可以做以下更改。

  • (1) 在 Provider 模块(服务提供者)的 Spring XML 配置文件中,新增 Dubbo 协议的定义(不同协议必须使用不同的端口)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 扫描并注册 Spring 组件 -->
<context:component-scan base-package="com.clay.dubbo.producer"/>

<!-- 配置服务应用名 -->
<dubbo:application name="dubbo-provider-application">
<dubbo:parameter key="qos.enable" value="true"/>
<dubbo:parameter key="qos.port" value="22222"/>
<dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
</dubbo:application>

<!-- 注册中心配置 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="5000"/>

<!-- 元数据中心配置 -->
<dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>

<!-- 配置服务协议(Dubbo) -->
<dubbo:protocol name="dubbo" port="20880"/>

<!-- 配置服务协议(REST) -->
<dubbo:protocol name="rest" port="8080" server="netty"/>

<!-- 开启 Dubbo 的注解扫描 -->
<dubbo:annotation package="com.clay.dubbo.producer"/>

</beans>
  • (2) 在 Provider 模块(服务提供者)的接口实现类中,通过 @DubboService 注解的 protocol 属性指定使用 Dubbo 和 REST 协议来暴露服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(protocol = {"dubbo", "rest"})
public class UserServiceImpl implements UserService {

@Override
public Boolean add(User user) {
System.out.println(user);
return true;
}

@Override
public User getById(Long id) {
return new User(id, "Peter", 18);
}

}
  • (3) 在 Consumer 模块(服务消费者)的控制器类中,通过 @DubboReference 注解的 protocol 属性指定使用 Dubbo 或者 REST 协议来进行远程服务调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("/system")
public class SystemController {

/**
* 引用 Dubbo 服务
*/
@DubboReference(protocol = "rest")
private UserService userService;

@GetMapping("/getUser/{id}")
public User getUser(@PathVariable("id") Long id) {
return userService.getById(id);
}

@PostMapping("/addUser")
public Boolean addUser(@RequestBody User user) {
return userService.add(user);
}

}

在 Consumer 模块(服务消费者)中,如果需要同时使用多种协议,可以通过 @DubboReference 注解的 protocol 属性指定 Dubbo 和 REST 协议,Dubbo 会按照协议的声明顺序尝试发起调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("/system")
public class SystemController {

/**
* 引用 Dubbo 服务
*
* <p> 根据协议的声明顺序,优先使用 dubbo 协议发起调用,当 dubbo 协议不可用时自动切换到 rest 协议
*/
@DubboReference(protocol = "dubbo,rest")
private UserService userService;

@GetMapping("/getUser/{id}")
public User getUser(@PathVariable("id") Long id) {
return userService.getById(id);
}

@PostMapping("/addUser")
public Boolean addUser(@RequestBody User user) {
return userService.add(user);
}

}

在 Consumer 模块(服务消费者)中,如果需要同时使用多种协议,还可以创建多个 @DubboReference 注解,然后每个 @DubboReference 注解分别指定不同的协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RestController
@RequestMapping("/system")
public class SystemController {

/**
* 引用 Dubbo 服务(使用 REST 协议)
*/
@DubboReference(protocol = "rest")
private UserService userServiceREST;

/**
* 引用 Dubbo 服务(使用 Dubbo 协议)
*/
@DubboReference(protocol = "dubbo")
private UserService userServiceDubbo;

@GetMapping("/getUser/{id}")
public User getUser(@PathVariable("id") Long id) {
return userServiceREST.getById(id);
}

@PostMapping("/addUser")
public Boolean addUser(@RequestBody User user) {
return userServiceDubbo.add(user);
}

}

支持不同类型的 Server

目前在 Dubbo 2 中,REST 协议可以跑在五种不同的 Server 上,分别是:

  • Netty:基于 Netty 框架的 REST Server,通过 <dubbo:protocol name="rest" port="8080" server="netty"/> 来配置。
  • Tomcat:基于嵌入式 Tomcat 的 REST Server,通过 <dubbo:protocol name="rest" port="8080" server="tomcat"/> 来配置。
  • Jetty默认服务器,基于嵌入式 Jetty 的 REST Server,通过 <dubbo:protocol name="rest" port="8080" server="jetty"/> 来配置。
  • SunHttp:使用 JDK 内置的 Sun HTTP server 作为 REST Server,通过 <dubbo:protocol name="rest" port="8080" server="sunhttp"/> 来配置,仅推荐在开发环境中使用。
  • Servlet:采用外部应用服务器的 Servlet 容器(如外部 Tomcat)来做 REST Server,通过 <dubbo:protocol name="rest" server="servlet"/> 来配置,这里不需要配置端口,另外还需要在 web.xml 中做额外的其他配置。

参考资料