项目中遇到一个需求,保存医生信息时,执业范围在医师编号为23开头时为必填项,其他医师编号时,执业范围为非必填项。当然这样的需求可以使用简单的if判断来解决,但是最近学习了注解的使用,刚才此需求可以用到,基本思路如下:
1、创建有条件判断字段为空的注解 ConditionalNotEmpty
2、在医生实体类--》执业范围字段上添加 ConditionalNotEmpty,并给出相应条件
3、切面类中使用java反射机制拿到注解信息,并根据传入的条件动态判断配置了该注解的字段是否需要校验空值
以下是大致代码:
1、注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConditionalNotEmpty {
//分组标识
String groupIdentify() default "";
//错误提示信息
String message();
//业务操作标识
String[] operation() default {};
//判断依据的字段名称,如医师编号
String conditionFiledName() default "";
//判断条件,如本例中为医师编号起始值为23或者25,那么配置23,25
String contionString() default "";
}
2、医生信息实体中,在执业范围字段配置该注解
@Data
@Table(name = "t_ins_staff")
public class Doctor implements Serializable{
private static final long serialVersionUID = -7355208328761399457L;
@Id
private String id;
@NotEmpty(message = "专业技术职务代码不能为空",groups = {InsertGroup.class})
@Length(max = 3,message = "专业技术职务代码不能超过3位",groups = {InsertGroup.class})
private String zyjszwdm;
@Length(max = 30,message = "执业证书编码不能超过30位",groups = {InsertGroup.class})
@ConditionalNotEmpty(groupIdentify = "1",message = "当专业技术职务代码前缀为 23(医师)及前缀为 25(护理)的编码时,执业证书编码必填",operation = {ValidateConstants.INSERT},conditionFiledName = "zyjszwdm",contionString = "23,25")
private String zyzsbm;
}
3、切面类中获取注解并进行判断
package com.xikang.common.validator.aop;
import com.xikang.common.exception.business.BusinessException;
import com.xikang.common.util.UUIDUtils;
import com.xikang.common.validator.annotation.ConditionalNotEmpty;
import com.xikang.common.validator.annotation.CustomValidated;
import com.xikang.common.validator.annotation.GroupNotEmpty;
import com.xikang.common.validator.annotation.ValidateObj;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author jiaorui
* @version 2018-09-29
* @desc 校验结果处理
*/
@Aspect
@Component
public class CustomValidatedAop {
@Autowired
protected Validator validator;
/**
* @desc 判断是否存在@CustomValidated注解
*/
@Before("@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping) || @annotation(org.springframework.web.bind.annotation.PutMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping )|| @annotation(org.springframework.web.bind.annotation.RequestMapping )")
public void doBefore(JoinPoint joinPoint) throws IOException {
//切入点方法参数
Object[] params = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
//获取方法参数类型
Class<?>[] paramsType = method.getParameterTypes();
Set<String> errorMessages = new HashSet<>();
//获取方法参数上的注解(因为方法可以有多参数;参数上可以有注解,返回二维数组)
Annotation[][] an = method.getParameterAnnotations();
int index = 0;
//循环参数
for (Annotation[] an1 : an) {
Object param = params[index];
//循环参数上的注解
for (Annotation an2 : an1) {
//有自定义校验注解
if (an2 instanceof CustomValidated) {
//参数是List
if (param instanceof List) {
List list = (List) param;
for (Object l : list) {
errorMessages.addAll(this.beanValidator(l, ((CustomValidated) an2).value()));
}
} else {//参数非list
//获取该参数类型
Class clazz = paramsType[index];
//反射所有字段
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
ValidateObj validateObj = field.getAnnotation(ValidateObj.class);
//如果字段上有自定义注解@ValidateObj
if(validateObj != null){
field.setAccessible(true);
try {
Object obj = field.get(param);
errorMessages.addAll(this.beanValidator(obj, ((CustomValidated) an2).value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
errorMessages.addAll(this.beanValidator(param, ((CustomValidated) an2).value()));
}
//执行分组校验 begin
Set<String> errorMessage = this.groupNotEmpty(param,((CustomValidated) an2).operation());
if(errorMessage != null && errorMessage.size() > 0){
errorMessages.addAll(errorMessage);
}
//执行分组校验 end
//执行有条件动态校验 begin
Set<String> errorMessageCon = this.conditionalNotEmpty(param,((CustomValidated) an2).operation());
if(errorMessageCon != null && errorMessageCon.size() > 0){
errorMessages.addAll(errorMessageCon);
}
//执行有条件动态校验 end
if (errorMessages.size() > 0) {
List rtnStr = new ArrayList<>();
rtnStr.addAll(errorMessages);
throw new BusinessException(this.listToString(rtnStr));
}
}
}
index++;
}
}
/**
* @desc 通过validator进行分组校验返回错误信息
*/
protected List<String> beanValidator(Object object, Class<?>... groups) {
List<String> errorMessages = new ArrayList();
try {
Set constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
return this.extractMessage(constraintViolations);
}
} catch (ConstraintViolationException ex) {
return errorMessages;
}
return errorMessages;
}
/**
* 转换Set<ConstraintViolation>为List<message>
*/
protected List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {
List<String> errorMessages = new ArrayList();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getMessage());
}
return errorMessages;
}
/**
* 工具方法 list转string
*/
protected String listToString(List<String> mList) {
String convertedListStr = "";
if (null != mList && mList.size() > 0) {
String[] mListArray = mList.toArray(new String[mList.size()]);
for (int i = 0; i < mListArray.length; i++) {
if (i < mListArray.length - 1) {
convertedListStr += mListArray[i] + ",";
} else {
convertedListStr += mListArray[i];
}
}
return convertedListStr;
} else {
return "";
}
}
/***
* Description: 分组校验属性不能同时为空
* Date: 2019/3/15 8:56
* @param
* @return
* @author: jiaorui
*/
protected Set<String> groupNotEmpty(Object param,String[] operation){
//本类及其父类的属性集合
List<Field> fieldList = new ArrayList<>() ;
Class tempClass = param.getClass();
//递归获取本身及父类的属性
//当父类不是object并且为null的时候说明到达了最上层的父类(form继承的Entity类).
while (tempClass != null && !tempClass.getName().toLowerCase().equals("java.lang.object")) {
//反射本类所有属性,包括私有属性
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
//得到父类,然后赋给自己
tempClass = tempClass.getSuperclass();
}
// key--groupId value true 都为空 false 不全为空
Map<String, Boolean> isNotEmptyMap = new HashMap<>();
// key--groupId value 所有message
Map<String, Set> emptyMessageMap = new HashMap<>();
for(Field field : fieldList){
GroupNotEmpty groupNotEmpty = field.getAnnotation(GroupNotEmpty.class);
if(groupNotEmpty != null){
//分组标识
String groupId = groupNotEmpty.groupIdentify();
if(StringUtils.isBlank(groupId)) {
groupId = UUIDUtils.generateUuid();
}
//操作标识
String[] operations = groupNotEmpty.operation();
//判断是否匹配controller中的校验操作
boolean isValidat = this.checkArray(operations,operation);
//不匹配跳出本次循环
if(isValidat){
continue;
}
//错误提示
String message = groupNotEmpty.message();
field.setAccessible(true);
try {
//带GroupNotEmpty注解的字段的值
Object obj = field.get(param);
boolean isNull = this.isNull(obj);
if (isNull) {
// 1. 为空则
if (isNotEmptyMap.containsKey(groupId)) {
emptyMessageMap.get(groupId).add(message);
} else {
isNotEmptyMap.put(groupId, true);
Set set = new HashSet();
set.add(message);
emptyMessageMap.put(groupId, set);
}
} else {
// 2. 不为空
isNotEmptyMap.put(groupId, false);
emptyMessageMap.put(groupId, new HashSet());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
Set returnSet = new HashSet();
for(String groupId: isNotEmptyMap.keySet()) {
if (isNotEmptyMap.get(groupId)) {
returnSet.addAll(emptyMessageMap.get(groupId));
}
}
return returnSet;
}
/***
* Description: 校验当某属性属于特定值时,该注解字段校验非空
* Date: 2020/06/01 8:56
* @param
* @return
* @author: zhangzihui
*/
protected Set<String> conditionalNotEmpty(Object param,String[] operation){
//本类及其父类的属性集合
List<Field> fieldList = new ArrayList<>() ;
Class tempClass = param.getClass();
//递归获取本身及父类的属性
//当父类不是object并且为null的时候说明到达了最上层的父类(form继承的Entity类).
while (tempClass != null && !tempClass.getName().toLowerCase().equals("java.lang.object")) {
//反射本类所有属性,包括私有属性
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
//得到父类,然后赋给自己
tempClass = tempClass.getSuperclass();
}
Set returnSet = new HashSet();
try{
for(Field field : fieldList){
ConditionalNotEmpty conditionalNotEmpty = field.getAnnotation(ConditionalNotEmpty.class);
if(conditionalNotEmpty != null){
String conditionFiledName=conditionalNotEmpty.conditionFiledName();
String contionString=conditionalNotEmpty.contionString();
String[] prefixStrs=null;
if(StringUtils.isNotEmpty(contionString)){
prefixStrs=contionString.split(",");
}
//根据conditionFiledName 的值动态判断当前field是否需要不为空
boolean needCheckEmptyFlag=false;
//获取条件字段的值
Field conditionField = param.getClass().getDeclaredField(conditionFiledName);
conditionField.setAccessible(true);
//该参数为想要获取值得对象
Object conditionFieldValue = conditionField.get(param);
for(String prefixStr:prefixStrs){
if(conditionFieldValue.toString().startsWith(prefixStr)){
//需要校验当前字段不为空
needCheckEmptyFlag=true;
}
}
if(needCheckEmptyFlag){
//操作标识
String[] operations = conditionalNotEmpty.operation();
//判断是否匹配controller中的校验操作
boolean isValidat = this.checkArray(operations,operation);
//不匹配跳出本次循环
if(isValidat){
continue;
}
//错误提示
String message = conditionalNotEmpty.message();
field.setAccessible(true);
//带ConditionalNotEmpty注解的字段的值
Object obj = field.get(param);
boolean isNull = this.isNull(obj);
if (isNull) {
returnSet.add(message);
}
}
}
}
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch(Exception e){
e.printStackTrace();
}
return returnSet;
}
//判空
protected boolean isNull(Object obj){
boolean isNull = false;
//字段是数组类型
if(obj instanceof java.lang.String[]){
String[] array = (String[]) obj;
if(array == null || array.length == 0){
isNull = true;
}
}
//字段是集合类型
else if(obj instanceof java.util.Collection){
Collection c = (Collection) obj;
if(c == null || c.size() == 0){
isNull = true;
}
}
//字段是其他对象类型
else {
if(obj == null){
isNull = true;
}else {
isNull = org.springframework.util.StringUtils.isEmpty(obj);
}
}
return isNull;
}
//判断一个数组中的元素是否在另一个数组中出现
private boolean checkArray(String [] array1,String [] array2){
boolean flag = true;
if((array1 != null && array1.length > 0) && (array2 != null && array2.length > 0)){
for(String s1 : array1){
if(ArrayUtils.contains(array2,s1)){
flag = false;
break;
}
}
}
return flag;
}
}