通过自定义注解,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": "飞屋环游记"
}