自定义注解使用简介

本文详解在Spring Boot中实现方法级参数校验的自定义注解全流程,涵盖Hibernate Validator依赖引入(地基)、@Validated注解启用方法校验(触发器)、符合JSR-380规范的自定义注解定义及ConstraintValidator实现(执行者),并强调Controller类上声明@Validated的关键约束。适用于Spring Boot 2.3+版本的校验机制适配。

由于 @RequestParam 这种直接写在方法参数上的校验,走的是 方法级别校验(Method Validation) 机制,和 @RequestBody 的校验机制完全不同。

以下是这三个关键内容的正确写法和内在逻辑:


1. spring-boot-starter-validation(地基)

  • 作用:引入 Hibernate Validator 实现库。没有它,所有的 @NotNull、@Validated 和你的自定义注解全部是哑火的废纸。

  • 注意:如果是 Spring Boot 2.3 及以上版本,默认不再包含它,必须像你这样手动引入,这一步你做对了。


2. @Validated(触发器)

  • 作用:它是 Spring 提供的注解,作用是开启方法参数级别的 AOP 校验。

  • 位置:必须加在 Controller 类上,绝对不能加在方法上。

  • 避坑:在 @RequestParam 参数校验中,千万不要用 @Valid!@Valid 主要用于级联校验(比如 @RequestBody 里的实体对象),在这个场景下加 @Valid 是无效的,甚至可能干扰框架的判断。


3. 自定义注解与校验器(执行者)

这是最容易写错的地方,必须严格遵守 JSR-380 规范(包含特定的三个方法)。

第一步:定义 @ValidFile 注解

java

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.FIELD}) // 必须允许在参数上使用
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FileValidator.class) // 🌟 核心:绑定校验逻辑类
@Documented
public @interface ValidFile {
    
    // 🌟 以下是必须包含的三个标准方法
    String message() default "文件不符合要求";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    // 可选:如果你想把限制值做成可配置的(比如不同接口允许不同大小)
    long maxSize() default 50 * 1024 * 1024L; // 默认 50MB
    
    String[] allowTypes() default {"jpg", "jpeg", "png", "pdf", "doc", "docx", "xls", "xlsx"};
}
第二步:实现 FileValidator 校验器

java

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;

public class FileValidator implements ConstraintValidator<ValidFile, MultipartFile> {
    
    private long maxSize;
    private String[] allowTypes;

    // 初始化时,获取注解上配置的参数
    @Override
    public void initialize(ValidFile constraintAnnotation) {
        this.maxSize = constraintAnnotation.maxSize();
        this.allowTypes = constraintAnnotation.allowTypes();
    }

    @Override
    public boolean isValid(MultipartFile file, ConstraintValidatorContext context) {
        System.out.println("====== 进入 FileValidator 校验了 ======");
        
        // 1. 如果文件为空,直接返回 true(如果要求不能为空,由 @NotNull 拦截)
        if (file == null || file.isEmpty()) {
            return true; 
        }

        // 2. 校验大小
        if (file.getSize() > maxSize) {
            setMessage(context, "文件大小不能超过 " + (maxSize / 1024 / 1024) + "MB");
            return false;
        }

        // 3. 校验后缀名
        String fileName = file.getOriginalFilename();
        if (fileName == null || !fileName.contains(".")) {
            setMessage(context, "文件名不合法");
            return false;
        }
        String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        if (!Arrays.asList(allowTypes).contains(ext)) {
            setMessage(context, "不支持的文件类型,仅支持:" + Arrays.toString(allowTypes));
            return false;
        }

        return true;
    }

    // 自定义返回的错误信息
    private void setMessage(ConstraintValidatorContext context, String message) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
    }
}

4. 最终 Controller 的正确姿势

以上都准备好后,你的 Controller 必须是这样的:

java

@RestController
@RequestMapping("/doc")
@Validated  // 🌟 核心:必须加在类上,开启 AOP 拦截
public class DocInfoController {

    @ApiOperation(value = "上传文件")
    @PostMapping(value = "/upload")
    public CommonResult<FileDTO.UploadRes> uploadFile(
            // 🌟 核心:直接使用 @ValidFile,前面不需要加 @Valid
            @ValidFile(message = "文件校验失败") @RequestParam(value = "file", required = false) MultipartFile file) {
        
        // 如果校验失败,根本不会执行到这里
        return CommonResult.success(docInfoService.uploadFile(file));
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

探索未知的自己

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值