Spring Validation:利用抽象请求参数类实现灵活的参数验证

本文旨在介绍如何在Spring Boot项目中,利用抽象类和继承机制,结合`javax.validation`框架,实现灵活且可扩展的请求参数验证方案。通过定义抽象的请求参数类,并让具体的请求参数类继承它,我们可以实现公共参数的统一验证,并针对不同的业务场景进行定制化的参数验证。

在实际的Web应用开发中,经常会遇到需要根据不同的业务场景,对请求参数进行不同验证的情况。如果将所有的验证逻辑都写在一个Controller方法中,会导致代码臃肿、难以维护。本文将介绍一种利用抽象类和继承机制,结合Spring Validation框架,实现灵活的请求参数验证方案。

方案概述

该方案的核心思想是:

  1. 定义一个抽象的请求参数类,包含所有请求中可能出现的公共参数,并使用javax.validation注解对这些参数进行验证。
  2. 针对不同的业务场景,创建具体的请求参数类,继承自抽象的请求参数类,并添加该场景特有的参数,同样使用javax.validation注解进行验证。
  3. 在Controller方法中,接收抽象的请求参数类作为参数,Spring会自动根据请求参数,实例化对应的具体请求参数类,并进行验证。
  4. 通过javax.validation.Validator接口,手动触发验证,并处理验证结果。

具体实现

1. 定义抽象请求参数类

import lombok.Data;
import javax.validation.constraints.NotEmpty;

@Data
public abstract class ReportRequestDTO  {
    @NotEmpty(message = "foo不能为空")
    private String foo;

    @NotEmpty(message = "bar不能为空")
    private String bar;

}

在这个例子中,ReportRequestDTO是一个抽象类,包含了两个公共参数foo和bar,并使用@NotEmpty注解,表示这两个参数不能为空。message属性用于自定义验证失败时的错误信息。

2. 定义具体请求参数类

import lombok.Data;
import javax.validation.constraints.NotEmpty;

@Data
public class ReportADTO extends ReportRequestDTO  {
    @NotEmpty(message = "foobar不能为空")
    private String foobar;
}

ReportADTO继承自ReportRequestDTO,并添加了一个特有的参数foobar,同样使用@NotEmpty注解进行验证。

3. Controller方法

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.ConstraintViolation;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

@RestController
@RequestMapping("/reports")
@Validated
public class ReportController {

    @Autowired
    private Validator validator;

    @GetMapping
    @ResponseBody
    public ResponseEntity getReport(@RequestParam(value = "category") String category,
                                            @Valid ReportRequestDTO reportRequestDTO) {

        if ("A".equals(category)) {
            ReportADTO reportADTO = new ReportADTO();
            reportADTO.setFoo(reportRequestDTO.getFoo());
            reportADTO.setBar(reportRequestDTO.getBar());
            // simulate set parameter from request
            reportADTO.setFoobar("test");

            Set> violations = validator.validate(reportADTO);

            if (!violations.isEmpty()) {
                StringBuilder sb = new StringBuilder();
                for (ConstraintViolation violation : violations) {
                    sb.append(violation.getMessage()).append(";");
                }
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, sb.toString());
            }

            return ResponseEntity.ok("Report A generated with: " + reportADTO.toString());

        } else {
            return ResponseEntity.ok("Other report");
        }
    }
}

在这个例子中,getReport方法接收一个ReportRequestDTO类型的参数reportRequestDTO。Spring会自动根据请求参数,尝试实例化ReportADTO或其他的ReportRequestDTO子类。

注意: 这里使用 javax.validation.Validator 手动触发验证。首先,需要注入Validator接口。然后,根据不同的category,手动创建对应的DTO对象,并将请求中的公共参数设置到该对象中。最后,调用validator.validate()方法进行验证,并处理验证结果。 如果验证失败,将错误信息封装到ResponseStatusException中,并抛出,Spring会自动处理该异常,并返回相应的错误信息。

4. 全局异常处理

为了统一处理验证失败的异常,可以自定义一个全局异常处理类。

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.server.ResponseStatusException;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity handleValidationException(ResponseStatusException ex) {
        return new ResponseEntity<>(ex.getReason(), ex.getStatus());
    }
}

总结

通过以上步骤,我们实现了一个灵活且可扩展的请求参数验证方案。该方案的优点包括:

  • 代码复用性高:公共参数的验证逻辑只需要定义一次,可以在多个具体的请求参数类中复用。
  • 可扩展性强:当需要添加新的业务场景时,只需要创建新的请求参数类,并继承自抽象的请求参数类即可。
  • 易于维护:每个请求参数类的验证逻辑都集中在该类中,易于理解和维护。

注意事项:

  • 确保Spring Validation框架已正确配置。
  • @Valid注解用于触发验证,需要添加到需要验证的参数上。
  • javax.validation.Validator接口提供了手动触发验证的功能,可以灵活地控制验证的时机和方式。
  • 全局异常处理可以统一处理验证失败的异常,并返回友好的错误信息。
  • 在实际应用中,可以根据需要,自定义更多的验证注解,以满足不同的业务需求。