最近公司重构项目,重构为最热的微服务框架 spring boot, 重构的时候遇到几个可以统一处理的问题,也是项目中经常遇到,列如:统一校验参数,统一捕获异常。。。
仅凭代码 去控制参数的校验,有时候是冗余的,但通过框架支持的 去控制参数的校验,是对于开发者很友好,先看下面的例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 @NotEmpty(message="手机号不能为空") 2 @Size(min=11,max=11,message="手机号码长度不正确") 3 @Pattern(regexp=StringUtils.REGEXP_MOBILE,message="手机号格式不正确") 4 private String mobile;
这是spring boot支持的 校验注解,然后我们在 contoller层 加上@Valid 注解 就可以达到校验的目的。这是一种框架自带的
本章 就展示一种 自定义的 AOP 校验,首先 写一个注解,注解里面可以写上 我们需要校验的规则, 比如长度,正则。。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 @Documented 2 @Target({ElementType.FIELD,ElementType.METHOD}) 3 @Retention(RetentionPolicy.RUNTIME) 4 public @interface ValidateParam { 5 6 int min() default 0; 7 8 int max() default Integer.MAX_VALUE; 9 10 String message() default "params is not null"; 11 12 String regexp(); 13 14 Class<?>[] groups() default { }; 15 16 Class<? extends Payload>[] payload() default { }; 17 18 boolean isNotNull() default true; 19 20 }
然后定义一个AOP类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 package com.onecard.primecard.common.aop; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.ParameterizedType; 6 import java.util.ArrayList; 7 import java.util.Arrays; 8 import java.util.regex.Pattern; 9 10 import org.aspectj.lang.JoinPoint; 11 import org.aspectj.lang.ProceedingJoinPoint; 12 import org.aspectj.lang.annotation.Around; 13 import org.aspectj.lang.annotation.Aspect; 14 import org.aspectj.lang.annotation.Before; 15 import org.aspectj.lang.annotation.Pointcut; 16 import org.slf4j.Logger; 17 import org.slf4j.LoggerFactory; 18 import org.springframework.context.ApplicationContext; 19 import org.springframework.context.support.ClassPathXmlApplicationContext; 20 import org.springframework.stereotype.Component; 21 22 import com.jfcf.core.dto.ResultData; 23 import com.onecard.core.support.util.StringUtils; 24 import com.onecard.primecard.common.annotation.ValidateParam; 25 import com.onecard.primecard.common.utils.ResultDataUtil; 26 27 28 29 30 /** 31 * 全局 切面类(校验参数) 32 * 33 * @author Administrator 34 * 35 */ 36 @Aspect 37 @Component 38 public class GobalHandlerAspect { 39 40 private static Logger logger = LoggerFactory.getLogger(GobalHandlerAspect.class); 41 42 @Pointcut("execution(* 包名.controller..*.*(..)) && execution(* 包名.controller..*.*(..))") 43 public void checkAspect(){}; 44 45 @Before("checkAspect()") 46 public void befor(JoinPoint joinPoint) throws Exception{ 47 //前置统一输出参数 48 Object[] args = joinPoint.getArgs(); 49 if(args != null && args.length>0){ 50 Object obj = args[0]; 51 ParameterizedType pt = (ParameterizedType)obj.getClass().getGenericSuperclass(); 52 Class<?> classzz = (Class<?>) pt.getActualTypeArguments()[0]; 53 logger.info("【小X卡】-【请求实体入参】:"+classzz.newInstance().toString()); 54 } 55 56 } 57 58 @Around("checkAspect()") 59 public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ 60 //校验参数 61 Object[] args = joinPoint.getArgs(); 62 Object obj = null; 63 if(args != null && args.length > 0){ 64 obj = args[0]; 65 Class classzz = obj.getClass(); 66 //没有顺序和秩序的数组 67 Field[] fieldArray = classzz.getDeclaredFields(); 68 ArrayList<Field> fieldList = new ArrayList<Field>(Arrays.asList(fieldArray)); 69 String res = checkParam(fieldList,obj); 70 if(StringUtils.isNotNull(res)){ 71 return ResultDataUtil.result(ResultData.STATUS_PARAM_ERROR, res); 72 } 73 } 74 75 return joinPoint.proceed(); 76 } 77 78 private String checkParam(ArrayList<Field> fieldList, Object obj) throws Exception { 79 80 for(Field field : fieldList){ 81 ValidateParam validateParam = field.getAnnotation(ValidateParam.class); 82 logger.info("【小X卡】获取注解值:"+validateParam.isNotNull()+"min="+validateParam.min()+"max="+validateParam.max()); 83 Method method = obj.getClass().getMethod("get"+getMethodName(field.getName())); 84 logger.info("【小X卡】入参实体方法名称:"+method.getName()); 85 if(method != null){ 86 Object val = method.invoke(obj); 87 logger.info("【小x卡】回调方法:"+val); 88 if(validateParam != null && validateParam.isNotNull() == true){ 89 if(null == val || "".equals(val) ){ 90 return field.getName()+"必填参数为空"; 91 } 92 } 93 if(validateParam.min()==11 && validateParam.max() == 11){ 94 if(val.toString().length() != 11){ 95 return field.getName()+"请输入参数正确的长度"; 96 } 97 } 98 if(validateParam.regexp().equals(StringUtils.REGEXP_MOBILE)){ 99 if(!Pattern.matches(StringUtils.REGEXP_MOBILE, val.toString())){ 100 return field.getName()+"参数格式错误"; 101 } 102 } 103 } 104 } 105 return null; 106 } 107 108 109 /** 110 * 方法首字母大写 111 * @param fieldName 112 * @return 113 */ 114 private String getMethodName(String fieldName) { 115 StringBuffer buffer = new StringBuffer(); 116 String firstLetter = fieldName.substring(0, 1).toUpperCase(); 117 return buffer.append(firstLetter).append(fieldName.substring(1, fieldName.length())).toString(); 118 119 } 120 }
定义一个切点 @Pointcut, 用execution 表达式,去获取要校验的 某个类 和某个方法, 也就是连接点,然后 用定义一个通知,上面代码中有2个通知,一个前置通知@Before,一个环绕通知@Around,我们使用功能最强大的环绕通知。
通过上面的代码可以看出 首先获取参数,然后通过反射机制 获取 入参对象中的全部字段, 再去获取 我们在字段中加 我们自定义注解的字段,通过反射方法的回调,获取字段值,对值做判断, 返回校验结果。