⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 geekhalo 「geekhalo」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 概览

日常开发中,最繁琐的便是编写 Controller。很多公司都制定了规范:Controller 不能存在任何的业务逻辑,主要完成参数解析和结果转换。

不过查看项目源码,你会发现 Controller 中存在了大量不该存在的逻辑,对此,你有什么好的方法?依赖 Code Review?从我角度,我觉得 Controller 根本就不需要存在。

1.1. 背景

之前对 CommandService 和 QueryService 进行封装,通过定义接口的方式快速搭建应用服务,大大提升了开发效率和代码质量,在有了应用服务之后,便是在其基础之上编写 Controller,把能力暴露出去。这是一个非常繁琐且没有技术含量的重复工作。而对于枯燥的重复工作,我的策略一直都是“交由框架完成”。

1.2. 目标

简单的说,我们的目标便是不写Controller,但还要保留 Controller 的效果。

  1. 不需要编写 Controller 代码,将 CommandService 和 QueryService 直接暴露为 Web 接口;
  2. 完成与 Swagger 框架的集成,动态生成 api doc,方便前端接入;

2. 快速入门

2.1. 环境准备

首先,在 pom 中增加 lego-starter,具体如下:

<dependency>  
<groupId>com.geekhalo.lego</groupId>
<artifactId>lego-starter</artifactId>
<version>0.1.11-rest-SNAPSHOT</version>
</dependency>

其次,增加 swagger 相关依赖,具体如下:

<dependency>  
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-data-rest</artifactId>
<version>3.0.0</version>
</dependency>

最后,新建 SpringFoxConfiguration,启用 Swagger 具体如下:

@Configuration  
@EnableSwagger2
public class SpringFoxConfiguration {
}

2.2. 初识统一控制器

打开浏览器,输入 http://127.0.0.1:8080/swagger-ui/ 打开 swagger 界面,会发现新增两个 Controller:

command-dispatcher-controller 是对 CommanderService 的Web暴露,主要用于执行写入和更新等业务操作,两个接入点包括:

  1. RequestBody 接入。以 json 作为入参,适用于复杂的参数结构;
  2. RequestParam 接入。以 param 作为参数,适用于简单场景;

query-dispatcher-controller 是对 QueryService 的Web暴露,主要用于执行业务的查询操作,同样支持 RequestBody 和 RequestParam 两种接入方式。与 command-dispatcher-controller 唯一区别是提供了对 Get 方法支持。

但在展开方法后,有点让人绝望。

serviceName 和 method 两个参数从哪获取?nativeRequest 和 nativeResponse 又是什么东西?这两个接口怎么使用?

看不明白也正常,因为我们不会直接使用这两个处理器。

2.3. Command 控制器

2.3.1. 启用 Command 控制器

OrderCommandService 接口上增加 @AutoRegisterWebController 注解,将其对外暴露为 Web 端口。

@CommandServiceDefinition(  
domainClass = Order.class,
idClass = Long.class,
repositoryClass = OrderRepository.class)
@AutoRegisterWebController(name = "order")
public interface OrderCommandService{
void cancel(Long orderId);

Long create(CreateOrderCommand command);

void paySuccess(PaySuccessCommand command);
}

2.3.2. 使用 Order Command 控制器

输入 http://127.0.0.1:8080/swagger-ui/ 访问 swagger 界面,发现新增一组 Controller。

OrderCommandService 服务中的所有方法全部出现在 Controller 中。

首先,展开 CommandByBody 中的 create 方法,可见:

然后,展开 CommandByParam 中的 create 方法,具体如下:

整体结构和手写 Controller 基本一致,所暴露的功能也全部相同。

2.4. Query 控制器

2.4.1. 启用 Query 控制器

OrderQueryService 接口上增加 @AutoRegisterWebController注解,将其对外暴露为 Web 端口。

@QueryServiceDefinition(domainClass = Order.class,  
repositoryClass = OrderQueryRepository.class)
@Validated
@AutoRegisterWebController(name = "order")
public interface OrderQueryService {
OrderDetail getById(@Valid @NotNull(message = "订单号不能为null") Long id);

Page<OrderDetail> pageByUserId(@Valid @NotNull(message = "查询参数不能为 null") PageByUserId query);

List<OrderDetail> getByUserId(@Valid @NotNull(message = "查询参数不能为 null") GetByUserId getByUserId);

Long countByUser(@Valid @NotNull(message = "查询参数不能为 null") CountByUserId countByUserId);

List<OrderDetail> getPaidByUserId(Long id);
}

2.3.2. 使用 Order Query 控制器

输入 http://127.0.0.1:8080/swagger-ui/ 访问 swagger 界面,发现新增一组 Controller。

OrderQueryService 服务中的所有方法全部出现在 Controller。

首先,展开 QueryByBody 中的 pageByUserId 方法,可见:

然后,展开 QueryByParam 中的 pageByUserId 方法,具体如下:

入参与返回值结构非常清晰,整体结构和手写 Controller 基本一致,所暴露的功能也全部相同。

3. 设计&扩展

整个设计分为两部分:

  1. 提供统一的Controller,作为所有请求的转发器;
  2. 提供插件与 Swagger 进行集成,提供完整的 api doc;

3.1. 统一 Controller

提供 QueryDispatcherController 作为所有查询请求的入口,核心架构如下:

初始化流程如下:

  1. Spring 对所有的 QueryService 进行实例化;
  2. 完成实例化的 QueryService Bean 自动注册到 QueryServicesRegistry
  3. QueryMethodRegistryQueryServicesRegistry 中获取服务实例,对 QueryMethod 进行解析,并完成注册;

执行流程如下:

  1. 客户端向服务器发起请求;
  2. 服务器将请求 路由到 QueryDispatcherController 的相关方法;
  3. QueryDispatcherController 根据 serviceName 和 methodName 从 QueryMethodRegistry 中获取 QueryMethod,执行业务方法,最后返回最终结果;

3.2. 与 Swagger 集成

提供 QueryServicesProvider 与 Swagger 进行集成,提供完整的 api doc,整体设计如下:

QueryServiceProviderQueryDispatcherController 一致,同样依赖于 QueryMethodRegistry 中的 QueryMethod 信息。

核心流程如下:

  1. QueryServicesProviderQueryMethodRegistry 中获取 QueryMethod 信息;
  2. 解析 QueryMethod 信息生成 RequestHander,并注册到 Swagger ;
  3. 用户请求 Swagger 时,将 RequestHander 转化为 api doc进行返回;

4. 项目信息

  • 项目仓库地址:https://gitee.com/litao851025/lego
  • 项目文档地址:https://gitee.com/litao851025/lego/wikis/support/web
文章目录
  1. 1. 1. 概览
    1. 1.1. 1.1. 背景
    2. 1.2. 1.2. 目标
  2. 2. 2. 快速入门
    1. 2.1. 2.1. 环境准备
    2. 2.2. 2.2. 初识统一控制器
    3. 2.3. 2.3. Command 控制器
      1. 2.3.0.1. 2.3.1. 启用 Command 控制器
      2. 2.3.0.2. 2.3.2. 使用 Order Command 控制器
  3. 2.4. 2.4. Query 控制器
    1. 2.4.0.1. 2.4.1. 启用 Query 控制器
    2. 2.4.0.2. 2.3.2. 使用 Order Query 控制器
  • 3. 3. 设计&扩展
    1. 3.1. 3.1. 统一 Controller
    2. 3.2. 3.2. 与 Swagger 集成
  • 4. 4. 项目信息