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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot在Controller层接收参数的n种姿势(超详细)

    目录 前言 接收get请求 接收post请求 前言 在工作中,比如要实现一个功能,前端传什么参数,后端的controller层中怎么接收参数 ,封装成了什么实体对象,有些参数是在URL上使用,有些参数是在body上使用,service层中做了什么逻辑,调用dao层的sql怎么写的,前端传的参数,后端用controller中的一个方法来接收请求这些都是我们需要思考的逻辑! 下面重点讲下SpringBoot接收请求的n种姿势,建议收藏哦- 接收get请求 (1)get请求无参数 @RestContr

  • SpringBoot开发详解之Controller接收参数及参数校验

    目录 Controller 中注解使用 传输参数的几种Method 获取参数的几种常用注解 使用对象直接获取参数 使用@Valid对参数进行校验 总结 Controller 中注解使用 接受参数的几种传输方式以及几种注解: 在上一篇中,我们使用了JDBC链接数据库,完成了简单的后端开发.但正如我在上文中抛出的问题,我们能不能更好的优化我们在Controller中接受参数的方式呢?这一篇中我们就来聊一聊怎么更有效的接收Json参数. 传输参数的几种Method 在定义一个Rest接口时,我们通常会

  • SpringBoot如何配置Controller实现Web请求处理

    目录 Controller处理请求 创建Controller 类 @Controller注解 标识方法@RequestMapping 测试 Controller处理请求 由于 在建立 SpringBoot项目时选择的 Web > Spring Web , Maven会导入 SpringMVC 框架 依赖, 做为 Web处理框架 在 SpringMVC框架中 , 通过 Controller类中的方法 来处理请求, 产生响应 在方法中 要解决以下问题 标识方法 转页 接收请求时传递信息 封装响应信息

  • springboot中的controller注意事项说明

    关于controller注意事项 spring boot的controller水深无比,经过学习,我总结一些tips,以便以后参照,减少错误: 1.controller主要有两个标签 @Controller和@RestController,两个标签无法同时发挥作用,前者标注的类只能返回静态文件,后者标注的类用于返回数据类型如json字符串 2.使用@Controller标签时 templates下的文件并不能被识别(自己试验过,发现和网上很多说法都不一样),只有static文件夹下文件能直接被读

  • SpringBoot Controller中的常用注解

    目录 概述 常用注解简介 1.@Controller 2.@RestController 3.@RequestMapping 4.@RequestBody 5.@RequestParam 6.@PathVariable 总结 概述 Controller是Spring接受并处理网页请求的组件,是整个应用的入口,因此学会Controller的常用注解对理解一个应用是重中之重.SpringBoot的Controller中经常会用到注解@Controller.@RestController.@Reque

  • SpringBoot controller参数校验方法详细讲解

    目录 单参数校验 实体类校验 分组校验 嵌套校验 自定义注解 参数校验主要使用两个标签@Validated和@Valid: @Valid是Hibernate的注解校验,@Validated是spring的,是@Valid的增强:这两个标签也有一些不同之处,@Valid可以标注在成员属性上也可以嵌套校验,而@Validated不行,但是@Validated可以使用分组校验: maven导入: <dependency> <groupId>org.springframework.boot

  • SpringBoot复杂参数应用详细讲解

    复杂参数: Map<String, Object> map Model model HttpServletRequest request HttpServletResponse response 以上复杂参数所携带的数据均可被放在 request 请求域中,其中 Map 与 Model 类型处理方法一致.(本文只介绍使用) 使用方法: 1. controller 类完整代码: import org.springframework.stereotype.Controller; import or

  • 详解SpringBoot中Controller接收对象列表实现

    如果Spring Boot中对应的Controller要接收一个对象,该对象中又存放了一个List列表,那么页面该如何传递相关应的参数信息呢. 本篇文章给大家一个简单的示例,提供一种实现方式. 实体类 首先看实体类的结构(注意使用了Lombok): @Data public class Rules { private List<Rule> rules; } 对应Rule实体类代码如下: @Data public class Rule { /** * 类名 */ private String c

  • Python中关于面向对象中继承的详细讲解

    目录 1.继承 2.单继承 3.多继承 4.子类重写父类的同名属性和方法 5.子类调用父类同名属性和方法 6.多层继承 7.调用父类方法super() 8.案例 1.继承 在程序中,继承描述的是多个类之间的所属关系. 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里. 那么类A就是基类,也叫做父类:类B就是派生类,也叫做子类. 案例: # 父类 class A(object): def __init__(self): self.num=50 def print_num(s

  • Java Thread多线程开发中Object类详细讲解

    目录 方法概览 Thread wait  notify notifyAll方法详解 作用 阻塞阶段 唤醒阶段 遇到中断 代码展示 特点 通过wait notify方法实现生产者和消费者 sleep方法详解 sleep不会释放锁 sleep响应中断 总结 join方法详解 代码展示 yield方法 方法概览 Thread wait  notify notifyAll方法详解 作用 阻塞阶段 使用了wait方法之后,线程就会进入阻塞阶段,只有发生以下四种情况中的其中一个,线程才会被唤醒 另一个线程调

  • java中dart类详细讲解

    dart 是一个面向对象的语言;面向对象有 继承 封装 多态 dart的所有东西都是对象,所有的对象都是继承与object类 一个类通常是由属性和方法组成的 在dart中如果你要自定义一个类的话,将这个类放在main函数外面 类名使用大驼峰方法名使用小驼峰 1.定义这个类的属性和方法 //定义一个类的属性和方法 class Person { String name = '张三'; int age = 19; void getInfo() { // print('我叫$name,今年$age');

  • 如何实现springboot中controller之间的相互调用

    springboot controller之间相互调用 SpringBoot之间内部调用 @Autowired private RestTemplate restTemplate ;//自动装配restTemplate -------------------返回json字符串类型---------------------------------- @RequestMapping("/selectHospatal") @ResponseBody public String selectH

  • springboot中使用ElasticSearch的详细教程

    新建项目 新建一个springboot项目springboot_es用于本次与ElasticSearch的整合,如下图 引入依赖 修改我们的pom.xml,加入spring-boot-starter-data-elasticsearch <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</

  • java、spring、springboot中整合Redis的详细讲解

    java整合Redis 1.引入依赖或者导入jar包 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> 2.代码实现 public class JedisTest { public static void main(String[]

  • 详细讲解springboot如何实现异步任务

    目录 Spring Boot介绍 Spring Boot特点 异步任务 Spring Boot介绍 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.用我的话来理解,就是 Spring Boot 其实不是什么新的框架,它默认配置了很多框架的使用方式,就像 Maven 整合了所有的 Jar 包,Spring Boot 整合了所有的框架. Spr

  • 超详细讲解SpringBoot参数校验实例

    目录 使用传统方式的弊端 引入依赖 注解说明 一.对实体类进行校验 1.entity 2.controller 3.编写全局统一异常处理 二.针对单个参数进行校验 三.分组校验 1.entity 2.controller 四.自定义分组校验 1.entity 2.CustomSequenceProvider 3.controller 五.自定义校验 1.定义校验注解 2.实现注解 六.嵌套校验 七.快速失败 注意事项 总结 使用传统方式的弊端 public String addUser(User

随机推荐