action的参数校验,使用注解和拦截器简单实现


项目里面一定会有很多的action,每一个都需要验证参数的合法性,这是个很繁琐的工作,jfinal自带的拦截器功能用着还是觉得麻烦;参考hibernate-validator等现有的框架写了点东西,自我感觉美美的,特地来晒一晒

(内容挺长,不过关键点只有一丢丢)


废话不多说,上代码(我的环境jfinal-java8,版本3.3):

一、首先需要定义自己的注解(参照了现有的很多验证框架)

blob.png

我这儿定义了挺多的,不过有个别的还没有完全实现,呵呵


贴出几个来打个样儿,其他的都类似

1. Length(被注解的字符串的大小必须在指定的范围内)

@Documented
@Repeatable(LengthArray.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface Length {
	
    String value();
    int min();
    int max();
    String message() default "数据校验不合法";

}

2. NotBlank(被注解的字符串必须非null,且长度必须大于0),注意下Repeatable,这个是java1.8才有的

@Documented
@Repeatable(NotBlankArray.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface NotBlank {
    String value();
    String message() default "数据必须有内容";
}

3. NotBlankArray(同2,这个表示同一位置可以出现多个相同的注解)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface NotBlankArray {
    NotBlank[] value();
}

4.(被注释的元素必须符合指定的正则表达式)

@Documented
//@Repeatable(LengthArray.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface Pattern {
    String value();
    String message() default "字段不符合规则";
    String regexp();
}


说明:

    value的值就是getPara("xxx");里面的xxx

    message是校验失败时的提示信息

    其他min,max,regexp等属性按需添加


二、然后编写action

public class TestController extends Controller {

    public void index(){
        renderText("/test/index");
    }
	
    @NotBlank(value="name", message="来者报上名来,空着算毛啊")
    @NotBlank(value="address")
    @Length(value="mobile", max = 11, min = 11, message="手机号码11位你不知道吗")
    //由数字、26个英文字母或下划线组成的字符串
    @Pattern(value="content", regexp="^\\w+$")
    @URL(value="debug", host = "", port = 0, protocol = "")
    public void valid01(){
		
    	String name1 = getPara("name");
    	String address1 = getPara("address");
    	String mobile1 = getPara("mobile");
    	Integer age1 = getParaToInt("age");
    	String content1 = getPara("content");
  	
        String result = "[" + name1 + "/" + address1 + "/" + mobile1 + "/" + content1 + "/" + age1 + "]";
        renderText("/test/valid01    " + result);
    }

    public void valid02(
            @NotBlank(value="")String name, 
            @NotBlank(value="")String address, 
            @Length(value="", max = 11, min = 11)String mobile,
            //由数字、26个英文字母或下划线组成的字符串
            @Pattern(value="", regexp="^\\w+$")String content){

        String name2 = getPara("name");
        String address2 = getPara("address");
        String mobile2 = getPara("mobile");
        Integer age2 = getParaToInt("age");
        String content2 = getPara("content");
		
        String result = "[" + name2 + "/" + address2 + "/" + mobile2 + "/" + content2 + "/" + age2 + "]";
        renderText("/test/valid02    " + result);
    }
}


三、编写拦截器

代码看着挺长,不过都不复杂,前面的四个方法是关键、后面都是各个校验的具体代码,只看其中一两个就明白了,可以自由扩展哈,so easy~~~

public class ParameterValidateInterceptor implements Interceptor {
	
	@Override
	public void intercept(Invocation inv) {

		//处理method的注解
		CheckResult checkResult = validateMethodAnnotation(inv);
		if (!checkResult.result) {
			inv.getController().renderJson( MyResult.build(7295, checkResult.paraName + ": " + checkResult.message) );
			return;
		} 
		
		//处理parameter的注解
		checkResult = validateParameterAnnotation(inv);
		if (!checkResult.result) {
			inv.getController().renderJson( MyResult.build(7297, checkResult.paraName + ": " + checkResult.message) );
			return;
		}

		//method和parameter注解验证都通过之后,才会继续调用action方法
		inv.invoke();
	}
	
	//处理方法上定义的注解
	private CheckResult validateMethodAnnotation(Invocation inv){
		CheckResult checkResult = new CheckResult(true, null, null);
		Annotation[] annotations = inv.getMethod().getDeclaredAnnotations();// .getAnnotations();
		for (Annotation annotation : annotations) {
			checkResult = validateSingleAnnotation(annotation, inv, null, false);
			
			if (!checkResult.result) {
				return checkResult;
			}
		}
		
		return checkResult;//new CheckResult(true, null, null);
	}
	
	//处理参数上定义的注解
	private CheckResult validateParameterAnnotation(Invocation inv){
		CheckResult checkResult = new CheckResult(true, null, null);
		//遍历action方法的所有参数
		Parameter[] parameters = inv.getMethod().getParameters();
		for (Parameter parameter : parameters) {
			String paraName = parameter.getName();
			//String paraValue = inv.getController().getPara(paraName);
			
			//遍历参数的所有注解
			Annotation[] annotations = parameter.getDeclaredAnnotations();
			for (Annotation annotation : annotations) {
				//System.out.println("  check parameter [ "+ paraName + "/" + paraValue + " ], annotition: " + annotation.annotationType().getSimpleName());
				checkResult = validateSingleAnnotation(annotation, inv, paraName, true);

				if (!checkResult.result) {
					return checkResult;
				}
			}
		}
		return checkResult;//new CheckResult(true, null, null);
	}

	/**
	 * 处理每个单独的注解
	 * @param annotation 发现的注解
	 * @param inv
	 * @param paraName	参数名(方法的注解得从value去获取,现在还没有;参数的注解直接就是参数名)
	 * @param type	类型:false说明是方法注解(还没有取到参数名paraName属性),true说明是参数注解(已经拿到了参数名paraName属性)
	 * @return
	 */
	private CheckResult validateSingleAnnotation(Annotation annotation, Invocation inv, String paraName, boolean type){
		CheckResult checkResult = new CheckResult(true, null, "");
		
		if (annotation.annotationType().equals(NotBlank.class)) {
			NotBlank singleAnnotation = (NotBlank) annotation;
			checkResult = notBlankValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(NotBlankArray.class)) {
			NotBlankArray arrayAnnotation = (NotBlankArray) annotation;
			checkResult = notBlankArrayValidator(inv, arrayAnnotation);
			
		} else if (annotation.annotationType().equals(Length.class)) {
			Length singleAnnotation = (Length) annotation;
			checkResult = lengthValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(LengthArray.class)) {
			LengthArray arrayAnnotation = (LengthArray) annotation;
			checkResult = lengthArrayValidator(inv, arrayAnnotation);
			
		} else if (annotation.annotationType().equals(MobileNumber.class)) {
			MobileNumber singleAnnotation = (MobileNumber) annotation;
			checkResult = mobileNumberValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(NotNull.class)) {
			NotNull singleAnnotation = (NotNull) annotation;
			checkResult = notNullValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(Past.class)) {
			Past singleAnnotation = (Past) annotation;
			checkResult = pastValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(Future.class)) {
			Future singleAnnotation = (Future) annotation;
			checkResult = futureValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(Null.class)) {
			Null singleAnnotation = (Null) annotation;
			checkResult = nullValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(AssertTrue.class)) {
			AssertTrue singleAnnotation = (AssertTrue) annotation;
			checkResult = assertTrueValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(AssertFalse.class)) {
			AssertFalse singleAnnotation = (AssertFalse) annotation;
			checkResult = assertFalseValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
		
		} else if (annotation.annotationType().equals(Email.class)) {
			Email singleAnnotation = (Email) annotation;
			checkResult = emailValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(URL.class)) {
			URL singleAnnotation = (URL) annotation;
			checkResult = urlValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else if (annotation.annotationType().equals(Pattern.class)) {
			Pattern singleAnnotation = (Pattern) annotation;
			checkResult = patternValidator(inv, singleAnnotation, type ? paraName : singleAnnotation.value());
			
		} else {
			System.out.println("unknown method annotation ---------------- " + annotation.toString());
		}
		return checkResult;

	}
	

	
	
	
	
	
	
	/**
	 * @Null(value="xxx")
	 * 校验:被注释的元素必须为 null
	 */
	private CheckResult nullValidator(Invocation inv, Null nullAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... Null -- para: " + paraName + ", value: " + paraValue);
		
		//开始校验
		return new CheckResult(paraValue==null, paraName, nullAnnotation.message());
	}
	
	/**
	 * @NotNull(value="xxx")
	 * 校验:string参数xxx的值不能为 null
	 */
	private CheckResult notNullValidator(Invocation inv, NotNull notNullAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... NotNull -- para: " + paraName + ", value: " + paraValue);

		//开始校验
		return new CheckResult(paraValue!=null, paraName, notNullAnnotation.message());
	}

	/**
	 * @AssertTrue(value="xxx")
	 * 校验: boolean参数xxx的值必须为true
	 */
	private CheckResult assertTrueValidator(Invocation inv, AssertTrue assertTrueAnnotation, String paraName){
		Boolean paraValue = inv.getController().getParaToBoolean(paraName);
		System.out.println(" ...... AssertTrue -- para: " + paraName + ", value: " + paraValue);

		//开始校验
		return new CheckResult(paraValue==null||paraValue, paraName, assertTrueAnnotation.message());
	}

	/**
	 * @AssertTrue(value="xxx")
	 * 校验: boolean参数xxx的值必须为false
	 */
	private CheckResult assertFalseValidator(Invocation inv, AssertFalse assertFalseAnnotation, String paraName){
		Boolean paraValue = inv.getController().getParaToBoolean(paraName);
		System.out.println(" ...... AssertFalse -- para: " + paraName + ", value: " + paraValue);

		//开始校验
		return new CheckResult(paraValue==null||!paraValue, paraName, assertFalseAnnotation.message());
	}

	/**
	 * @Past(value="xxx")
	 * 校验: date参数的xxx的值必须是已经过去的时间(jfinal目前只认“yyyy-MM-dd”的格式)
	 */
	private CheckResult pastValidator(Invocation inv, Past pastAnnotation, String paraName){
		Date paraValue = inv.getController().getParaToDate(paraName);
		System.out.println(" ...... Past -- para: " + paraName + ", value: " + paraValue);

		//开始校验
		return new CheckResult(paraValue==null||paraValue.getTime()<new Date().getTime(), paraName, pastAnnotation.message());
	}

	/**
	 * @Future(value="xxx")
	 * 校验: date参数的xxx的值必须是将来的时间(jfinal目前只认“yyyy-MM-dd”的格式)
	 */
	private CheckResult futureValidator(Invocation inv, Future futureAnnotation, String paraName){
		Date paraValue = inv.getController().getParaToDate(paraName);
		System.out.println(" ...... Future -- para: " + paraName + ", value: " + paraValue);

		//开始校验
		return new CheckResult(paraValue==null||paraValue.getTime()>new Date().getTime(), paraName, futureAnnotation.message());
	}

	
	/**
	 * @Pattern(value="xxx", message="...", regexp="......")
	 * 校验: string参数xxx的值必须能匹配正则表达式regexp(但可以为null或空串)
	 */
	private CheckResult patternValidator(Invocation inv, Pattern patternAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... Pattern -- para: " + paraName + ", value: " + paraValue);
		
		//开始校验
		if (StrKit.isBlank(paraValue)) {
			return new CheckResult(true, null, null);
		}
		
		java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(patternAnnotation.regexp());
		Matcher matcher = pattern.matcher(paraValue);
		return new CheckResult(matcher.matches(), paraName, patternAnnotation.message());
	}

	/**
	 * @Email(value="xxx", message="...")
	 * 校验: string参数xxx的值必须是合法的email地址,还未完成!!!
	 */
	private CheckResult emailValidator(Invocation inv, Email emailAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... Pattern -- para: " + paraName + ", value: " + paraValue);
		
		//开始校验,参考【https://github.com/hibernate/hibernate-validator/blob/ff4324e6c182992c74110bfb51021a2bfee39d5e/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/bv/EmailValidator.java】
		if (StrKit.isBlank(paraValue)) {
			return new CheckResult(true, null, null);
		}

		String regexp = "\\w+@\\w+(\\.\\w+)+";
		java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regexp);
		Matcher matcher = pattern.matcher(paraValue);
		return new CheckResult(matcher.matches(), paraName, emailAnnotation.message());
	}

	/**
	 * @Length(value="xxx", min=mm, max=nn, message="...")
	 * 校验: string参数xxx的值长度必须在min和max的范围内
	 */
	private CheckResult lengthValidator(Invocation inv, Length lengthAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... Length -- para: " + paraName + ", value: " + paraValue);
		
		//开始校验
		int min = lengthAnnotation.min();
		int max = lengthAnnotation.max();
		return new CheckResult(paraValue!=null&&(paraValue.length()>=min&&paraValue.length()<=max), paraName, lengthAnnotation.message());
	}
	/**
	 * @Length(value="xxx",...)
	 * @Length(value="yyy",...)
	 * 同一位置有多个@Length注解
	 */
	private CheckResult lengthArrayValidator(Invocation inv, LengthArray lengthArrayAnnotation){
		for (Length lengthAnnotation : lengthArrayAnnotation.value()) {
			CheckResult checkResult = lengthValidator(inv, lengthAnnotation, lengthAnnotation.value());
			if(!checkResult.result){
				return checkResult;
			}
		}
		return new CheckResult(true, null, null);
	}
	
	/**
	 * @NotBlank(value="xxx", message="...")
	 * 校验: string参数xxx的值不能为null,且长度必须大于0
	 */
	private CheckResult notBlankValidator(Invocation inv, NotBlank notBlankAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... NotBlank -- para: " + paraName + ", value: " + paraValue);
		
		//开始校验
		return new CheckResult(paraValue!=null && paraValue.trim().length() > 0, paraName, notBlankAnnotation.message());
	}
	/**
	 * @NotBlank(value="xxx")
	 * @NotBlank(value="yyy")
	 * 同一位置有多个@NotBlank注解
	 */
	private CheckResult notBlankArrayValidator(Invocation inv, NotBlankArray notBlankArrayAnnotation){
		for (NotBlank notBlankAnnotation : notBlankArrayAnnotation.value()) {
			CheckResult checkResult = notBlankValidator(inv, notBlankAnnotation, notBlankAnnotation.value());
			if(!checkResult.result){
				return checkResult;
			}
		}
		return new CheckResult(true, null, null);
	}
	
	/**
	 * @URL(value="xxx", protocol="aaa", host="bbb", port=zzz, message="")
	 * 校验:string参数xxx必须是一个有效的url,还未完成
	 */
	private CheckResult urlValidator(Invocation inv, URL urlAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... URL -- para: " + paraName + ", value: " + paraValue);

		//开始校验
		if (StrKit.isBlank(paraValue)) {
			return new CheckResult(true, paraName, null);
		}

		String regexp = "^(?:https?://)?[\\w]{1,}(?:\\.?[\\w]{1,})+[\\w-_/?&=#%:]*$";
		java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regexp);
		Matcher matcher = pattern.matcher(paraValue);
		return new CheckResult(matcher.matches(), paraName, urlAnnotation.message());
		//下面这个方法复杂些,可以验证协议、主机、端口,等等
//		CheckResult checkResultSuccess = new CheckResult(true, paraName, null);
//		CheckResult checkResultFail = new CheckResult(false, paraName, urlAnnotation.message());
//		if ( paraValue == null || paraValue.length() == 0 ) {
//			return checkResultSuccess;
//		}
//		//^(?:https?://)?[\\w]{1,}(?:\\.?[\\w]{1,})+[\\w-_/?&=#%:]*$
//		java.net.URL url;
//		try {
//			url = new java.net.URL( paraValue );
//		}
//		catch (MalformedURLException e) {
//			return checkResultFail;
//		}
//		if ( urlAnnotation.protocol() != null && urlAnnotation.protocol().length() > 0 && !url.getProtocol().equals( urlAnnotation.protocol() ) ) {
//			return checkResultFail;
//		}
//		if ( urlAnnotation.host() != null && urlAnnotation.host().length() > 0 && !url.getHost().equals( urlAnnotation.host() ) ) {
//			return checkResultFail;
//		}
//		if ( urlAnnotation.port() != -1 && url.getPort() != urlAnnotation.port() ) {
//			return checkResultFail;
//		}
//		return checkResultSuccess;
	}

	/**
	 * @MobileNumber(value="xxx")
	 * 验证: string参数xxx的值必须是合法的手机号码
	 */
	private CheckResult mobileNumberValidator(Invocation inv, MobileNumber mobileNumberAnnotation, String paraName){
		String paraValue = inv.getController().getPara(paraName);
		System.out.println(" ...... MobileNumber -- para: " + paraName + ", value: " + paraValue);

		//开始校验
		if (StrKit.isBlank(paraValue)) {
			return new CheckResult(true, paraName, null);
		}

		String regexp = "^((17[0-9])|(14[0-9])|(13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$";
		java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regexp);
		Matcher matcher = pattern.matcher(paraValue);
		return new CheckResult(matcher.matches(), paraName, mobileNumberAnnotation.message());
	}

	
	
	
	
	
	
	
	class CheckResult {
		private boolean result;
		private String paraName;
		private String message;
		
		public CheckResult(boolean result, String paraName, String message) {
			super();
			this.result = result;
			this.paraName = paraName;
			this.message = message;
		}
	}

}

还有个MyResult,也贴出来吧

public class MyResult {
	
	private Integer status;
	private String message;
	private Integer totalPage;
	private Integer totalNum;
	private Object data;
	
	private static final Integer SUCCESS_CODE = 0;
	private static final String SUCCESS_MSG = "success";
	
	// get,set 略
	
	@Override
	public String toString() {
		return "MyCustomResult [status=" + status + ", message=" + message + ", totalPage=" + totalPage + ", totalNum="
				+ totalNum + ", data=" + data + "]";
	}

	// 构造方法
	public MyResult(){
		
	}
	
	public MyResult(Integer status, String message, Object data) {
		this.status = status;
		this.message = message;
		if (data instanceof Page) {
			this.data = ((Page<?>) data).getList();
			setTotalNum(((Page<?>) data).getTotalRow());
			setTotalPage(((Page<?>) data).getTotalPage());
		} else {
			this.data = data;
		}
	}
	
	// 静态方法
	public static MyResult build(Integer status, String message, Object data){
		return new MyResult(status, message, data);
	}
	
	public static MyResult build(Integer status, String message){
		return new MyResult(status, message, null);
	}
	
	public static MyResult ok(Object data){
		return new MyResult(SUCCESS_CODE, SUCCESS_MSG, data);
	}
	
	public static MyResult ok(){
		return new MyResult(SUCCESS_CODE, SUCCESS_MSG, null);
	}
	
}


四、配置路由和拦截器

	@Override
	public void configRoute(Routes me) {
		me.add("test", TestController.class);
	}
	@Override
	public void configInterceptor(Interceptors me) {
		me.addGlobalActionInterceptor(new ParameterValidateInterceptor());
	}


五、测试喽~~~

我用的是Postman,贴图

blob.png


六、谢谢~~~

评论区

JFinal

2018-03-15 18:28

使用注解实现 validator 功能,确实有不少用户曾经提过这个需求,感谢你的分享,jfinal 有你更美好

lyh061619

2018-03-15 22:17

@JFinal 用这种方案作用不是很有优势的呢谁喜欢简单玩玩下就好了^_^!!,如果用action完全被@Length@NotNull@NotBlank占了大半边天了,且又不美观,还是一个原生框架提供的intercetpro Validator来得实在,没那么乱,再说如果用当前这种有几十个字段或更多要做过虑,只能用简直不法直视来形容了

wumugulu

2018-03-16 09:36

@lyh061619 萝卜青菜各有所爱;参数多的话,稍加扩展就可以这么用了,不过我没有贴出来,hoho~

声明:
@Documented
@Repeatable(NotBlankArray.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface NotBlank {
String[] value();
String message() default "数据必须有内容";
}

使用:
@NotBlank(value={"address", "name", "address"})

netwild

2018-03-20 14:42

有点启发,感谢分享

烈火123

2018-04-02 16:25

学习一下

zhuweiliang

2018-04-22 22:06

感觉这种会浪费一些性能,来代替了开发效率

localhost8080

2018-07-06 17:00

赞,收藏

热门分享

扫码入社