Spring Validation:基于抽象请求参数类的灵活校验方案

本文旨在解决Spring Boot项目中,如何针对不同类型的报告生成需求,设计可扩展且易于维护的请求参数校验方案。通过抽象父类定义通用参数,子类继承并添加特定参数,结合Spring Validation框架,实现灵活的请求参数校验,避免代码冗余,提高代码复用率。

在实际的Spring Boot项目中,我们经常会遇到需要根据不同的请求参数生成不同类型的报告的场景。例如,一个报告生成接口可能需要根据category参数来决定生成哪种类型的报告,而不同类型的报告可能需要不同的请求参数。如何设计一个可扩展且易于维护的请求参数校验方案,就显得尤为重要。

一种常见的做法是定义一个抽象的请求参数类,其中包含所有报告类型都需要的通用参数,然后为每种报告类型定义一个具体的请求参数类,继承自抽象类,并添加该报告类型特有的参数。

1. 定义抽象请求参数类

首先,定义一个抽象类 ReportRequestDTO,其中包含所有报告类型都需要的通用参数,并使用JSR-303注解进行校验。

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;
}

在这个例子中,foo 和 bar 是所有报告类型都需要的通用参数,@NotEmpty 注解表示这两个参数不能为空。message属性可以自定义校验失败时的提示信息。

2. 定义具体请求参数类

接下来,为每种报告类型定义一个具体的请求参数类,继承自 ReportRequestDTO,并添加该报告类型特有的参数,同样使用JSR-303注解进行校验。

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

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

@Data
public class ReportBDTO extends ReportRequestDTO {
    private Integer someNumber;
}

ReportADTO 继承了 ReportRequestDTO,并添加了 foobar 参数,这个参数是 ReportA 特有的。ReportBDTO 继承了 ReportRequestDTO,并添加了 someNumber 参数,这个参数是 ReportB 特有的。

3. 在Controller中使用Validation

在 Controller 中,接收请求参数,并使用 @Valid 注解进行校验。关键在于如何根据category参数实例化对应的DTO。

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import org.springframework.web.server.ResponseStatusException;

@RestController
@Validated // 需要添加此注解才能使@Valid生效
public class ReportController {

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

        if ("A".equals(category)) {
            if (!(reportRequestDTO instanceof ReportADTO)) {
                 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "category A requires ReportADTO");
            }
            ReportADTO reportADTO = (ReportADTO) reportRequestDTO;
            // Process ReportA
            return ResponseEntity.ok("Report A generated with foo: " + reportADTO.getFoo() + ", bar: " + reportADTO.getBar() + ", foobar: " + reportADTO.getFoobar());
        } else if ("B".equals(category)) {
            if (!(reportRequestDTO instanceof ReportBDTO)) {
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "category B requires ReportBDTO");
            }
            ReportBDTO reportBDTO = (ReportBDTO) reportRequestDTO;
            // Process ReportB
            return ResponseEntity.ok("Report B generated with foo: " + reportBDTO.getFoo() + ", bar: " + reportBDTO.getBar() + ", someNumber: " + reportBDTO.getSomeNumber());
        } else {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid category");
        }
    }
}

在这个例子中,我们使用了 @Valid 注解来校验 ReportRequestDTO 对象。如果校验失败,Spring Boot 会自动抛出一个 MethodArgumentNotValidException 异常。

重要提示:

  • @Validated 注解: Controller类需要添加@Validated注解,才能使@Valid注解生效。
  • 类型转换: 在Controller方法中,需要根据category参数,将ReportRequestDTO转换为对应的具体类型。
  • 异常处理: 需要处理MethodArgumentNotValidException异常,并返回合适的错误信息。可以使用@ExceptionHandler注解来定义全局异常处理。
  • DTO实例化: 由于无法直接通过new关键字实例化抽象类,需要借助其他方式,例如反射、工厂模式等,根据category参数动态创建对应的DTO实例。 本例中,直接使用了 @ModelAttribute,Spring会自动根据请求参数进行绑定,但需要确保请求参数的名称与DTO的属性名称一致。
  • ModelAttribute绑定: @ModelAttribute 注解会将请求参数绑定到 ReportRequestDTO 对象上。 如果请求参数中包含了 ReportADTO 或 ReportBDTO 特有的参数,Spring 会尝试将这些参数也绑定到 ReportRequestDTO 对象上,但由于 ReportRequestDTO 类中没有这些属性,这些参数会被忽略。因此,需要在Controller方法中进行类型转换,才能访问到这些参数。

4. 全局异常处理

为了更好地处理校验失败的情况,可以定义一个全局异常处理类,处理 MethodArgumentNotValidException 异常,并返回统一的错误信息。

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            errors.put(error.getField(), error.getDefaultMessage());
        });
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}

总结

通过定义抽象请求参数类和具体请求参数类,结合Spring Validation框架,我们可以实现一个可扩展且易于维护的请求参数校验方案。这种方案可以避免代码冗余,提高代码复用率,并使代码更加清晰易懂。同时,全局异常处理可以提供统一的错误信息,提高用户体验。

注意事项

  • 在实际项目中,可以根据需要添加更多的校验注解,例如 @Size、@Email、@Pattern 等。
  • 可以使用自定义的校验注解,实现更复杂的校验逻辑。
  • 可以结合AOP,实现更加灵活的校验方案。
  • 务必进行充分的测试,确保校验逻辑的正确性。

这种方法允许你在不修改现有端点的情况下添加新的报告类型,只需创建新的DTO类并实现相应的报告生成逻辑即可,提高了代码的可维护性和可扩展性。

关于我们

奈瑶·映南科技互联网学院是多元化综合资讯平台,提供网络资讯、运营推广经验、营销引流方法、网站技术、文学艺术范文及好站推荐等内容,覆盖多重需求,助力用户学习提升、便捷查阅,打造实用优质的内容服务平台。

搜索Search

搜索一下,你就知道。