剑客
关注科技互联网

Spring AOP完成一个简单的参数统一校验框架

版权声明:此文章转载自_infocool

原文链接:http://www.infocool.net/kb/Java/201610/200829.html

如需转载请联系听云College团队成员小尹 邮箱:yinhy#tingyun.com

最近刚刚学习了Spring AOP,也是首次使用Spring AOP进行项目开发,尝试写了一个简单的参数校验框架,也许对像我一样新接触spring AOP的童鞋有所参考,故此分享,若有不合理的地方,请大神帮忙指正,非常感谢!

搭建Spring开发环境这里就不详细说明了,除了spring的一些核心包,再引入spring-aop.jar即可。

我想要做的效果是:在所有的Controller方法中,通过注解开关定义是否校验参数,并且可配置参数实体中各个元素的校验规则。

  1. 定义参数校验的开关

在controller方法中,可能有些参数需要校验,也有些参数不需要校验,所以定义了一个参数校验开关。我使用的是一个注解,Valid注解定义如下:

package com.test.constant.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Valid {
}
    Valid只是一个简单的注解,注意它的ElementType我主要用在PARAMETER上,因为这样才可以选择性地去定义哪个参数需要做校验。
    @Valid注解在controller中方法使用如下:
/**
 * 功能: 修改问题
 * @param form
 * @param servletRequest
 * @param servletResponse
 * @return
 * @throws Exception
 */
@ResponseBody
@RequestMapping("/work/disProblemController/update")
public ResponseInfo<String> updateProblem(@Valid UpdateProblemForm form,HttpServletRequest servletRequest,
HttpServletResponse servletResponse) throws Exception {
ResponseInfo<String> responseInfo = new ResponseInfo<String>();
try {
problemService.updateProblem(form);
} catch (Exception e) {
throw new Exception("修改问题异常",e);
}
responseInfo.setRtnCode(AlmRetConstant.code.APP_RET_SUCCESS_CODE);
responseInfo.setRtnMsg(AlmRetConstant.msg.APP_RET_SUCCESS_CODE);
responseInfo.setSuccess(true);
return responseInfo;
}

2. 定义参数校验规则

Check注解定义参数校验规则,现在只做了几种简单的校验规则,如后续接入其他更复杂的规则,考虑使用正则表达式校验。代码如下:

package com.test.constant.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
/**
 * 是否非空
 */
public boolean notNull() default false;
/**
 * 是否为数值
 */
public boolean numeric() default false;
/**
 * 最大长度
 */
public int maxLen() default -1;
/**
 * 最小长度
 */
public int minLen() default -1;
/**
 * 最小数值
 */
public long minNum() default -999999;
}
    
    @Check注解在参数实体中的使用方式如下(以UpdateProblemForm实体为例):
package com.test.form;
import com.pingan.almcenter.constant.annotation.Check;
/**
 * 修改问题表单
 *
 */
public class UpdateProblemForm {
@Check(notNull=true)
private Integer id; //问题ID
@Check(notNull = true, maxLen = 30)
private String problemName;// 问题名称
@Check(notNull = true, maxLen = 3)
private String problemType;// 问题类型
@Check(notNull = true, maxLen = 3)
private String problemStatus;// 问题状态
@Check(maxLen = 320)
private String problemMsg;// 问题详细信息
@Check(notNull = true, maxLen = 30)
private String bankName;// 所属银行名称
//@Check(notNull=true)
private Integer bankId;// 所属银行id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProblemName() {
return problemName;
}
public void setProblemName(String problemName) {
this.problemName = problemName;
}
public String getProblemType() {
return problemType;
}
public void setProblemType(String problemType) {
this.problemType = problemType;
}
public String getProblemStatus() {
return problemStatus;
}
public void setProblemStatus(String problemStatus) {
this.problemStatus = problemStatus;
}
public String getProblemMsg() {
return problemMsg;
}
public void setProblemMsg(String problemMsg) {
this.problemMsg = problemMsg;
}
public String getBankName() {
return bankName;
}
public void setBankName(String bankName) {
this.bankName = bankName;
}
public Integer getBankId() {
return bankId;
}
public void setBankId(Integer bankId) {
this.bankId = bankId;
}
}

3. 最后是Aspect的主体内容,定义 Around方法并实现参数校验

Aspect类的代码如下:

定义切点。我只想用它来校验controller包中方法的参数,所以我切点切在controller上,切点的定义方式还有很多种,可以考虑其他方式。

package com.test.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class ParameterCheckAspect {
@Autowired
private ParameterCheckOption parameterCheckOption;
// 定义切点
@Pointcut("within(com.pingan.almcenter.controller..*)")
public void check() {
}
/**
 * 切面方法,使用统一异常处理
 * 
 * @param joinPoint
 * @return
 * @throws Throwable
 */
@Around(value = "check()", argNames = "Valid")
public Object checkIsValid(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = null;
// 参数校验,未抛出异常表示验证OK
parameterCheckOption.checkValid(joinPoint);
object = ((ProceedingJoinPoint) joinPoint).proceed();
return object;
}
}
   ParameterCheckOption处理类代码如下:
package com.test.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.pingan.almcenter.constant.BaseComponent;
import com.pingan.almcenter.constant.annotation.Check;
import com.pingan.almcenter.constant.annotation.Valid;
import com.pingan.almcenter.constant.exception.ParameterException;
@Component
public class ParameterCheckOption extends BaseComponent{
public void checkValid(ProceedingJoinPoint joinPoint) throws Exception{
Object[] args = null;
Method method = null;
Object target = null;
String methodName = null;
String str = "";
try {
methodName = joinPoint.getSignature().getName();
target = joinPoint.getTarget();
method = getMethodByClassAndName(target.getClass(), methodName);
Annotation[][] annotations = method.getParameterAnnotations();
args = joinPoint.getArgs(); // 方法的参数
if (annotations != null) {
for (int i = 0; i < annotations.length; i++) {
Annotation[] anno = annotations[i];
for (int j = 0; j < anno.length; j++) {
if (annotations[i][j].annotationType().equals(
Valid.class)) {
str = checkParam(args[i]);
if (StringUtils.hasText(str)) {
throw new ParameterException(str);
}
}
}
}
}
} catch (Throwable e) {
logger.error("参数校验异常" + e);
throw new ParameterException(str);
}
}
/**
 * 校验参数
 * 
 * @param args
 * @return
 * @throws Exception
 */
private String checkParam(Object args) throws Exception {
String retStr = "";
Field[] field = args.getClass().getDeclaredFields();// 获取方法参数(实体)的field
for (int j = 0; j < field.length; j++) {
Check check = field[j].getAnnotation(Check.class);// 获取方法参数(实体)的field上的注解Check
if (check != null) {
String str = validateFiled(check, field[j], args);
if (StringUtils.hasText(str)) {
retStr = str;
return retStr;
}
}
}
return retStr;
}
/**
 * 校验参数规则
 * 
 * @param check
 * @param field
 * @param name
 * @return
 * @throws Exception
 */
public String validateFiled(Check check, Field field, Object args)
throws Exception {
field.setAccessible(true);
// 获取field长度
int length = 0;
if (field.get(args) != null) {
length = (String.valueOf(field.get(args))).length();
}
if (check.notNull()) {
if (field.get(args) == null
|| "".equals(String.valueOf(field.get(args)))) {
return field.getName() + "不能为空";
}
}
if (check.maxLen() > 0 && (length > check.maxLen())) {
return field.getName() + "长度不能大于" + check.maxLen();
}
if (check.minLen() > 0 && (length < check.minLen())) {
return field.getName() + "长度不能小于" + check.minLen();
}
if (check.numeric() && field.get(args) != null) {
try {
new BigDecimal(String.valueOf(field.get(args)));
} catch (Exception e) {
return field.getName() + "必须为数值型";
}
}
if (check.minNum() != -999999) {
try {
long fieldValue = Long
.parseLong(String.valueOf(field.get(args)));
if (fieldValue < check.minNum()) {
return field.getName() + "必须不小于" + check.minNum();
}
} catch (Exception e) {
return field.getName() + "必须为数值型,且不小于" + check.minNum();
}
}
return "";
}
/**
 * 根据类和方法名得到方法
 */
@SuppressWarnings("rawtypes")
public Method getMethodByClassAndName(Class c, String methodName)
throws Exception {
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
return method;
}
}
return null;
}
}

由于框架使用了另一个统一异常处理小框架,所以所有的异常都没处理,而是直接抛出,由统一异常处理类去处理。单独用可以自主处理异常。

终上所述,Spring AOP进行参数统一校验的功能就实现了。由于是初接触AOP,所以写得比较粗糙,若有不妥之处,请大牛们批评指正,

想阅读更多技术文章,请访问听云技术博客,访问听云官方网站感受更多应用性能优化魔力。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址