【Java学习】自定义注解的使用,校验 Excel 上传文件
2026/9/15 7:49:53 网站建设 项目流程

基础概念

一、引入依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>

验证注解都在javax.validation.constraints这个包下

二、定义自定义注解 @XlsxFile

项目中的注解定义在XlsxFile.java

@Documented @Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = XlsxFileValidator.class) public @interface XlsxFile { String message() default "请上传有效的 Excel 文件"; long maxSize() default 5 * 1024 * 1024; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }

@Constraint(validatedBy = XlsxFileValidator.class)
这是自定义校验注解最关键的配置。
当遇到@XlsxFile时,使用XlsxFileValidator执行具体校验。


注解负责声明规则,Validator 负责实现规则。

messagegroupspayload

一个标准的 Bean Validation 约束注解通常需要提供:

String message() default "..."; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};

它们的作用分别是:

  • message:默认校验失败信息;
  • groups:校验分组,可以针对不同业务场景执行不同校验;
  • payload:携带额外元数据,普通项目中较少直接使用。

三、实现自定义校验器


校验器位于XlsxFileValidator.java

public class XlsxFileValidator implements ConstraintValidator<XlsxFile, MultipartFile> { private long maxSize; @Override public void initialize(XlsxFile annotation) { this.maxSize = annotation.maxSize(); } @Override public boolean isValid( MultipartFile file, ConstraintValidatorContext context) { if (file == null || file.isEmpty()) { return fail(context, "请选择要上传的 Excel 文件"); } if (file.getSize() > maxSize) { return fail(context, "Excel 文件不能超过 5 MB"); } String filename = file.getOriginalFilename(); if (filename == null || !filename.toLowerCase(Locale.ROOT) .endsWith(".xlsx")) { return fail(context, "仅支持 .xlsx 文件"); } return true; } private boolean fail( ConstraintValidatorContext context, String message) { context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(message) .addConstraintViolation(); return false; } }

1.ConstraintValidator的两个泛型

ConstraintValidator<XlsxFile, MultipartFile>

第一个泛型表示对应的注解类型:

XlsxFile

第二个泛型表示被校验的数据类型:

MultipartFile

因此,这个校验器用于校验标注了@XlsxFileMultipartFile数据。

2.initialize()方法

@Override public void initialize(XlsxFile annotation) { this.maxSize = annotation.maxSize(); }

该方法用于读取注解上的配置。
例如:

@XlsxFile(maxSize = 10 * 1024 * 1024)

那么annotation.maxSize()得到的就是 10 MB。

3.isValid()方法

isValid()是真正执行校验的方法:

@Override public boolean isValid( MultipartFile file, ConstraintValidatorContext context) { }

返回值含义:

  • 返回true:校验通过;
  • 返回false:校验失败。

当前项目依次校验了文件是否为空、文件大小以及文件后缀。

4. 自定义错误信息

如果直接返回false,框架会使用注解的默认message

项目中希望不同错误返回不同提示,因此使用:

context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(message) .addConstraintViolation();

这样便可以分别返回:

请选择要上传的 Excel 文件
Excel 文件不能超过 5 MB
仅支持 .xlsx 文件

四、在 DTO 中使用注解

项目中的请求对象是UserImportRequest.java

public class UserImportRequest { @XlsxFile(maxSize = 5 * 1024 * 1024) private MultipartFile file; public MultipartFile getFile() { return file; } public void setFile(MultipartFile file) { this.file = file; } }

六、使用@Valid触发校验

仅仅在字段上添加@XlsxFile还不够,还需要在 Controller 参数前添加@Valid

@PostMapping( value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE ) public ImportResult importUsers( @Valid @ModelAttribute UserImportRequest request) { MultipartFile file = request.getFile(); try (InputStream inputStream = file.getInputStream()) { return userService.importUsers(inputStream); } catch (IOException exception) { throw new UserImportException( 0, "文件读取失败", List.of(new ImportError( 0, "file", "无法读取上传的文件" )) ); } }

其中:

  • @ModelAttribute:将multipart/form-data请求参数绑定到 DTO;
  • @Valid:触发 DTO 字段上的 Bean Validation 校验;
  • 校验通过后才会进入方法主体;
  • 校验失败时会抛出参数校验异常。

这里很容易忘记@Valid。如果没有它,字段上的@XlsxFile通常不会自动执行。

七、统一处理校验异常

项目通过GlobalExceptionHandler.java统一处理参数校验异常:

@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ImportResult> handleValidationException( MethodArgumentNotValidException exception) { List<ImportError> errors = exception .getBindingResult() .getFieldErrors() .stream() .map(error -> new ImportError( 0, error.getField(), error.getDefaultMessage() )) .toList(); ImportResult result = ImportResult.failure( 0, "文件校验失败", errors ); return ResponseEntity.badRequest().body(result); }

这里通过:

exception.getBindingResult().getFieldErrors()

获取所有字段错误,再转换成项目统一的ImportError对象。

这样Controller不需要编写大量if/else,客户端收到的异常结构也更加统一。

八、学习总结

自定义注解这种声明式写法更加直观,也更容易复用和维护。

自定义校验注解的通用实现步骤可以总结为:

  1. 引入Validation依赖;
  2. 创建自定义注解;
  3. 使用@Constraint绑定校验器;
  4. 实现ConstraintValidator
  5. 在 DTO 字段上使用注解;
  6. 通过@Valid触发校验;
  7. 使用全局异常处理器统一返回错误。

这套模式不只适用于 Excel 文件,还可以扩展到身份证号、手机号、枚举值、日期范围、业务编码以及多个字段之间的关联校验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询