Controller 层代码就该这么写,简洁又优雅!(1)

简介: Controller 层代码就该这么写,简洁又优雅!

来源:https://juejin.cn/post/7123091045071454238

一个优秀的 Controller 层逻辑


说到 Controller,相信大家都不陌生,它可以很方便地对外提供数据接口。它的定位,我认为是「不可或缺的配角」。


说它不可或缺是因为无论是传统的三层架构还是现在的 COLA 架构,Controller 层依旧有一席之地,说明他的必要性。


说它是配角是因为 Controller 层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收和响应请求。


从现状看问题

Controller 主要的工作有以下几项:


  • 接收请求并解析参数
  • 调用 Service 执行具体的业务代码(可能包含参数校验)
  • 捕获业务逻辑异常做出反馈
  • 业务逻辑执行成功做出响应
//DTO
@Data
public class TestDTO {
    private Integer num;
    private String type;
}
//Service
@Service
public class TestService {
    public Double service(TestDTO testDTO) throws Exception {
        if (testDTO.getNum() <= 0) {
            throw new Exception("输入的数字需要大于0");
        }
        if (testDTO.getType().equals("square")) {
            return Math.pow(testDTO.getNum(), 2);
        }
        if (testDTO.getType().equals("factorial")) {
            double result = 1;
            int num = testDTO.getNum();
            while (num > 1) {
                result = result * num;
                num -= 1;
            }
            return result;
        }
        throw new Exception("未识别的算法");
    }
}
//Controller
@RestController
public class TestController {
    private TestService testService;
    @PostMapping("/test")
    public Double test(@RequestBody TestDTO testDTO) {
        try {
            Double result = this.testService.service(testDTO);
            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Autowired
    public DTOid setTestService(TestService testService) {
        this.testService = testService;
    }
}
  • 如果真的按照上面所列的工作项来开发 Controller 代码会有几个问题:


参数校验过多地耦合了业务代码,违背单一职责原则

可能在多个业务中都抛出同一个异常,导致代码重复

各种异常反馈和成功响应格式不统一,接口对接不友好

改造 Controller 层逻辑

统一返回结构

统一返回值类型无论项目前后端是否分离都是非常必要的,方便对接接口的开发人员更加清晰地知道这个接口的调用是否成功(不能仅仅简单地看返回值是否为 null 就判断成功与否,因为有些接口的设计就是如此)。


推荐一个开源免费的 Spring Boot 最全教程:


https://github.com/javastacks/spring-boot-best-practice


使用一个状态码、状态信息就能清楚地了解接口调用情况:

//定义返回数据结构
public interface IResult {
    Integer getCode();
    String getMessage();
}
//常用结果的枚举
public enum ResultEnum implements IResult {
    SUCCESS(2001, "接口调用成功"),
    VALIDATE_FAILED(2002, "参数校验失败"),
    COMMON_FAILED(2003, "接口调用失败"),
    FORBIDDEN(2004, "没有权限访问资源");
    private Integer code;
    private String message;
    //省略get、set方法和构造方法
}
//统一返回数据结构
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    public static <T> Result<T> success(T data) {
        return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
    }
    public static <T> Result<T> success(String message, T data) {
        return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);
    }
    public static Result<?> failed() {
        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
    }
    public static Result<?> failed(String message) {
        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
    }
    public static Result<?> failed(IResult errorResult) {
        return new Result<>(errorResult.getCode(), errorResult.getMessage(), null);
    }
    public static <T> Result<T> instance(Integer code, String message, T data) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        result.setData(data);
        return result;
    }
}

统一返回结构后,在 Controller 中就可以使用了,但是每一个 Controller 都写这么一段最终封装的逻辑,这些都是很重复的工作,所以还要继续想办法进一步处理统一返回结构。

统一包装处理

Spring 中提供了一个类 ResponseBodyAdvice ,能帮助我们实现上述需求:


public interface ResponseBodyAdvice<T> {
    boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
    @Nullable
    T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}

ResponseBodyAdvice 是对 Controller 返回的内容在 HttpMessageConverter 进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。


那这样就可以把统一包装的工作放到这个类里面:


supports: 判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要

beforeBodyWrite: 对 response 进行具体的处理


// 如果引入了swagger或knife4j的文档生成组件,这里需要仅扫描自己项目的包,否则文档无法正常生成
@RestControllerAdvice(basePackages = "com.example.demo")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 如果不需要进行封装的,可以添加一些校验手段,比如添加标记排除的注解
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 提供一定的灵活度,如果body已经被包装了,就不进行包装
        if (body instanceof Result) {
            return body;
        }
        return Result.success(body);
    }
}

经过这样改造,既能实现对 Controller 返回的数据进行统一包装,又不需要对原有代码进行大量的改动。

相关文章
|
前端开发 网络协议 Dubbo
超详细Netty入门,看这篇就够了!
本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。
91428 32
超详细Netty入门,看这篇就够了!
|
安全 JavaScript 前端开发
JDK1.8的重要的新特性与功能
Java Development Kit (JDK) 1.8,也称为Java 8,是Java平台的一个重大更新,于2014年3月发布。它引入了多项新特性、新的API和性能提升
1178 3
|
XML JavaScript 前端开发
【高效编程】编码规范与静态代码检查插件的使用(SonarList都用起来吧)
您好,我是码农飞哥,感谢您阅读本文!如果此文对您有所帮助,请毫不犹豫的一键三连吧,前面几篇文章介绍的都是开发类的插件,这篇文章将介绍一下编码规范和静态代码检查相关的插件。
1327 0
【高效编程】编码规范与静态代码检查插件的使用(SonarList都用起来吧)
|
开发框架 缓存 前端开发
基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理
基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理
|
12月前
|
编译器 Android开发
配置环境变量,使CMakeLists.txt可直接使用Android NDK工具链编译项目
配置环境变量,使CMakeLists.txt可直接使用Android NDK工具链编译项目
|
前端开发 Go
vscode10大常用插件
本文介绍了前端开发中常用的工具及VSCode必备插件。推荐使用VSCode作为入门工具,并介绍了WebStorm和HBuilder等其他选项。VSCode插件包括:Open-In-Browser、live-server、Beautify、Code Runner、Image Preview、Path Intellisense、Turbo Console Log、css-auto-prefix、Bracket Pair Colorizer 和 Auto Rename Tag,这些插件能够显著提升开发效率和代码质量。此外,还提供了录制Gif图的工具GifCam。
609 5
vscode10大常用插件
|
存储 关系型数据库 MySQL
TiDB与MySQL、PostgreSQL等数据库的比较分析
【2月更文挑战第25天】本文将对TiDB、MySQL和PostgreSQL等数据库进行详细的比较分析,探讨它们各自的优势和劣势。TiDB作为一款分布式关系型数据库,在扩展性、并发性能等方面表现突出;MySQL以其易用性和成熟性受到广泛应用;PostgreSQL则在数据完整性、扩展性等方面具有优势。通过对比这些数据库的特点和适用场景,帮助企业更好地选择适合自己业务需求的数据库系统。
2179 4
|
存储 开发工具 Android开发
Android系统 权限组管理和兼容性
Android系统 权限组管理和兼容性
346 0
|
NoSQL Java Redis
解释pom中的依赖dependency
解释pom中的依赖dependency
279 0
|
存储 机器学习/深度学习 Rust
Python性能优化指南--让你的Python代码快x3倍的秘诀
Python最为人诟病的就是其执行速度。如何让Python程序跑得更快一直是Python核心团队和社区努力的方向。本文将带大家深入探讨Python程序性能优化方法。
5493 1
Python性能优化指南--让你的Python代码快x3倍的秘诀