大纲
前言
随着微服务的流行以及多语言互操作诉求日益增多,在 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 注解。
学习资源
版本对应关系
SpringBoot 与 Dubbo 的版本必须互相匹配,否则不同版本之间可能会存在兼容性问题,最终导致服务无法正常运行。两者的版本对应关系如下:
Dubbo 分支 | 最新版本 | JDK | SpringBoot | 详细说明 |
---|
3.3.x | 3.3.0 | 8, 17, 21 | 2.x、3.x | 生产可用(推荐,长期维护)! 最新 Triple 协议升级,内置 Metrics、Tracing、GraalVM 支持等 |
3.2.x | 3.2.10 | 8, 17 | 2.x、3.x | 生产可用(长期维护)! |
3.1.x | 3.1.11 | 8, 17 | 2.x、3.x | 仅修复安全漏洞! |
3.0.x | 3.0.15 | 8 | 2.x | 停止维护! |
2.7.x | 2.7.23 | 8 | 2.x | 停止维护! |
2.6.x | 2.6.20 | 6, 7 | - | 停止维护! |
2.5.x | 2.5.10 | 6, 7 | - | 停止维护! |
如果仍然使用版本低于 2.7.0
的旧版 Dubbo,请使用以下 Spring Boot 启动器:
Dubbo Spring Boot Starter | Dubbo | Spring Boot |
---|
0.2.1.RELEASE | 2.6.5+ | 2.x |
0.1.2.RELEASE | 2.6.5+ | 1.x |
Dubbo 整合案例
本节将整合 SpringBoot 与 Dubbo,并配置 Dubbo 支持使用 REST 协议,使用的注册中心是 ZooKeeper。值得一提的是,本教程的内容也适用于 Spring Cloud 项目。
版本说明
组件 | 版本 | 说明 |
---|
SpringBoot | 2.7.18 | |
Dubbo Spring Boot Starter | 2.7.23 | 依赖 Dubbo 2.7.23 |
Zookeeper Server | 3.8.4 | ZooKeeper 服务器,作为服务注册中心 |
Curator Recipes | 5.5.0 | 用于 Dubbo 连接 ZooKeeper 注册中心,依赖 Curator Client 5.5.0 和 ZooKeeper Client 3.7.1 |
JDK | 1.8 | 支持 JDK 1.8 及以上版本 |
提示
- 如果使用的是高版本的 SpringBoot(比如
3.3.3
),那么需要使用 3.3.1
版本的 Dubbo Spring Boot Starter。 - 如果使用的是较低版本的 ZooKeeper 服务器(比如
3.4.10
),那么就需要降低 Curator Recipes 的版本,否则 Dubbo 将无法正常连接 ZooKeeper 服务器。
模块说明
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) @Produces({MediaType.APPLICATION_JSON}) Boolean add(User user);
@GET @Path("/getById/{id}") @Produces({MediaType.APPLICATION_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
| <properties> <spring-boot.version>2.7.18</spring-boot.version> <dubbo.version>2.7.23</dubbo.version> <curator.version>5.5.0</curator.version> </properties>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>com.clay.dubbo</groupId> <artifactId>dubbo-lesson-08-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-rpc-rest</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>${curator.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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); }
}
|
1 2 3 4 5 6 7 8
| @SpringBootApplication public class ProviderApplication {
public static void main(String[] args) { SpringApplication.run(ProviderApplication.class); }
}
|
- 配置文件(
application.yml
),重点是定义 REST 协议,并使用基于嵌入式 Tomcat 的 REST Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| server: port: 9090
spring: application: name: dubbo-provider-application
dubbo: application: name: ${spring.application.name} registry: address: zookeeper://127.0.0.1:2181 protocol: name: rest port: 8080 server: tomcat scan: base-packages: com.clay.dubbo.provider
|
提示
若不希望在 YML 配置文件中指定 dubbo.scan.base-packages
参数,那么可以在主启动类上标注 @EnableDubbo(scanBasePackages = "xxx")
注解或者 @DubboComponentScan(basePackages = "xxx")
注解来替代。
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
| <properties> <spring-boot.version>2.7.18</spring-boot.version> <dubbo.version>2.7.23</dubbo.version> <curator.version>5.5.0</curator.version> </properties>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>com.clay.dubbo</groupId> <artifactId>dubbo-lesson-08-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-rpc-rest</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>${curator.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
|
- 业务测试类,
@DubboReference
注解主要用于在服务消费者端引用 Dubbo 服务提供者的服务,并设置 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
| import org.apache.dubbo.config.annotation.DubboReference;
@Slf4j @RestController @RequestMapping("/system") public class SystemController {
@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); }
}
|
1 2 3 4 5 6 7 8
| @SpringBootApplication public class ConsumerApplication {
public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class); }
}
|
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
| server: port: 9095
spring: application: name: dubbo-consumer-application
dubbo: application: name: ${spring.application.name} registry: address: zookeeper://127.0.0.1:2181 scan: base-packages: com.clay.dubbo.consumer consumer: check: false retries: 0 timeout: 1000
|
提示
若不希望在 YML 配置文件中指定 dubbo.scan.base-packages
参数,那么可以在主启动类上标注 @EnableDubbo(scanBasePackages = "xxx")
注解或者 @DubboComponentScan(basePackages = "xxx")
注解来替代。
测试代码
(1) 在 IDEA 开发工具内,分别启动 Provider 和 Consumer 模块。
(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-08
。
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 依赖),也就是在服务提供者的 YML 配置文件中,可以同时配置多种协议,但不同协议必须使用不同的端口,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| dubbo: protocols: dubbo-protocol: name: dubbo port: 20880 rest-protocol: name: rest port: 7777 server: tomcat hessian-protocol: name: hessian port: 8888 server: tomcat
|
当服务提供者暴露服务时,可以使用 @DubboService
注解的 protocol
属性指定多种协议,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @DubboService(protocol = "dubbo") public class OrderServiceImpl implements OrderService { }
@DubboService(protocol = "rest") public class RoleServiceImpl implements RoleService { }
@DubboService(protocol = "hessian") public class UserServiceImpl implements UserService { }
@DubboService(protocol = {"dubbo", "rest"}) public class SystemServiceImpl implements SystemService { }
|
当服务消费者调用远程服务时,只需要通过 @DubboReference
注解的 protocol
属性指定对应的协议即可,如下所示:
1 2 3 4 5 6 7 8 9 10 11
| @DubboReference(protocol = "dubbo") private OrderService orderService;
@DubboReference(protocol = "rest") private RoleService roleService;
@DubboReference(protocol = "dubbo,rest") private SystemService systemService;
|
配置案例
在上面案例代码的基础上,为了让 Provider 模块支持多种协议(比如 Dubbo 和 REST 协议),可以做以下更改。
- (1) 在 Provider 模块(服务提供者)的 YML 配置文件中,新增 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
| server: port: 9090
spring: application: name: dubbo-provider-application
dubbo: application: name: ${spring.application.name} registry: address: zookeeper://127.0.0.1:2181 protocols: dubbo-protocol: name: dubbo port: 20880 rest-protocol: name: rest port: 8080 server: tomcat scan: base-packages: com.clay.dubbo.provider
|
- (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 22
| @Slf4j @RestController @RequestMapping("/system") public class SystemController {
@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 {
@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 {
@DubboReference(protocol = "rest") private UserService userServiceREST;
@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
中做额外的其他配置。
配置内容说明
上述的 <dubbo:protocol/>
配置内容是 Spring XML 中的配置,如果是 SpringBoot 项目,只需要转化成 YML 配置格式就可以使用。
使用 Nacos 作为注册中心
早期的 Dubbo 都采用 Zookeeper 作为注册中心,现在基本上都使用 Nacos 作为注册中心,毕竟 Dubbo 和 Nacos 都是阿里巴巴的产品。若希望在 SpringBoot 整合 Dubbo 时,使用 Nacos 作为注册中心,那么在上述案例的基础上,按照以下步骤进行更改即可。
- (1) 由于不再需要使用 ZooKeeper 作为注册中心,因此需要移除
curator-recipes
依赖
- (2) 由于需要使用 Nacos 作为注册中心,因此需要引入
dubbo-registry-nacos
1 2 3 4 5
| <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-nacos</artifactId> <version>${dubbo.version}</version> </dependency>
|
- (3) 在 Provider 和 Consumer 模块中,指定 Nacos 注册中心的地址
1 2 3 4
| dubbo: registry: address: nacos://127.0.0.1:8848
|
参考资料