深入浅出Spring Boot接口

举报
torchstar 发表于 2022/11/30 22:43:39 2022/11/30
1.1k+ 0 0
【摘要】 深入浅出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)
@Documented
public @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);

输出

//String
selectedConverterType:class org.springframework.http.converter.StringHttpMessageConverter
selectedContentType:text/html
//其他
selectedContentType:application/json
selectedConverterType: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]"
}
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。