SpringBoot: Controller层的优雅实现

news/2024/10/31 1:29:21/

目录

  • 1. 实现目标
  • 2. 统一状态码
  • 3. 统一响应体
  • 4. 统一异常
  • 5. 统一入参校验
  • 6. 统一返回结果
  • 7. 统一异常处理
  • 8. 验证

1. 实现目标

  1. 优雅校验接口入参
  2. 响应体格式统一处理
  3. 异常统一处理

2. 统一状态码

  • 创建状态码接口,所有状态码必须实现这个接口,统一标准
package com.example.mavendemo.enums;public interface StatusCode {int getCode();String getMsg();
}
  • 定义状态码
package com.example.mavendemo.enums;import lombok.AllArgsConstructor;
import lombok.Getter;@Getter
@AllArgsConstructor
public enum ResponseCode implements StatusCode {SUCCESS(0, "请求成功"),FAILED(1, "请求失败"),VALIDATE_ERROR(2, "参数校验失败"),RESPONSE_ERROR(3, "返回失败"),APP_ERROR(40000,"服务内部异常"),BUSINESS_ERROR(40001,"业务异常");private int code;private String msg;
}

3. 统一响应体

  • 响应结果统一使用ResultResponse进行包装,返回给前端
package com.example.mavendemo.dto;import com.example.mavendemo.enums.ResponseCode;
import com.example.mavendemo.enums.StatusCode;import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class ResultResponse {private int code;private String msg;private Object data;public static ResultResponse ok() {return new ResultResponse(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMsg(), null);}public static ResultResponse ok(Object data) {return new ResultResponse(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMsg(), data);}public static ResultResponse error(StatusCode statusCode) {return new ResultResponse(statusCode.getCode(), statusCode.getMsg(), null);}public static ResultResponse error(StatusCode statusCode, Object data) {return new ResultResponse(statusCode.getCode(), statusCode.getMsg(), data);}}

4. 统一异常

  • 自定义一个异常类,对于特定错误,传入响应的状态码
package com.example.mavendemo.exception;import com.example.mavendemo.enums.ResponseCode;
import com.example.mavendemo.enums.StatusCode;import lombok.Getter;@Getter
public class ApiException extends RuntimeException {private StatusCode statusCode;public ApiException(String message) {super(message);this.statusCode = ResponseCode.APP_ERROR;}public ApiException(StatusCode statusCode, String message) {super(message);this.statusCode = statusCode;}}

5. 统一入参校验

  • 需要引入spring-boot-starter-validation
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.6</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>maven-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>maven-demo</name><description>maven-demo</description><properties><java.version>8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--参数校验--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions><execution><goals><goal>repackage</goal></goals></execution></executions><configuration><includeSystemScope>true</includeSystemScope><mainClass>com.example.mavendemo.MavenDemoApplication</mainClass></configuration></plugin></plugins></build>
</project>
  • 定义实体类,对于需要校验的字段添加注解,例如@NotBlank,@Max
package com.example.mavendemo.model;import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;import lombok.Data;@Data
public class User {@NotBlank(message = "姓名不能为空")private String name;@Max(value = 18, message = "年龄不能大于18")private Integer age;private String address;}
  • 在传参时,使用@Valid,例如@RequestBody @Valid User user

6. 统一返回结果

  • 如果不想统一响应体,可以定义一个注解,标记具体的接口进行排除
package com.example.mavendemo.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotResponseAdvice {
}
  • 实现ResponseBodyAdvice接口,重写supports和beforeBodyWrite方法,完成对response body的增强
package com.example.mavendemo.advice;import com.example.mavendemo.annotation.NotResponseAdvice;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import com.example.mavendemo.dto.ResultResponse;/*** basePackages指定作用范围**/
@RestControllerAdvice(basePackages = {"com.example.mavendemo.controller"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {/*** 校验是否执行beforeBodyWrite方法*/@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 如果返回值已经是ResultResponse,或有NotResponseAdvice注解则不进行包装return !(returnType.getParameterType().isAssignableFrom(ResultResponse.class)|| returnType.hasMethodAnnotation(NotResponseAdvice.class));}/*** 对返回值进行处理*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {return ResultResponse.ok(body);}
}
  • 这里需要注意String类型的返回值,使用上面的代码会报错java.lang.ClassCastException: cannot be cast to java.lang.String
  • 需要介绍一下HttpMessageConverter接口,负责将请求信息转换为一个java对象,将java对象输出为响应信息;所以在触发@ResponseBody注解时,Spring都会遍历这个HttpMessageConverter列表,然后选择第一个符合返回值类型的转换器然后进行转换。
  • HttpMessageConverter如下

在这里插入图片描述

  • String类型会优先使用StringHttpMessageConverter转换器。实际上String类型既可以使用MappingJackson2HttpMessageConverter,也可以使用StringHttpMessageConverter来解析。所以可以将HttpMessageConverter列表反转,调换MappingJackson2HttpMessageConverter和StringHttpMessageConverter的顺序来解决。
  • 实现WebMvcConfigurer,重写configureMessageConverters方法
package com.example.mavendemo.advice;import java.util.Collections;
import java.util.List;import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebResponseConfig implements WebMvcConfigurer {/*** 反转HttpMessageConverter集合,将MappingJackson2HttpMessageConverter移到StringHttpMessageConverter前面*/@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {Collections.reverse(converters);}}

7. 统一异常处理

  • 使用@RestControllerAdvice注解对Controller进行增强,配合@ExceptionHandler使用,统一处理异常
package com.example.mavendemo.advice;import com.example.mavendemo.exception.ApiException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import com.example.mavendemo.dto.ResultResponse;
import com.example.mavendemo.enums.ResponseCode;@RestControllerAdvice
public class ControllerExceptionAdvice {/*** 参数错误异常*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResultResponse methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {return ResultResponse.error(ResponseCode.VALIDATE_ERROR,e.getBindingResult().getAllErrors().get(0).getDefaultMessage());}/*** 自定义异常*/@ExceptionHandler(ApiException.class)public ResultResponse apiExceptionHandler(ApiException e) {return ResultResponse.error(e.getStatusCode(), e.getMessage());}/*** 异常兜底*/@ExceptionHandler(Exception.class)public ResultResponse exceptionHandler(Exception e) {return ResultResponse.error(ResponseCode.FAILED, e.getMessage());}}

8. 验证

  • 测试类
package com.example.mavendemo.controller;import javax.validation.Valid;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import com.example.mavendemo.annotation.NotResponseAdvice;
import com.example.mavendemo.enums.ResponseCode;
import com.example.mavendemo.exception.ApiException;
import com.example.mavendemo.model.User;@RestController
public class HelloController {/*** 测试入参校验和统一响应体*/@PostMapping("/hello")public User hello(@RequestBody @Valid User user) {return user;}/*** 测试String类型返回值的响应体*/@GetMapping("/name")public String getName(String name) {return name;}/*** 测试不统一响应体和统一异常处理情况*/@NotResponseAdvice@GetMapping("/findUser")public User getAddress(String name, Integer age) {if (age > 18) {throw new ApiException(ResponseCode.BUSINESS_ERROR, "查无此人");}User user = new User();user.setName(name);user.setAge(age);user.setAddress("北京市王府井大街1号");return user;}}

在这里插入图片描述

  • 验证参数校验

在这里插入图片描述

  • 验证统一响应体
  1. 返回值是java对象

在这里插入图片描述

  1. 返回值是String类型

在这里插入图片描述

  • 验证不需要统一的响应体

在这里插入图片描述

  • 验证统一异常处理

在这里插入图片描述


http://www.ppmy.cn/news/1086.html

相关文章

【YOLO系列改进NO.45】首发最新特征融合技术RepGFPN(DAMO-YOLO)

文章目录前言一、解决问题二、基本原理三、​添加方法四、总结前言 作为当前先进的深度学习目标检测算法YOLOv7&#xff0c;已经集合了大量的trick&#xff0c;但是还是有提高和改进的空间&#xff0c;针对具体应用场景下的检测难点&#xff0c;可以不同的改进方法。此后的系列…

【Python百日进阶-数据分析】Day121 - Plotly Figure参数: 散点图(三)marker 标记

文章目录marker 标记marker 标记 代码&#xff1a; fig.update_traces(markerdict(…), selectordict(type‘scatter’)) 类型&#xff1a;包含下面列出的一个或多个键的字典。 autocolorscale 自动色标 代码&#xff1a; fig.update_traces(marker_autocolorscale, selectord…

Nexus私服(三)

(一) maven中snapshots和releases snapshots快照在maven指的是开发阶段的版本,会频繁的更新,常指在开发测试阶段,一般在pom的版本中声明1.0-SNAPSHOTsnapshots快照的引入是为了解决开发阶段依赖的问题。当我们的版本号中不显示声明SNAPSHOT,maven会默认你的是releases稳定版本。…

Yolo算法检测之Anchor Boxes原理详解

刚开始yolo系列的目标检测算法&#xff0c;在一个网格中只能检测一个对象&#xff0c;但是我们在实验中发现&#xff0c;一个网格中很多时候存在不仅一个目标&#xff0c;可能存在多个目标&#xff0c;类似如下图所示&#xff0c;下面中间的网格中就存在人和车辆两个目标的中心…

十一、【React-Router6】Hooks 汇总

文章目录 1.useRoutes() 2. useNavigate() 3. useParams() 4. useSearchParams() 5. useLocation() 6. useMatch() 7. useInRouterContext() 8. useNavigationType() 9. useOutlet() 10. useResolvedPath() 1、useRoutes() 根据路由表&#xff0c;动态创建 <Routes> 和…

switch case与while语句练习

switch case 选择 假设用1&#xff0c;2。。。。7分别表示星期一。。。。星期天&#xff0c;现输入一个数字&#xff0c;输出对应的星期几。比如&#xff1a;输入3&#xff0c;则输出“星期三” #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() {int d…

基于HFSS的线阵综合分析

摘要&#xff1a; 常规的阵列天线方向图综合是基于阵因子分析法&#xff0c;且不考虑单元之间电磁耦合的一种快速分析手段。本次推文则简单阐述一个基于HFSS的线阵综合实例。 HFSS中的直线阵 均匀直线阵的基础知识已在前面的推文中进行了多次阐述举例&#xff0c;这里就不赘…

第五周 丹巴晨景——跟随光线,渲染照片氛围

目录5.1 尽可能的运用光线&#xff0c;晨景与星空5.2 不同光线的造型特点5.3 色彩的魅力课件光线作业5.1 尽可能的运用光线&#xff0c;晨景与星空 甘孜州 折多山垭口 新都桥镇 塔公草原 藏寨 中国最美丽的乡村——丹巴(四川省) 光影比较明显的地方 暗&#xff0c;则延长曝光时…