項目中遇到一個需求,保存醫生信息時,執業范圍在醫師編號為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_doctor")
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 zwdm;
@Length(max = 30,message = "證書編碼不能超過30位",groups = {InsertGroup.class})
@ConditionalNotEmpty(groupIdentify = "1",message = "當職務代碼前綴為 aa及前綴為 bb的編碼時,證書編碼必填",operation = {ValidateConstants.INSERT},conditionFiledName = "zwdm",contionString = "aa,bb")
private String zsbm;
}
3、切面類中獲取注解並進行判斷
package mypackage;
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.*;
/**
* @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: 分組校驗屬性不能同時為空
* @param
* @return
*/
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: 校驗當某屬性屬於特定值時,該注解字段校驗非空
* @param
* @return
*/
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;
}
}
//
import java.lang.annotation.*;
/**
*
* @version 2018-09-29
* @desc 與Validated功能相仿只是把錯誤結果在aop中直接處理
*/
@Target({ ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomValidated {
Class<?>[] value() default {};
//業務操作標識 jiaor
String[] operation() default {};
}