通过自定义注解,AOP,反射,group分组编写适用于[*Controller,*RemoteImpl,*ServiceImpl]通用性手动调用validate方法
拓展方式:该通用方法可扩展性,通用性比较好
1.支持对象多层继承,以及在对象中定义List<> 对象。
2.该效验方法适用于Controller,Remote,Service层级,因为分布式项目远程调用,都是调用接口,所以使用这种方式,可以大大缩短开发时间。
1,创建适用于validate分组类
public class Group {
public static interface Page{
}
public static interface Add{
}
public static interface Edit{
}
public static interface Del{
}
public static interface Detail{
}
public static interface Approve{
}
}
2.创建注解Annotation类
import java.lang.annotation.*;
/**
* @AnnotationName PropertyValidate
* @Description TODO
* @Author liyahui
* @Date 2021-1-18 11:17
* @Version 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface PropertyValidate {
// 入参对象中的一对多List<T> 属性名称
String[] values() default {};
// 方法入参对象
Class<?> paramClass() default Object.class;
// validate分组数据
Class<?>[] groups() default {};
}
3.创建父类ArticleMaster,子类MovieDto extends ArticleMaster,在创建一个TestClass 类定义在Dto中List类型;
import com.example.springtest.entity.ArticleMaster;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import java.io.Serializable;
import java.util.List;
@Data
public class MovieDto extends ArticleMaster implements Serializable {
@NotBlank(message = "创建人名称不能为空")
private String createName;
private String createId;
@NotEmpty(groups = {Group.Add.class, Group.Edit.class}, message = "集合对象不能为空")
private List<TestClass> testClasses;
}
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* <p> TableName : t_article_master </p >
* @author {Hubery}
* CopyRight Copyright 1999-2019 {www.baidu.com} Inc. All rights reserved.
* @date 2020-11-30 16:20:29
*/
@Data
public class ArticleMaster implements Serializable {
/**
* <pre><b>column</b> : id
* <br><b>comment</b> : 主键
* <br><b>type</b> : VARCHAR( 32 )
* 允许空: ( false ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private String id;
/**
* <pre><b>column</b> : link_id
* <br><b>comment</b> : 电影id
* <br><b>type</b> : VARCHAR( 32 )
* 允许空: ( true ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private String linkId;
/**
* <pre><b>column</b> : directors
* <br><b>comment</b> : 导演
* <br><b>type</b> : VARCHAR( 100 )
* 允许空: ( true ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private String directors;
/**
* <pre><b>column</b> : title
* <br><b>comment</b> : 标题
* <br><b>type</b> : VARCHAR( 100 )
* 允许空: ( true ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private String title;
/**
* <pre><b>column</b> : cover
* <br><b>comment</b> : 封面
* <br><b>type</b> : VARCHAR( 100 )
* 允许空: ( true ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private String cover;
/**
* <pre><b>column</b> : rate
* <br><b>comment</b> : 评分
* <br><b>type</b> : VARCHAR( 100 )
* 允许空: ( true ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private String rate;
/**
* <pre><b>column</b> : casts
* <br><b>comment</b> : 演员
* <br><b>type</b> : VARCHAR( 100 )
* 允许空: ( true ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private String casts;
/**
* <pre><b>column</b> : gmt_create
* <br><b>comment</b> : 创建时间
* <br><b>type</b> : TIMESTAMP( 19 )
* 允许空: ( true ) 缺省值: ( null )
*
* @version 2020-11-30 16:20:29
*/
private Date gmtCreate;
private static final long serialVersionUID = 1L;
}
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @ClassName TestClass
* @Description TODO
* @Author liyahui
* @Date 2021-1-18 11:23
* @Version 1.0
*/
@Data
public class TestClass {
@NotNull(groups = {Group.Add.class, Group.Edit.class}, message = "a不能为空")
private Long a;
@NotNull(groups = {Group.Add.class, Group.Edit.class}, message = "b不能为空")
private Long b;
}
4.通过SpringAop 前置切面,反射,validate 进行效验
import com.alibaba.fastjson.JSON;
import com.localhost.common.code.CodeEnum;
import com.localhost.common.exception.BusinessException;
import com.localhost.common.utils.Ognl;
import com.localhost.common.vo.PropertyValidate;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @ClassName PropertyValidateFilter
* @Description 该AOP拦截器主要作用对加入@PropertyValidate注解[Controller,remoteImpl,ServiceImpl]层级的方法中,效验入参对象的添加了validate内置注解字段
* @Author liyahui
* @Date 2021-1-15 16:33
* @Version 1.0
*/
@Aspect
@Component
public class PropertyValidateFilter {
@Resource
protected Validator globalValidator;
@Pointcut(value = "@annotation(com.localhost.common.vo.PropertyValidate)")
public void validatePointCut() {
}
@Before("validatePointCut()")
public <T extends Serializable> void beforePropertyValidate(JoinPoint point) {
MethodSignature sign = (MethodSignature) point.getSignature();
Method method = sign.getMethod();
PropertyValidate annotation = method.getAnnotation(PropertyValidate.class);
String[] nameSpaces = annotation.values();
Class<?> aClass = annotation.paramClass();
Class<?>[] groups = annotation.groups();
StringJoiner joiner = new StringJoiner("");
if (point.getArgs() != null && point.getArgs().length > 0) {
String data = JSON.toJSONString(point.getArgs()[0]);
joiner.add(data);
}
// TODO: 2021-1-18 转换为入参对象实体
T t = (T) JSON.parseObject(String.valueOf(joiner), aClass);
// TODO: 2021-1-18 想对入参实体对象进行validate效验
propertyValidate(t, groups);
Stream.of(nameSpaces).forEach(n -> {
Field field = null;
try {
field = t.getClass().getDeclaredField(n);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
Type t1 = field.getGenericType();
ParameterizedType pt = (ParameterizedType) t1;
// TODO: 2021-1-18 得到对象list中实例的类型
Class c = (Class) pt.getActualTypeArguments()[0];
// TODO: 2021-1-18 获取其中的集合对象集合
List list = getObjectList(t, c);
if (Ognl.isNotEmpty(list)) {
list.stream().forEach(v -> {
// TODO: 2021-1-18 循环遍历手动调用validate方法。
propertyValidate(v, groups);
});
}
});
}
/**
* 反射获取对象中的list<C>类型数据
*
* @param t 入参实体类Class
* @param c 入参实体中的List属性变量类的Class
* @param <T>
*/
public static <C, T> List<C> getObjectList(T t, C c) {
List<C> resultList = new ArrayList<>();
if (!ObjectUtils.isEmpty(t)) {
Field[] fields = getAllFields(t.getClass());
Field[] filterList = filterField(fields);
Arrays.stream(filterList).forEach(var -> {
// TODO: 2021-1-18 List集合
if (List.class.isAssignableFrom(var.getType())) {
Type type = var.getGenericType();
if (type instanceof ParameterizedType) {
if (!var.isAccessible()) {
// TODO: 2021-1-18 给与访问私有属性的权限
var.setAccessible(true);
}
// TODO: 2021-1-18 获取到属性值的字节码
try {
Class<?> clzz = var.get(t).getClass();
// TODO: 2021-1-18 反射调用获取到list的size方法来获取到集合的大小
Method sizeMethod = clzz.getDeclaredMethod("size");
if (!sizeMethod.isAccessible()) {
// TODO: 2021-1-18 给与访问私有属性的权限
sizeMethod.setAccessible(true);
}
// TODO: 2021-1-18 集合长度
int size = (int) sizeMethod.invoke(var.get(t));
// TODO: 2021-1-18 循环遍历获取到数据
for (int i = 0; i < size; i++) {
// TODO: 2021-1-18 反射获取到list的get方法
Method getMethod = clzz.getDeclaredMethod("get", int.class);
// TODO: 2021-1-18 调用get方法获取数据
if (!getMethod.isAccessible()) {
getMethod.setAccessible(true);
}
C var1 = (C) getMethod.invoke(var.get(t), i);
resultList.add(var1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
return resultList;
}
/**
* 反射获取所有的字段
*
* @param c
* @return
*/
public static Field[] getAllFields(Class c) {
List<Field> fieldList = new ArrayList<>();
while (c != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(c.getDeclaredFields())));
c = c.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
/**
* 过滤字段
*
* @param
* @return
*/
public static Field[] filterField(Field[] fields) {
// TODO: 2021-1-18 效验字段不能被 final , static , Abstract 修饰
List<Field> tempList = Arrays.stream(fields).filter(field -> null != field
&& !Modifier.isFinal(field.getModifiers())
&& !Modifier.isStatic(field.getModifiers())
&& !Modifier.isAbstract(field.getModifiers())).collect(Collectors.toList());
// TODO: 2021-1-18 根据上面被filter过滤后的集合是否为空 ? 1 : List.size();
int arrLength = CollectionUtils.isEmpty(tempList) ? 1 : tempList.size();
Field[] resultArr = new Field[arrLength];
if (!CollectionUtils.isEmpty(tempList)) {
tempList.toArray(resultArr);
}
return resultArr;
}
/**
* 手动根据分组调用validate方法
*
* @param t 实体对象
* @param groups 分组效验
* @return
*/
private <T> void propertyValidate(T t, Class<?>... groups) {
Set<ConstraintViolation<T>> set = globalValidator.validate(t, groups);
for (ConstraintViolation<T> constraintViolation : set) {
System.out.println(constraintViolation.getMessage());
throw new BusinessException(CodeEnum.FAIL.getCode(), constraintViolation.getMessage());
}
}
}
5.使用方法,只需要在需要做效验的层级类头加入,对应入参属性
@Override
@PropertyValidate(values = "testClasses", paramClass = MovieDto.class, groups = {Group.Add.class, Group.Edit.class})
public ResponseData<Integer> save(MovieDto movieDto) {
// TODO Auto-generated method stub
movieDto.setId(generatorUuid());
movieDto.setCreateId(generatorUuid());
movieDto.setCreateName("Hubery");
return ResponseData.success(articleMasterMapper.save(movieDto));
}
测试数据:
{
"casts": "asdasdasd",
"cover": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p485887754.jpg",
"createId": "1236452145",
"createName": "Hubery",
"directors": "[\"彼特·道格特\",\"鲍勃·彼德森\"]",
"linkId": "232355123",
"rate": "9.0",
"testClasses": [
{
"a": 1,
"b": 2
},
{
"a": 1,
"b": null
}
],
"title": "飞屋环游记"
}