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

来源:互联网 发布:画面好的大型网络手游 编辑:程序博客网 时间:2024/06/11 22:58

      最近刚刚学习了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;// 所属银行idpublic 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@Aspectpublic class ParameterCheckAspect {@Autowiredprivate 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;// 参数校验,未抛出异常表示验证OKparameterCheckOption.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;@Componentpublic 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();// 获取方法参数(实体)的fieldfor (int j = 0; j < field.length; j++) {Check check = field[j].getAnnotation(Check.class);// 获取方法参数(实体)的field上的注解Checkif (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,所以写得比较粗糙,若有不妥之处,请大牛们批评指正,谢谢。







0 0
原创粉丝点击