写点什么

深入浅出 Spring Boot 接口

作者:@下一站
  • 2022-11-30
    陕西
  • 本文字数:3679 字

    阅读完需:约 12 分钟

深入浅出Spring Boot接口

统一返回格式

定义一个业务 CODE 枚举类

public enum ResultCodeEnum {
SUCCESS(1, "成功"), //参数错误
ILLEGAL_PARAMETER(10001, "非法参数");
private Integer code; private String msg;
ResultCodeEnum(Integer code, String msg) { this.code = code; this.msg = msg; }
public Integer getCode() { return code; } public String getMsg() { return msg; }}
复制代码

定义统一返回类

public class R<T> implements Serializable {
private T data; private Integer code; private String msg;
public static <T> R<T> ok(T data) { R<T> r = new R<>(); r.setData(data); return r; }
public static <T> R<T> ok(ResultCodeEnum codeEnum, T data) { R<T> r = new R<>(); r.setData(data); r.setCode(codeEnum.getCode()); r.setMsg(codeEnum.getMsg()); return r; }
public static <T> R<T> fail(Integer code, String msg) { R<T> r = new R<>(); r.setCode(code); r.setMsg(msg); return r; }
public static <T> R<T> fail(ResultCodeEnum codeEnum) { R<T> r = new R<>(); r.setCode(codeEnum.getCode()); r.setMsg(codeEnum.getMsg()); return r; } //省略部分代码}
复制代码

这样每个接口都需要使用 R.ok()包裹返回值,下面我们通过一些配置来实现自动包裹。

定义一个封装注解

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documentedpublic @interface Result {}
复制代码


ResponseBodyAdvice 接口

Allows customizing the response after the execution of an @ResponseBody or a ResponseEntity controller method but before the body is written with an HttpMessageConverter.
复制代码

这个接口作用在 Controller 返回结果之后和 written with an HttpMessageConverter 之前。

true if beforeBodyWrite should be invoked; false otherwise
复制代码

重写 supports 方法 ,返回 true 调用 beforeBodyWrite 。

    @Override    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {        //方法所在class        Class<?> methodClass = returnType.getContainingClass();        //获取注解        Result r = methodClass.getAnnotation(Result.class);        //如果不存在不处理        if (ObjectUtils.isEmpty(r)) {            return false;        }        //如果已经是R返回false,如果不是R返回true        boolean assignableFrom = returnType.getParameterType().isAssignableFrom(R.class);        return !assignableFrom;    }
复制代码

当接口返回 String 类型报错如下

R cannot be cast to java.lang.String	at org.springframework.http.converter.StringHttpMessageConverter.addDefaultHeaders
复制代码


Invoked after an HttpMessageConverter is selected and just before its write method is invoked.
复制代码

beforeBodyWrite 调用在 HttpMessageConverter 选择后和 write 方法调用之前。

重写 beforeBodyWrite 方法

    @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType,                                  MediaType selectedContentType,                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,                                  ServerHttpRequest request,                                  ServerHttpResponse response) {
if (body instanceof String) { try { ObjectMapper objectMapper = new ObjectMapper(); //修改返回类型 response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE); R<Object> r = R.ok(body); return objectMapper.writeValueAsString(r); } catch (JsonProcessingException e) { e.printStackTrace(); } } return R.ok(body); }
复制代码

到这里我们就不需要使用 R.ok 来包裹返回类型了。

selectedContentType 和 selectedConverterType 是选定的 ContentType 和 ConverterType

打印一下这两个值

        logger.info("selectedContentType:" + selectedContentType);        logger.info("selectedConverterType:" + selectedConverterType);
复制代码

输出

//StringselectedConverterType:class org.springframework.http.converter.StringHttpMessageConverterselectedContentType:text/html//其他selectedContentType:application/jsonselectedConverterType:class org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
复制代码

那么谁调用的 beforeBodyWrite 这个方法呢 ?

beforeBodyWrite 调用过程

在方法内部打一个断点定位到 RequestResponseBodyAdviceChain,processBody 中 141 行调用 beforeBodyWrite

				body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,						contentType, converterType, request, response);
复制代码

beforeBodyWrite 中 116 行调用 processBody

		return processBody(body, returnType, contentType, converterType, request, response);
复制代码

AbstractMessageConverterMethodProcessor 中 268 行调用了 beforeBodyWrite

					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),							inputMessage, outputMessage);
复制代码

全局异常处理

迭代到这里,只需要在类上加入 @Result 注解就可以实现接口的封装处理,那么异常的情况呢?

在 ResultBodyAdvice 中加入这一段代码,通过 code 和 msg 来统一处理异常。

    @ExceptionHandler(Exception.class)    public R runtimeExceptionHandle(Throwable e) {        if (RuntimeException.class.equals(e.getClass())) {            return R.fail(ResultCodeEnum.SERVER_ERROR);        } else if (IllegalArgumentException.class.equals(e.getClass())) {            return R.fail(ResultCodeEnum.ILLEGAL_PARAMETER);        } else {            return R.fail(3000, "其他错误");        }    }
复制代码


参数校验

目的:消灭 if 条件判断。

约定:Get 请求不传 body,Post 请求通过 body 传参。RequestParam 绑定 url 参数,RequestBody 绑定 body 体中参数。

@RequestParam("id") Integer id@RequestBody String name
复制代码

@Validated

添加依赖

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-validation</artifactId>        </dependency>
复制代码


public class Hello6VO {    @Min(value = 10, message = "最小值10")    private Integer id;
@Min(value = 5, message = "最小值5") private Integer id2;}
复制代码


    @GetMapping("hello6")    public void hello6(@Validated Hello6VO vo) {        System.out.println(vo.getId());    }
@PostMapping("hello7") public void hello7(@RequestBody @Validated Hello6VO vo) { System.out.println(vo.getId()); }
复制代码


捕获失败异常

else if (e instanceof org.springframework.validation.BindException) {            BindException exception = (org.springframework.validation.BindException) e;            //优先处理字段错误            List<FieldError> fieldErrorsList = exception.getBindingResult().getFieldErrors();            List<String> fieldErrorMessageList = new ArrayList<>(fieldErrorsList.size());            //获取默认提示信息列表            for (FieldError fieldError : fieldErrorsList) {                //字段名                String field = fieldError.getField();                //提示信息                String message = fieldError.getDefaultMessage();                //拒绝的值                Object rejectedValue = fieldError.getRejectedValue();                String str = "字段:" + field + " 提示信息:" + message + " 拒绝的值:" + rejectedValue;                fieldErrorMessageList.add(str);            }            return R.fail(ResultCodeEnum.VALIDATE_FAILED.getCode(), fieldErrorMessageList.toString());        }
复制代码

输出

{  "data": null,  "code": 1002,  "msg": "[字段:id 提示信息:最小值10 拒绝的值:1, 字段:id2 提示信息:最小值5 拒绝的值:2]"}
复制代码


发布于: 刚刚阅读数: 3
用户头像

@下一站

关注

懒人 2020-11-22 加入

都是黄泉预约客,何必难为每一天,执念太强,无法豁然。

评论

发布
暂无评论
深入浅出Spring Boot接口_程序设计_@下一站_InfoQ写作社区