利用AOP实现基于注解的参数检查

一、问题引入

在用Spring MVC做Web端、APP端的接口时,经常要对接口参数做有效性检查。

@RequestMapping({ "/getVerifyCode" })
@ResponseBody
public Map<String, Object> getVerifyCode(String mobile){
  try{
     //参数检查
     if(StringUtils.isBlank(mobile) && !MobileUtils.isMobileNumber(mobile)){
       throw new IllegalArgumentException("参数错误");
     }
    //此处调用业务代码
     ... ...
  }
  catch(Exception e){
  }
}


上面的参数检查代码在每个接口都是必须要的,但是如果在每个接口都写一遍确实很烦人。

在Spring MVC中有Validator解决方案:

@RequestMapping(value="/add")
@ResponseBody
public AjaxJson add(@Valid InpuInfo inputInfo, Errors errors){
    try {
	if(errors.hasErrors()){
		StringBuilder errorMsg = new StringBuilder();
		List<FieldError> errorsList = errors.getFieldErrors();
		for(FieldError fieldError: errorsList){
			errorMsg.append(fieldError.getDefaultMessage()).append(";");
		}
		logger.error("参数错误:{}", errorMsg.toString());
                throw new IllegalArgumentException("参数错误");

	}
	//调用业务代码
	... ...

    }
    catch(Exception e){
    }
}


 

其中InpuInfo 的校验如下:

@SuppressWarnings("serial")
public class InputPo implements Serializable {

   @Pattern(regexp="[0-2]$", message="性别参数不正确")
   private String gender = "2";
   @Size(max=200, min=3, message="用户名不能少于3大于200个字符")
   private String userName;
   @Pattern(regexp="^((13[0-9])|(145,147)|(15[0-9])|(18[0-9]))\\d{8}$", message="{mobile_isinvalid}")
   private String mobile;
   @Size(max=20, min=6, message="密码不能少于6大于20个字符")
   private String password;
   @Min(value=1)
   private Integer orgId;
   //后面的省略
}
这样是可以实现自动的参数检查,但是有时候我的接口中仅一个参数,如果用Spring的Validator框架,仍然需要写一个业务bean来封装这个参数,显得太麻烦了。

如果能够在参数上也能支持自动注解就好了。比如之前的getVerifyCode接口直接在mobile参数前添加注解。如下:

@RequestMapping(value = "/getVerifyCode")
@ResponseBody
@ParameterCheck
public Map<String, String> getVerifyCode(
	<span style="color:#ff6666;">@Pattern(regexp="^((13[0-9])|(145,147)|(15[0-9])|(18[0-9]))\\d{8}$", message="手机号码不正确") </span>String mobile ,
	Errors error){
	if(errors != null){
	   throw new IllegalArgumentException("参数错误");
	}
        
	//调用业务代码
	... ...

}	

如果能够实现上面的代码,getVerifyCode接口将变得更简洁。下面我来分析下如何实现此功能:

1 注解

      为了实现实现对接口的参数自动检测,必须先引入一个方法级的注解,Spring AOP将对引用此注解的方法进行特殊处理,代码如下

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ParameterCheck {
}

2 AOP拦截器设计

@Component
@Aspect
public class ParameterCheckAopHandler {
        //对有ParameterCheck注解的方法进行拦截
	@Around("@annotation(com.test.ParameterCheck)")  
	public Object parameterCheck(ProceedingJoinPoint joinPoint) throws Throwable{ 
		Object target = joinPoint.getTarget();
		Class<?> clazz = target.getClass();
		//拦截的方法名
                String methodName = joinPoint.getSignature().getName();
		Object[] args = joinPoint.getArgs();
		//通过反射获取对象注解的方法
		Method method = ReflectUtils.getMethod(target.getClass(), methodName);
                //获取该方法在参数上的注解,每个参数可以有多个注解,得到的是一个二维数组
		Annotation[][] parameterAnnotaions = method.getParameterAnnotations();
		
		Errors errors = new Errors();
		for(int i = 0; i < parameterAnnotaions.length; i++){
			Annotation[] oneParameterAnnotaions = parameterAnnotaions[i];
			
			for(int j = 0; j < oneParameterAnnotaions.length; j++){
                                //获取到基于正则表达式上的注解,在实际应用中可能需要根据业务对其他注解也进行相关的校验
				if(oneParameterAnnotaions[j].annotationType() == Pattern.class){
					Pattern pattern = (Pattern)oneParameterAnnotaions[j];
                                        boolean isValid = false;
					if(args[i] != null){
						String regexp = pattern.regexp();
						
						java.util.regex.Pattern patt = java.util.regex.Pattern.compile(regexp);
						
						boolean matched = patt.matcher(args[i].toString()).matches();
						
						if(matched){
						   isValid = true;
						}
					}
                                        //记录字段错误
					if(!isVaild){
					   FieldError objectError = new FieldError("", pattern.message());
					   errors.getErrors().add(objectError);
					}
				}
			}
		}
		//把字段校验错误传个接口的参数
		args[args.length-1] = errors;
		return joinPoint.proceed(args);
	}
}

其中
FieldError定义如下:
public class FieldError {
	private String filed;
	private String errorMsg;
	
	public FieldError(){
	}
	public FieldError(String filed, String errorMsg){
		this.filed = filed;
		this.errorMsg = errorMsg;
	}
	public String getField() {
		return filed;
	}
	public void setFielde(String filed) {
		this.filed = filed;
	}
	public String getErrorMsg() {
		return errorMsg;
	}
	public void setErrorMsg(String errorMsg) {
		this.errorMsg = errorMsg;
	}
}
Erros 定义如下:
public class Errors {
	
	public List<FieldError> errors = new ArrayList<FieldError>();

	public List<FieldError> getErrors() {
		return errors;
	}

	public void setErrors(List<FieldError> errors) {
		this.errors = errors;
	}
}

3 接口参数校验

@RequestMapping(value = "/getVerifyCode")
@ResponseBody
//此处定义AOP拦截注解
@ParameterCheck
public Map<String, String> getVerifyCode(
        //此处直接在参数上利用validation-api中注解,用正则表达式对电话号码进行校验。读者也可以定义自己的注解</span>
        @Pattern(regexp="^((13[0-9])|(145,147)|(15[0-9])|(18[0-9]))\\d{8}$", message="手机号码不正确") String mobile ,
	Errors errors){
    try{
	if(errors != null){
	   throw new IllegalArgumentException("参数错误");
	}
	//调用业务代码
        ... ...
    }
    catch(Exception e){
    }
}

三、小结

   本文利用Spring的AOP技术,实现了基于注解的方式对接口的参数进行自动检测。





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP是Spring框架中的一个重要模块,它提供了面向切面编程(AOP)的支持。AOP是一种编程思想,它可以在不改变原有代码的情况下,通过在程序运行时动态地将代码“织入”到现有代码中,从而实现对原有代码的增强。 Spring AOP提供了基于注解AOP实现,使得开发者可以通过注解的方式来定义切面、切点和通知等相关内容,从而简化了AOP的使用。 下面是一个基于注解AOP实现的例子: 1. 定义切面类 ```java @Aspect @Component public class LogAspect { @Pointcut("@annotation(Log)") public void logPointcut() {} @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { // 前置通知 System.out.println("执行方法:" + joinPoint.getSignature().getName()); } @AfterReturning("logPointcut()") public void afterLog(JoinPoint joinPoint) { // 后置通知 System.out.println("方法执行完成:" + joinPoint.getSignature().getName()); } @AfterThrowing(pointcut = "logPointcut()", throwing = "ex") public void afterThrowingLog(JoinPoint joinPoint, Exception ex) { // 异常通知 System.out.println("方法执行异常:" + joinPoint.getSignature().getName() + ",异常信息:" + ex.getMessage()); } } ``` 2. 定义业务逻辑类 ```java @Service public class UserService { @Log public void addUser(User user) { // 添加用户 System.out.println("添加用户:" + user.getName()); } @Log public void deleteUser(String userId) { // 删除用户 System.out.println("删除用户:" + userId); throw new RuntimeException("删除用户异常"); } } ``` 3. 在配置文件中开启AOP ```xml <aop:aspectj-autoproxy/> <context:component-scan base-package="com.example"/> ``` 在这个例子中,我们定义了一个切面类LogAspect,其中通过@Aspect注解定义了一个切面,通过@Pointcut注解定义了一个切点,通过@Before、@AfterReturning和@AfterThrowing注解分别定义了前置通知、后置通知和异常通知。 在业务逻辑类中,我们通过@Log注解标注了需要增强的方法。 最后,在配置文件中,我们通过<aop:aspectj-autoproxy/>开启了AOP功能,并通过<context:component-scan>扫描了指定包下的所有组件。 这样,当我们调用UserService中的方法时,就会触发LogAspect中定义的通知,从而实现对原有代码的增强。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值