自定义校验注解框架
经过上一篇文章的学习大家对校验注解有了初步认识,如果没有了解上一篇文章或对注解了解不深的小朋友请认真
学习!!!
本框架基于Spring容器
之上写的,利用AOP
和反射
原理实现注解验证。
注意:属性set方法上注解优先于属性名上的注解!!!
引用相关依赖
<!-- 自定义注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
前期准备
- 首先得写一个总开关注解,如果
@SpringBootApplication
注解类上有此注解说明整个项目开启注解验证,如果没有所有注解失效。
/**
* @Author: LailaiMonkey
* @Description:表示该项目启用注解验证
* @Date:Created in 2020-04-12 15:46
* @Modified By:
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({MonkeyValidatorConfiguration.class})
public @interface EnableMonkeyValidator {
}
如图:
- 第二个开关写在需要验证的
service
实现类上,表示此类是否需要注解验证,否则方法上校验注解失效。
/**
* @Author: LailaiMonkey
* @Description:表示该类中所能方法启用注解验证
* @Date:Created in 2020-04-12 15:03
* @Modified By:
*/
@Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface MonkeyValidator {
}
- 加载所有注解配置
/**
* @Author: LailaiMonkey
* @Description:加载所有注解
* @Date:Created in 2020-04-12 15:27
* @Modified By:
*/
public class MonkeyValidatorConfiguration {
/**
* MonkeyValidatorImpl没有@component修饰,所以无法直接在AOP中注入,通过加载bean方式注入
*
* @param monkeyInterface
* @return
*/
@Bean
public MonkeyValidatorAOP monkeyInterfaceAOP(MonkeyValidatorImpl monkeyInterface) {
return new MonkeyValidatorAOP(monkeyInterface);
}
/**
* 可以获得继承所有分解器的注解
*
* @param monkeyResolvers
* @return
*/
@Bean
public MonkeyValidatorImpl monkeyInterfaceImpl(List<AbstractMonkeyResolver> monkeyResolvers) {
return new MonkeyValidatorImpl(monkeyResolvers);
}
/**
* 加载所有注解
* 如果在框架中添加自己注解需要以Bean方式注入
* @return
*/
@Bean
public MonkeyEmailImpl monkeyEmail() {
return new MonkeyEmailImpl();
}
@Bean
public MonkeyMaxImpl monkeyMax() {
return new MonkeyMaxImpl();
}
//把所有注解以@Bean方式注入进来,此处省略,见代码
}
- AOP拦截
Service
实现上的注解,表示该类下的所有方法都需要验证
/**
* @Author: LailaiMonkey
* @Description:
* @Date:Created in 2020-04-12 15:04
* @Modified By:
*/
@Aspect
@Order
public class MonkeyValidatorAOP {
private MonkeyValidatorImpl monkeyValidator;
public MonkeyValidatorAOP(MonkeyValidatorImpl monkeyValidator) {
this.monkeyValidator = monkeyValidator;
}
/**
* 只拦截自定义注解
*/
@Pointcut("@within(com.monkey.monkeyValidator.MonkeyValidator) " +
"|| @annotation(com.monkey.monkeyValidator.MonkeyValidator)")
private void validateObject() {
}
@Before("validateObject()")
public void before(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//获取方法上的注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
//参数名
String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
//参数值
Object[] parameterValues = joinPoint.getArgs();
for (int i = 0; i < parameterValues.length; i++) {
//获得每个参数上的注解
Annotation[] annotations = parameterAnnotations[i];
monkeyValidator.validator(annotations, parameterNames[i], parameterValues[i]);
}
}
}
- 可以理解为校验框架的实现类,主要负责调用,需要调用哪个注解的实现类
/**
* @Author: LailaiMonkey
* @Description:
* @Date:Created in 2020-04-12 15:34
* @Modified By:
*/
public class MonkeyValidatorImpl {
private final ExpressionParser expressionParser = new SpelExpressionParser();
private Map<Class, AbstractMonkeyResolver> monkeyResolversMap = new HashMap<>();
/**
* 把注解和实现类对应起来
*
* @param monkeyResolvers
*/
MonkeyValidatorImpl(List<AbstractMonkeyResolver> monkeyResolvers) {
for (AbstractMonkeyResolver monkeyResolver : monkeyResolvers) {
monkeyResolversMap.put(monkeyResolver.getHandleClass(), monkeyResolver);
}
}
/**
* @param parameterAnnotations 参数注解
* @param parameterName 参数名
* @param parameterValue 参数值
*/
public void validator(Annotation[] parameterAnnotations,
String parameterName,
Object parameterValue) {
//根据方法注解调用相关验证
for (Annotation methodAnnotation : parameterAnnotations) {
//验证实体类型
if (MonkeyModel.class.isAssignableFrom(methodAnnotation.annotationType())) {
if (parameterValue != null) {
//集合、数组、Map、实体
if (parameterValue instanceof Collection) {
int index = 0;
for (Object o : ((Collection) parameterValue)) {
validatorModel(o, index);
index++;
}
} else if (parameterValue instanceof Object[]) {
int index = 0;
for (Object o : (Object[]) parameterValue) {
validatorModel(o, index);
index++;
}
} else if (parameterValue instanceof Map) {
int[] index = {0};
((Map) parameterValue).forEach((k, v) -> {
validatorModel(v, index[0]);
index[0]++;
});
} else {
validatorModel(parameterValue, null);
}
}
} else {
//筛选自定义验证注解
monkeyValidator(methodAnnotation, parameterName, parameterValue, null, null);
}
}
}
/**
* 验证实体
*
* @param model
*/
private void validatorModel(Object model, Integer index) {
try {
Field[] fields = model.getClass().getDeclaredFields();
String modelName = model.getClass().getName();
for (Field field : fields) {
//字段值
Object fieldValue = FieldUtils.getFieldValue(model, field.getName());
//获得属性上所有注解
Annotation[] annotations = findAnnotations(field, model.getClass());
boolean needValidator = true;
for (Annotation annotation : annotations) {
if (MonkeyValidatorOnCondition.class.isAssignableFrom(annotation.annotationType())) {
MonkeyValidatorOnCondition validate = (MonkeyValidatorOnCondition) annotation;
//获得该注解表达式
String value = validate.value();
//使用了Spring的SqEL,可以在EvaluationContext单独计算表达式的值
//把整个Model值放入EvaluationContext中
StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext(model);
//解析表达式
Expression expression = expressionParser.parseExpression(value);
//计算指定表达式值
Object expressionValue = expression.getValue(standardEvaluationContext);
//表达式为假不需要验证
if (Boolean.FALSE.equals(expressionValue)) {
needValidator = false;
break;
}
}
}
//需要验证
if (needValidator) {
for (Annotation annotation : annotations) {
monkeyValidator(annotation, field.getName(), fieldValue, index, modelName);
}
}
}
} catch (Exception e) {
throw new MonkeyValidatorException(e);
}
}
@SuppressWarnings("unchecked")
private void monkeyValidator(Annotation methodAnnotation, String parameterName,
Object parameterValue, Integer index, String modelName) {
//筛选自定义验证注解
if (monkeyResolversMap.containsKey(methodAnnotation.annotationType())) {
AbstractMonkeyResolver monkeyResolver = monkeyResolversMap.get(methodAnnotation.annotationType());
boolean validator = monkeyResolver.validator(parameterValue, methodAnnotation);
//验证失败抛出异常
if (!validator) {
StringBuilder errorMessage = new StringBuilder(parameterName + ":" + monkeyResolver.getMessage(methodAnnotation));
if (!StringUtils.isEmpty(modelName)) {
if (index == null) {
errorMessage.replace(0, errorMessage.length(), modelName.substring(modelName.lastIndexOf('.') + 1) + "." + errorMessage);
} else {
errorMessage.replace(0, errorMessage.length(), modelName.substring(modelName.lastIndexOf('.') + 1) + "[" + index + "]." + errorMessage);
}
}
throw new MonkeyValidatorException(errorMessage.toString());
}
}
}
/**
* 获得该字段上所有注解,set方法注解优先于字段注解
*
* @param field
* @param clazz
* @return
*/
private Annotation[] findAnnotations(Field field, Class<?> clazz) {
//获得set方法上注解
Method method = MethodUtils.getAccessibleMethod(clazz, "set" + StringUtils.capitalize(field.getName()), field.getType());
Annotation[] methodAnnotations = method.getDeclaredAnnotations();
if (methodAnnotations.length > 0) {
return methodAnnotations;
}
//字段上注解
return field.getDeclaredAnnotations();
}
- 最后需要所有注解实现,因为注解验证返回值和消息体等。。。都是一样的,所以需要继承一个抽象类
/**
* @Author: LailaiMonkey
* @Description:
* @Date:Created in 2020-04-12 15:23
* @Modified By:
*/
abstract public class AbstractMonkeyResolver<T extends Annotation> {
public abstract Class<T> getHandleClass();
public abstract boolean validator(Object value, T annotation);
public abstract String getMessage(T annotation);
}
自定义注解
前期配置已经准备完毕,现在开始写对应的校验注解,这里只举一种(验证字符串不为null或’’),详情见源码。
如果想要此注解生效需要在MonkeyValidatorConfiguration
中把注解以Bean
方式注入进来,否则不生效。
- 开始撰写自定义注解
/**
* @Author: LailaiMonkey
* @Description:用于字符串不能为空或空串
* @Date:Created in 2020-04-12 15:16
* @Modified By:
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface MonkeyNotBlank {
/**
* 提示信息
* @return
*/
String message() default "不能为空";
/**
* 不为Null则校验
* @return
*/
boolean nullable() default false;
}
- 撰写注解实现类,就是该注解需要处理的逻辑
/**
* @Author: LailaiMonkey
* @Description:
* @Date:Created in 2020-04-12 15:18
* @Modified By:
*/
public class MonkeyNotBlankImpl extends AbstractMonkeyResolver<MonkeyNotBlank> {
@Override
public Class<MonkeyNotBlank> getHandleClass() {
return MonkeyNotBlank.class;
}
@Override
public boolean validator(Object value, MonkeyNotBlank annotation) {
if (value == null) {
return annotation.nullable();
}
return !StringUtils.isEmpty(value.toString().trim());
}
@Override
public String getMessage(MonkeyNotBlank annotation) {
return annotation.message();
}
}
经过一系列的准备终于写完一个验证注解,前期准备很多,不感兴趣的小伙可以直接复制过来,直接编写自定义注解即可。这里小编不带着大家运行了,大家下载源码去运行一下吧。
注解使用方法
小编的源码几乎包含项目开发中多数情况的参数验证。
- 验证参数是集合、数组、Map、实体
需要在参数前面加@MonkeyModel
表示此参数为以上四种类型,在对接实体属性上添加相应注解即可。
接口代码如下:(接口上注解不生效,规范下我加上了)
/**
* ======== 下面是自定注解使用演示 =========
**/
void monkeyValidator(@MonkeyModel MonkeyValidatorModel model, @MonkeyNotBlank String param);
实现类代码如下:
别忘了在实现类上加@MonkeyValidator
注解,否则不生效
/**
* ======== 下面是自定注解使用演示 =========
**/
@Override
public void monkeyValidator(@MonkeyModel MonkeyValidatorModel model, @MonkeyNotBlank String param) {
}
需要验证的Model
代码如下:
/**
* @Author: LailaiMonkey
* @Description:
* @Date:Created in 2020-04-18 16:09
* @Modified By:
*/
@Data
public class MonkeyValidatorModel {
@MonkeyNotBlank
private String name;
@MonkeySize(min = 3, max = 6)
private List<String> friendNames;
@MonkeyMin(value = 18)
@MonkeyMax(value = 60)
private Integer age;
@MonkeyTel
private String tel;
@MonkeyEmail
private String email;
}
- 以上是基本的使用方法还有一个特殊情况
例如:年龄为18我才对手机号进行验证,否则不需要验证手机号
Model
需要改为如下:
/**
* @Author: LailaiMonkey
* @Description:
* @Date:Created in 2020-04-18 16:09
* @Modified By:
*/
@Data
public class MonkeyValidatorModel {
@MonkeyMin(value = 18)
@MonkeyMax(value = 60)
private Integer age;
@MonkeyValidatorOnCondition("age != null && age == 18")
@MonkeyTel
private String tel;
}
@MonkeyValidatorOnCondition
只有满足表达式的值,此属性下所有注解生效,否则不生效。
分组验证
- 那怎么进行分组验证呢?(添加需要验证,更新不需要验证)
只需要变换一下Model
的写法即可完成
Model
代码如下:
/**
* @Author: LailaiMonkey
* @Description:
* @Date:Created in 2020-04-18 16:09
* @Modified By:
*/
public class MonkeyValidatorModel {
interface Common {
String getName();
void setName(String name);
}
class Create implements Common {
@MonkeyNotBlank
private String name;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
}
参数使用MonkeyValidatorModel.Create
即可实现分组验证
- 项目目录
自定义异常就不说了,注解会了异常也没问题了。impl是所有注解的实现。
总结
需要自定义注解只需三步:
- 撰写自定义注解
- 撰写自定义注解实现类
- 在
MonkeyValidatorConfiguration
配置类中以Bean
方法注入该注解
优点:灵活,扩展性强、简单
缺点:如果底层校验可能会有问题
GitHub源码地址:https://github.com/Lailaimonkey/MonkeyValidator.git
CSDN源码地址:https://download.csdn.net/download/h273979586/12338106
如果有哪个注解失效或哪种情况不能验证希望小伙伴在下面留言,小编第一时间回复。