从来都是在controller层进行参数校验,便对吗?

大家好,这里是小奏 ,觉得文章不错可以关注公众号小奏技术

传统的参数校验

相信现在的大部分web项目进行参数校验都是通过如下方式

  1. 添加依赖
xml 复制代码
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
  1. 在 controller进行参数校验
  • ValidationController
java 复制代码
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class ValidationController {

    private final XiaoZouService xiaoZouService;

    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody XiaoZouDTO xiaoZouDTO) {
        xiaoZouService.createUser(xiaoZouDTO);
        return ResponseEntity.ok("用户创建成功");
    }
}
  • XiaoZouDTO
java 复制代码
@Data
public class XiaoZouDTO {

    @NotNull(message = "用户ID不能为空")
    private Long id;

    @NotBlank(message = "用户名不能为空")
    @Length(min = 2, max = 20, message = "用户名长度必须在2-20之间")
    private String username;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
}

实际的业务逻辑处理被封装在xiaoZouService

异常处理器

我们再随便加一个全局异常处理器

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        StringBuilder errors = new StringBuilder();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.append(fieldName).append(": ").append(errorMessage).append("; ");
        });
        return ResponseEntity.badRequest().body(errors.toString());
    }
}

测试

然后我们进行接口请求

curl 复制代码
POST http://localhost:8091/api/xiao-zou
Accept: application/json
Content-Type: application/json

{
  "name": "xiao-zou",
  "age": 18
}

参数我们随便乱传,就会出现如下错误信息提示

css 复制代码
email: 邮箱不能为空; id: 用户ID不能为空; username: 用户名不能为空; 

结果看似满足我们的预期。

但是目前我们会注意到一个问题

  1. 我们的参数实际使用是在service,但是我们的参数校验是在controller
  2. 如果我们存在自定义aop切面进行参数修改,aop的切面执行顺序在参数校验之后,那么我们的参数校验就失效了
  3. 我们的service方法并不单单仅在controller中调用,我们的service方法可能会在其他地方调用,比如jobmqRPC等等,这种情况下我们的参数校验就失效了

我们来实际举例说明

AOP切面修改参数导致的参数校验失败

假设我们的的邮箱为了数据安全之类的业务背景,统一通过请求头获取

那么我们可能定义如下一个切面

java 复制代码
@Aspect
@Slf4j
@RequiredArgsConstructor
public class XiaoZouHttpAspect {

    @Pointcut("@annotation(com.spring.boot.base.annotation.XiaoZouResponse)")
    public void controllerMethodAspect() {

    }
    
    @GetMapping
    @Before("controllerMethodAspect()")
    public void doBefore(JoinPoint joinPoint) {

        Object[] args = joinPoint.getArgs();
        if (Objects.isNull(args) || args.length == 0) {
            return;
        }
        for (Object argObj : args) {
            if (argObj instanceof XiaoZouDTO) {
                XiaoZouDTO xiaoZouDTO = (XiaoZouDTO) argObj;
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest httpServletRequest = requestAttributes.getRequest();
                xiaoZouDTO.setEmail(httpServletRequest.getHeader("x-xiaozou-email"));
            }
        }
        
    }
    
}

此时如果请求头里面没有x-xiaozou-email,但是我们的body里面传了email

这时候的参数校验也会失败,失败步骤如下

  1. XiaoZouDTO接收到body的email参数进行参数校验通过了参数校验
  2. XiaoZouHttpAspect切面修改了email参数,但是这个修改是在参数校验之后的,email为空进入到service层,所以参数校验失败

dubbo调用参数校验失效

dubbo例的测试用就很简单了。我们的service可能既给controller调用,也可能给dubbo调用

java 复制代码
@DubboService
public class XiaoZouServiceImpl implements XiaoZouService {

    @Override
    public String createUser(XiaoZouDTO xiaoZouDTO) {
        return name;
    }
    
}

dubbo过来的请求不会经过我们在controller的参数校验,所以参数校验失效

这里的业务逻辑处理就会因为必要的参数为空出现不符合我们预期的业务逻辑

MQ、xxl-job参数校验失败

MQxxl-job等等的调用也是一样的,我们的参数校验只会在controller中生效,其他地方调用就会失效

最佳实践(解决方式)

很明显最佳的参数校验并不是在controller中进行,而是在service中进行

因为我们的参数实际是给service使用的,我们的很多请求也会绕过controller导致service参数校验失效

所以最佳方式应该是在接口层service进行参数校验

如果我们想要对XiaoZouService进行参数校验,我们应该使用如下方式

java 复制代码
@Validated
public interface XiaoZouService {

    boolean createUser(@Valid XiaoZouDTO xiaoZouDTO);
}
  1. 我们在service接口上加上@Validated注解
  2. service方法上加上@Valid注解

这样我们的参数校验就会在service层生效了。

不管请求是从controllerdubbomqjob等等过来的,我们的参数校验都会生效。

就不会出现因为参数校验失效导致的业务逻辑出现问题了

总结

目前行业内大部分的教程和规范都是在controller中进行参数校验

导致大家都在controller中进行参数校验。实际这个做法是非常不合理和容易出问题的

如果我们在指定项目规范的时候,我们应该规定参数校验应该在service层进行,而不是在controller层进行

相关推荐
银氨溶液2 小时前
RabbitMQ实现消息发送接收——实战篇(路由模式)
java·开发语言·后端·消息队列·rabbitmq·消息分发
爱敲代码的小冰3 小时前
spring boot 过滤器
java·spring boot·后端
大梦百万秋3 小时前
Go 语言新手入门:快速掌握 Go 基础
开发语言·后端·golang
猿java6 小时前
在Spring中,事务是如何隔离的?
java·后端·spring
橘子海全栈攻城狮6 小时前
[源码+调试+讲解]微信小程序的成都美食分享系统springboot
开发语言·spring boot·后端·微信小程序·小程序·美食
uhakadotcom7 小时前
杀疯了,90后博士第一次创业,刚成立1年半,融资12亿,估值30亿
后端·算法·架构
麻衣带我去上学7 小时前
SpringCloud启动源码分析
后端·spring·spring cloud
人生の三重奏7 小时前
django项目3——连接sqlite数据库
后端·python·django