springboot中controller层代码优雅写法

本文探讨了Spring框架中Controller层的职责和常见问题,提出了优雅的解决方案。包括统一返回结构以增强接口友好性,使用ResponseBodyAdvice进行响应处理的统一,利用JSR303进行参数校验,以及自定义异常和全局异常处理,以提升代码的整洁性和可维护性。
摘要由CSDN通过智能技术生成

在基于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的校验规则,可以明确返回的结构,包括异常情况。

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在 Spring Boot ,控制器负责处理来自客户端的请求,并返回相应的响应。控制器类通常使用 @Controller 或 @RestController 注解进行注释。前者主要用于处理视图请求,而后者则主要用于处理 RESTful API 请求。控制器类的方法通常使用 @RequestMapping 注解进行注释,以指定该方法处理的请求路径和请求方法。 ### 回答2: Spring Boot框架是在Spring的基础上搭建而成的,它将Spring的各种优秀特性和工具集集成在了一起,更加简单、快速、便捷地开发Web应用。而Controller是Web应用的一个重要组成部分,它类似于Web应用的入口,使得用户通过请求访问到应用的业务逻辑和数据。下面我们来聊一聊Spring BootController。 首先,Spring Boot提供了多种@Controller的注解,如@RestController、@Controller和@Component等,它们的区别在于注解的作用范围和默认返回类型的不同。其@RestController是@Controller和@ResponseBody的组合注解,它表示该类是控制器且所有方法都返回JSON数据。而@Component是Spring框架的一个注解,用于将类标注为组件类,这些类会被Spring自动扫描并装配到应用上下文。 其次,在Spring Boot,我们可以使用@RequestMapping注解来定义请求的URL路径,以及请求方式(GET、POST、PUT、DELETE等),从而让Spring Boot自动将请求映射到对应的Controller方法。在Controller方法,我们可以使用@RequestParam注解来获取请求参数,或者使用@PathVariable注解来获取URL路径的参数,还可以使用@RequestBody注解来获取请求体的JSON数据,并转化为Java对象。 最后,Spring BootController还支持使用@ExceptionHandler注解来定义异常处理方法,用于捕捉和处理应用程序抛出的异常,同时保证应用程序的健壮性和可靠性。异常处理方法需要通过方法签名来标识要处理的异常类型,这样在应用程序抛出对应的异常时,就可以自动调用对应的异常处理方法进行处理。 总之,Spring Boot框架Controller提供了丰富的注解和方法来处理请求、返回响应和处理异常,简化了Web应用的开发和维护工作,让程序员专注于业务逻辑的实现,增强了程序的可读性和可维护性,大大提高了开发效率和应用质量。 ### 回答3: Spring Boot 是众多开源框架的一种,它能够帮助开发者快速搭建和开发基于 Spring 框架的 Web 应用。在 Spring Boot ,控制器Controller)是应用程序的一个重要组成部分,它负责接收来自客户端的请求并返回对应的响应。 而在 Spring Boot ,控制器的核心是 @Controller 注解。这个注解用于声明一个控制器类,使其被 Spring Boot 框架所管理。 在控制器常用的注解还有 @RequestMapping、@GetMapping、@PostMapping、@PutMapping 和 @DeleteMapping 等。这些注解可以用来声明请求 URL 和处理方法之间的映射关系。 在控制器方法,可以通过注解 @RequestBody 来获取请求体的数据,通过 @RequestParam 来获取请求 URL 的参数,通过 @PathVariable 来获取 URL 的路径参数。 除了以上常用的注解之外,Spring Boot 的控制器还支持 AOP、事务管理、数据验证和异常处理等功能,这使得开发者能够更加方便地进行 Web 应用的开发,提高代码的可维护性和可读性。 总之,Spring Boot 的控制器在 Web 应用的开发过程扮演着重要的角色,通过注解和各种功能的支持,使得开发者能够更加轻松方便地实现请求的处理和相应的响应。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值