目录
引言
在实际项目中,我们经常需要对用户输入的数据进行验证。用户在编写验证注解时,可能会使用占位符来定义错误消息,例如 @Size(min = 1, max = 100, message = "长度必须介于 {min} 与 {max} 之间")。这样的错误消息虽然灵活,但在实际显示时不够直观,无法直接告诉用户具体的错误原因。本文将展示如何通过AOP技术动态替换这些占位符,生成更加具体的错误消息。
代码实现
以下是一个示例代码,展示了如何通过AOP切面动态替换验证器错误消息中的占位符:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintValidatorContext;
import java.util.Map;
/**
* 验证器切面类,用于拦截验证器的验证方法并定制错误消息
* 主要目的是为了获取验证器的默认错误消息,并根据其中的占位符替换实际的字段值
* 从而生成更加动态和有意义的错误提示信息
* <p>
* 长度必须介于 {min} 与 {max} 之间 -> @Size(min = 1, max = 100, message = "长度必须介于 {min} 与 {max} 之间")
*
*
* @author ds
*/
@Aspect
@Component
public class ValidatorAspect {
/**
* 定义切点,匹配所有执行验证的约束验证器的isValid方法
* 这里使用了AspectJ的切点表达式来精确匹配验证方法
*
* @param value 被验证的值
* @param context 验证器上下文,包含了验证过程中的上下文信息
*/
@Pointcut(value = "execution(* javax.validation.ConstraintValidator+.isValid(Object, javax.validation.ConstraintValidatorContext)) && args(value, context)", argNames = "value,context")
public void isValidMethod(Object value, ConstraintValidatorContext context) {
}
/**
* 在验证方法返回后执行,如果验证失败,则定制错误消息
* 通过反射和流处理来动态替换错误消息中的占位符
*
* @param joinPoint 切入点对象,包含了关于当前正在被拦截的方法的信息
* @param value 被验证的值
* @param context 验证器上下文
* @param result 验证方法的返回值,表示验证是否成功
*/
@AfterReturning(value = "isValidMethod(value, context)", argNames = "joinPoint,value,context,result", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object value, ConstraintValidatorContext context, boolean result) {
// 如果验证失败,则定制错误消息
if (!result) {
// 获取约束描述符中的属性,用于替换错误消息中的占位符
Map<String, Object> attributes = ((ConstraintDescriptorImpl<?>) ((ConstraintValidatorContextImpl) context).getConstraintDescriptor()).getAnnotationDescriptor().getAttributes();
// 获取默认的错误消息模板
String message = context.getDefaultConstraintMessageTemplate();
// 使用流替换模板中的占位符,s1 为当前处理的消息字符串,entry 为属性项
message = attributes.entrySet().stream()
.reduce(message,
(str, entry) -> str.replaceAll("{" + entry.getKey() + "}", entry.getValue().toString()),
// 并行流情况下的合并逻辑,这里可以直接返回 s1
(s1, s2) -> s1
);
// 禁用默认的错误消息
context.disableDefaultConstraintViolation();
// 构建并添加新的错误消息
context.buildConstraintViolationWithTemplate(message)
.addConstraintViolation();
}
}
}
关键点解析
1. 切点定义
@Pointcut 注解定义了一个切点,匹配所有执行验证的约束验证器的 isValid 方法。使用 execution 表达式来精确匹配方法签名,并通过 args 参数指定方法参数。
2. 后置通知
@AfterReturning 注解定义了一个后置通知,在验证方法返回后执行。
如果验证失败(result 为 false),则通过反射获取约束描述符中的属性,并使用 StrUtil.replace 方法动态替换错误消息中的占位符。
3. 自定义错误消息
context.disableDefaultConstraintViolation() 方法禁用默认的错误消息。
context.buildConstraintViolationWithTemplate(message).addConstraintViolation() 方法构建并添加新的错误消息。
通过上述代码实现,我们可以动态替换验证器错误消息中的占位符,生成更加具体和友好的验证错误消息,提升用户体验。AOP技术的引入使得这一过程变得简单且高效。希望本文能为读者在实际项目中解决类似问题提供参考。