如何在方法调用前拦截并校验参数?自定义注解校验方案求助
解决方法调用前的参数拦截与自定义字段校验问题
针对你遇到的「用Proxy导致事务异常」和「@Valid校验范围太广」的问题,我整理了两个贴合Spring生态的可行方案,帮你精准实现只校验标注@DtoFields的字段:
方案一:Spring AOP 拦截(推荐,无事务冲突)
Spring AOP是Spring原生的拦截方案,会自动融入现有代理链(比如事务代理),不会破坏原有功能,完美避开你之前遇到的事务提交异常。
实现步骤:
- 定义切面类,拦截带有
@Dto注解的Controller方法:
@Aspect @Component public class DtoValidationAspect { // 拦截所有标注了@Dto的方法 @Around("@annotation(com.yourpackage.Dto)") public Object validateTargetFields(ProceedingJoinPoint joinPoint) throws Throwable { // 获取方法的所有参数 Object[] args = joinPoint.getArgs(); for (Object arg : args) { // 这里可以根据实际情况调整判断逻辑,比如判断是否是需要校验的DTO类型 if (arg instanceof Entity) { validateDtoFields((Entity) arg); } } // 执行原方法逻辑 return joinPoint.proceed(); } // 核心校验逻辑:只校验标注了@DtoFields的字段 private void validateDtoFields(Entity entity) throws IllegalAccessException { Field[] allFields = entity.getClass().getDeclaredFields(); for (Field field : allFields) { // 检查字段是否标注了@DtoFields if (field.isAnnotationPresent(DtoFields.class)) { field.setAccessible(true); Object fieldValue = field.get(entity); // 这里写你的自定义校验规则,比如非空校验、格式校验等 if (fieldValue == null) { throw new IllegalArgumentException(String.format("字段[%s]不能为空", field.getName())); } // 示例:如果是字符串类型,校验长度 if (fieldValue instanceof String && ((String) fieldValue).trim().isEmpty()) { throw new IllegalArgumentException(String.format("字段[%s]不能为空白", field.getName())); } // 其他自定义校验规则... } } } }
- 无需修改原有Controller和Entity,保持你现有的
@Dto和@DtoFields注解即可。
为什么这个方案不会有事务问题?
Spring的事务代理是基于动态代理实现的,而Spring AOP会自动将自己的切面逻辑加入到现有的代理链中,不会覆盖或破坏事务代理的功能,所以不会出现你之前遇到的TransactionSystemException。
方案二:自定义校验组(复用JSR-380体系)
如果你想复用Spring自带的JSR-380校验(比如@NotNull),但只想校验标注@DtoFields的字段,可以通过校验组来实现范围控制:
实现步骤:
- 定义一个校验组接口:
// 空接口,仅作为校验组标识 public interface DtoValidationGroup {}
- 修改Entity的字段注解,将需要通过
@Dto校验的字段的校验注解指定到DtoValidationGroup组:
@Column(nullable = false) @NotNull(groups = DtoValidationGroup.class) // 仅在Dto校验组生效 @DtoFields protected String name; protected String description; @Column(nullable = false, updatable = false) @CreatedDate protected LocalDateTime creationDate; @NotNull // 未指定组,不会被Dto校验组触发 protected boolean deleted; @NotNull(groups = Default.class) // Default组是默认组,仅@Valid会触发 @LastModifiedDate protected LocalDateTime modificationDate;
- 修改Controller方法参数,用
@Validated(DtoValidationGroup.class)替代@Valid:
@PostMapping @Dto public CompletableFuture<Response> createEntity(@RequestBody @Validated(DtoValidationGroup.class) Entity entity) { // 业务逻辑... }
这样Spring就只会校验标注了DtoValidationGroup组的字段,也就是你标记了@DtoFields的那些字段,不会触发其他未指定组的校验注解。
为什么你之前的Proxy方案会出问题?
你用BeanPostProcessor手动创建Proxy.newProxyInstance,会覆盖Spring原本为Controller生成的代理(比如事务代理),导致事务切面的逻辑没有被正确执行,最终在提交JPA事务时抛出RollbackException。Spring的代理是分层嵌套的,手动创建代理会破坏这个结构,而Spring AOP会自动处理代理链的整合,所以不会有这个问题。
内容的提问来源于stack exchange,提问作者袗褉褌械屑 袦邪褌褞褏懈薪
相关产品推荐
相关产品推荐

