• 深入浅出Spring Boot接口


    如何优雅的写 Controller 层代码?
    瞧瞧,人家这后端API接口写得,那叫一个巴适~,再看看我的,像坨屎!

    统一返回格式

    定义一个业务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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    定义统一返回类

    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;
        }
    	//省略部分代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

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

    定义一个封装注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    public @interface Result {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    R cannot be cast to java.lang.String
    	at org.springframework.http.converter.StringHttpMessageConverter.addDefaultHeaders
    
    • 1
    • 2

    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    selectedContentType和selectedConverterType是选定的ContentType和ConverterType

    打印一下这两个值

            logger.info("selectedContentType:" + selectedContentType);
            logger.info("selectedConverterType:" + selectedConverterType);
    
    • 1
    • 2

    输出

    //String
    selectedConverterType:class org.springframework.http.converter.StringHttpMessageConverter
    selectedContentType:text/html
    //其他
    selectedContentType:application/json
    selectedConverterType:class org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    beforeBodyWrite 调用过程

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

    				body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
    						contentType, converterType, request, response);
    
    • 1
    • 2

    beforeBodyWrite中116行调用processBody

    		return processBody(body, returnType, contentType, converterType, request, response);
    
    • 1

    AbstractMessageConverterMethodProcessor中268行调用了beforeBodyWrite

    					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
    							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
    							inputMessage, outputMessage);
    
    • 1
    • 2
    • 3

    全局异常处理

    迭代到这里,只需要在类上加入@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, "其他错误");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    参数校验

    SpringBoot 还在用 if 校验参数?那你真的太low了,老司机都是这么玩的!

    目的:消灭 if 条件判断。

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

    @RequestParam("id") Integer id
    @RequestBody String name
    
    • 1
    • 2

    @Validated

    添加依赖

            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-validationartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    public class Hello6VO {
        @Min(value = 10, message = "最小值10")
        private Integer id;
    
        @Min(value = 5, message = "最小值5")
        private Integer id2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
        @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());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    捕获失败异常

    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());
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出

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

    分组校验

    通用思想

    后端思想篇:设计好接口的36个锦囊!

    性能优化

    7种方式,教你提升 SpringBoot 项目的吞吐量
    Spring Boot 程序优化的 14 个小妙招!
    实战总结!18种接口优化方案的总结

  • 相关阅读:
    springboot幼儿园书刊信息管理系统毕业设计源码141858
    springboot:集成Kaptcha实现图片验证码
    阿里巴巴面试题- - -JVM篇(十七)
    攻防世界-very-easy-sql
    【考研复试】计算机相关专业面试英语自我介绍范文(一)
    RuntimeError: Error compiling objects for extension手把手带你解决(超详细)
    React18学习
    Multi-Paxos算法
    基于生产数据测试设计、测试回归
    用上了Jenkins,个人部署项目真方便!
  • 原文地址:https://blog.csdn.net/qq_37151886/article/details/128100804