一个注解干掉所有Controller

大家好,我是老赵

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:

86b49e82fb88691a2aa680ecfddd9516.png
image

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

  1. RequestBody 接入。以 json 作为入参,适用于复杂的参数结构;

  2. RequestParam 接入。以 param 作为参数,适用于简单场景;

d9371d5bfdaa082de81042e66a9df65f.png
image

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

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

71931835528ea59a662f0b764fb2c710.png
image

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。

1aa9ea8b680c3641141408021427dbb8.png
image

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

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

0e4828ceb9c4d6d5ad74d3ae0aa3c72e.png
image

63378e5cba735f087452e59c24b48a42.png
image

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

0c0647844f60c6e176453f08fe6ae3ce.png
image

a685846db9f3cf3d4d545efff10fe572.png
image

整体结构和手写 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。

d8e26798b4bfc6fafd5bee6db6a553ad.png
image

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

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

672fd40a9a1820182ad621f4b40121f3.png
image

36a08ec4b8d0e9ecbaaa28472c02a6ce.png
image

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

a64fe3b999bc1758b57f26cfd0de1e2a.png
image

c95d33a39ad4394283a004be4cf1a53a.png
image

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

3. 设计&扩展

整个设计分为两部分:

  1. 提供统一的Controller,作为所有请求的转发器;

  2. 提供插件与 Swagger 进行集成,提供完整的 api doc;

3.1. 统一 Controller

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

24fe16d4d21bf5f7acb50e8d00e992be.png
image

初始化流程如下:

  1. Spring 对所有的 QueryService 进行实例化;

  2. 完成实例化的 QueryService Bean 自动注册到 QueryServicesRegistry;

  3. QueryMethodRegistry 从 QueryServicesRegistry 中获取服务实例,对 QueryMethod 进行解析,并完成注册;

执行流程如下:

  1. 客户端向服务器发起请求;

  2. 服务器将请求 路由到 QueryDispatcherController 的相关方法;

  3. QueryDispatcherController 根据 serviceName 和 methodName 从 QueryMethodRegistry 中获取 QueryMethod,执行业务方法,最后返回最终结果;

3.2. 与 Swagger 集成

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

c0bc4751c584e8f20103f695666808d6.png
image

QueryServiceProvider 与 QueryDispatcherController 一致,同样依赖于 QueryMethodRegistry 中的 QueryMethod 信息。

核心流程如下:

  1. QueryServicesProvider 从 QueryMethodRegistry 中获取 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.CTO给公司搭建了一套网关服务,包含动态路由、鉴权等功能,看完秒会(含流程图)
2.再见 xxl-job!更强大的新一代分布式任务调度框架来了!
3.SpringBoot+ElasticSearch 实现模糊查询,批量CRUD,排序,分页,高亮!

4.网站都变成灰色了,它是怎么实现的?
5.再见了 Shiro!
6.IntelliJ IDEA终于支持对Redis 的可视化窗口操作了,真香!
7.学计算机的女生后来都怎么样了?
8.从阿里跳槽来的工程师,分享了三套干掉 “重复代码”方式,真的太绝了!
9.吹爆,Nginx 可视化!配置监控一条龙!

b3c2bdd236359cf91bdf751ed7d21482.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值