SpringBoot中controller深层详细讲解

 更新时间:2023年02月02日 16:39:03   作者:ldcaws  
这篇文章主要介绍了SpringBoot在Controller层接收参数的常用方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

在基于spring框架的项目开发中,必然会遇到controller层,它可以很方便的对外提供数据接口服务,也是非常关键的出口,所以非常有必要进行规范统一,使其既简洁又优雅。

controller层的职责为负责接收和响应请求,一般不负责具体的逻辑业务的实现。controller主要工作如下:

  • 接收请求并解析参数;
  • 调用service层执行具体的业务逻辑(可能包含参数校验);
  • 捕获业务异常做出反馈;
  • 业务逻辑执行成功做出响应;

目前controller层代码会存在的问题:

  • 参数校验过多地耦合了业务代码,违背了单一职责原则;
  • 可能在多个业务逻辑中抛出同一个异常,导致代码重复;
  • 各种异常反馈和成功响应格式不统一,接口对接不友好;

优雅写法一:统一返回结构

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

统一返回结构,通过状态码就能清楚的知道接口的调用情况:

@Data
public class ResponseData<T> {
    private Boolean status = true;
    private int code = 200;
    private String message;
    private T data;
    public static ResponseData ok(Object data) {
        return new ResponseData(data);
    }
    public static ResponseData ok(Object data,String message) {
        return new ResponseData(data,message);
    }
    public static ResponseData fail(String message,int code) {
        ResponseData responseData= new ResponseData();
        responseData.setCode(code);
        responseData.setMessage(message);
        responseData.setStatus(false);
        responseData.setData(null);
        return responseData;
    }
    public ResponseData() {
        super();
    }
    public ResponseData(T data) {
        super();
        this.data = data;
    }
    public ResponseData(T data,String message) {
        super();
        this.data = data;
        this.message=message;
    }
}
@AllArgsConstructor
@Data
public enum ResponseCode {
    SYS_FAIL(1, "操作失败"),
    SYS_SUCESS(200, "操作成功"),
    SYSTEM_ERROR_CODE_403(403, "权限不足"),
    SYSTEM_ERROR_CODE_404(404, "未找到请求资源"),
	;
	private int code;
    private String msg;
}

统一返回结构后,就可以在controller中使用了,但是每个controller都这么写,都是很重复的工作,所以还可以继续想办法处理统一返回结构。

优雅写法二:统一包装处理

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

ResponseBodyAdvice 是对 Controller 返回的内容在 HttpMessageConverter 进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。这样就可以把统一包装处理的工作放到这个类里面,其中supports判断是否要交给beforeBodyWrite 方法执行,true为需要,false为不需要,beforeBodyWrite 是对response的具体处理。

@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返回的数据进行统一,又不需要对原有代码进行大量的改动了。

优雅写法三:参数校验

Java API 的规范 JSR303 定义了校验的标准 validation-api ,其中一个比较出名的实现是 hibernate validation。

@PathVariable 和 @RequestParam 参数校验:get请求的参数接收一般依赖这两个注解,但是处于 url 有长度限制和代码的可维护性,超过 5 个参数尽量用实体来传参;

对 @PathVariable 和 @RequestParam 参数进行校验需要在入参处声明约束的注解,如果校验失败,会抛出 MethodArgumentNotValidException 异常。

@RestController
@RequestMapping("/test")
public class TestController {
    private TestService testService;
	@Autowired
    public void setTestService(TestService prettyTestService) {
        this.testService = prettyTestService;
    }
    @GetMapping("/{num}")
    public Integer num(@PathVariable("num") @Min(1) @Max(20) Integer num) {
        return num * num;
    }
    @GetMapping("/email")
    public String email(@RequestParam @NotBlank @Email String email) {
        return email;
    }
}

@RequestBody 参数校验:post和put 请求的参数推荐使用 @RequestBody 请求体参数;

对 @RequestBody 参数进行校验需要在 DTO 对象中加入校验条件后,再搭配 @Validated 即可完成自动校验。如果校验失败,会抛出 ConstraintViolationException 异常。

@Data
public class TestDTO {
    @NotBlank
    private String userName;
    @NotBlank
    @Length(min = 6, max = 20)
    private String password;
    @NotNull
    @Email
    private String email;
}
@RestController
@RequestMapping("/test")
public class TestController {
    private TestService testService;
	@Autowired
    public void setTestService(TestService testService) {
        this.testService = testService;
    }
    @PostMapping("/testValidation")
    public void testValidation(@RequestBody @Validated TestDTO testDTO) {
        this.testService.save(testDTO);
    }
}

自定义校验规则:有些时候 JSR303 标准中提供的校验规则不满足复杂的业务需求,也可以自定义校验规则;

优雅写法四:自定义异常与统一拦截异常

原来抛出的异常会有如下问题:

  • 抛出的异常不够具体,只是简单地把错误信息放到了 Exception 中;
  • 抛出异常后,Controller 不能具体地根据异常做出反馈;
  • 虽然做了参数自动校验,但是异常返回结构和正常返回结构不一致;

自定义异常是为了后面统一拦截异常时,对业务中的异常有更加细颗粒度的区分,拦截时针对不同的异常作出不同的响应。

统一拦截异常的是为了可以与前面定义下来的统一包装返回结构能对应上,还有就是希望无论系统发生什么异常,Http 的状态码都要是 200 ,尽可能由业务来区分系统的异常。

//自定义异常
public class ForbiddenException extends RuntimeException {
    public ForbiddenException(String message) {
        super(message);
    }
}
//自定义异常
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
}
//统一拦截异常
@RestControllerAdvice(basePackages = "com.example.demo")
public class ExceptionAdvice {
    /**
     * 捕获 {@code BusinessException} 异常
     */
    @ExceptionHandler({BusinessException.class})
    public Result<?> handleBusinessException(BusinessException ex) {
        return Result.failed(ex.getMessage());
    }
    /**
     * 捕获 {@code ForbiddenException} 异常
     */
    @ExceptionHandler({ForbiddenException.class})
    public Result<?> handleForbiddenException(ForbiddenException ex) {
        return Result.failed(ResultEnum.FORBIDDEN);
    }
    /**
     * {@code @RequestBody} 参数校验不通过时抛出的异常处理
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder("校验失败:");
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
        }
        String msg = sb.toString();
        if (StringUtils.hasText(msg)) {
            return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), msg);
        }
        return Result.failed(ResultEnum.VALIDATE_FAILED);
    }
    /**
     * {@code @PathVariable} 和 {@code @RequestParam} 参数校验不通过时抛出的异常处理
     */
    @ExceptionHandler({ConstraintViolationException.class})
    public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {
        if (StringUtils.hasText(ex.getMessage())) {
            return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), ex.getMessage());
        }
        return Result.failed(ResultEnum.VALIDATE_FAILED);
    }
    /**
     * 顶级异常捕获并统一处理,当其他异常无法处理时候选择使用
     */
    @ExceptionHandler({Exception.class})
    public Result<?> handle(Exception ex) {
        return Result.failed(ex.getMessage());
    }
}

通过上述写法,可以发现 Controller 的代码变得非常简洁优雅,可以清楚知道每个参数、每个DTO的校验规则,可以明确返回的结构,包括异常情况。

到此这篇关于SpringBoot中controller深层详细讲解的文章就介绍到这了,更多相关SpringBoot controller内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot优雅地实现全局异常处理的方法详解

    SpringBoot优雅地实现全局异常处理的方法详解

    这篇文章主要为大家详细介绍了SpringBoot如何优雅地实现全局异常处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-08-08
  • java外部类与内部类简介

    java外部类与内部类简介

    这篇文章简单介绍了java外部类与内部类,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • Java基础学习之集合底层原理

    Java基础学习之集合底层原理

    今天带大家回顾Java基础的相关知识,文中对集合底层原理作了非常详细的图文介绍,对Java初学者有非常好的帮助,需要的朋友可以参考下
    2021-05-05
  • 微信开发--自定义菜单查询返码乱码的解决方法

    微信开发--自定义菜单查询返码乱码的解决方法

    本篇文章主要介绍了微信开发--自定义菜单查询返码乱码的解决方法,具有很好的参考价值。下面跟着小编一起来看下吧
    2017-03-03
  • SpringBoot静态资源与首页配置实现原理深入分析

    SpringBoot静态资源与首页配置实现原理深入分析

    最近在做SpringBoot项目的时候遇到了“白页”问题,通过查资料对SpringBoot访问静态资源做了总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-10-10
  • Java数据类型的全面剖析

    Java数据类型的全面剖析

    这篇文章主要介绍了Java基本数据类型,结合实例形式详细分析了java基本数据类型、数据类型范围、易错题等相关原理与操作技巧,需要的朋友可以参考下
    2021-10-10
  • IntelliJ IDEA本地代码提交到github网站不显示与本地不同步问题的解决办法

    IntelliJ IDEA本地代码提交到github网站不显示与本地不同步问题的解决办法

    今天小编就为大家分享一篇关于IntelliJ IDEA本地代码提交到github网站不显示与本地不同步问题的解决办法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10
  • 完整的logback配置示例ELK整合包含生成json日志

    完整的logback配置示例ELK整合包含生成json日志

    这篇文章主要为大家介绍了完整的logback配置示例ELK整合包含生成json日志,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • Java设计模式之单态模式(Singleton模式)介绍

    Java设计模式之单态模式(Singleton模式)介绍

    这篇文章主要介绍了Java设计模式之单态模式(Singleton模式)介绍,本文讲解了如何使用单例模式、使用单例模式注意事项等内容,需要的朋友可以参考下
    2015-03-03
  • Java执行JS脚本工具

    Java执行JS脚本工具

    今天小编就为大家分享一篇关于Java执行JS脚本工具,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12

最新评论