SpringMVC使用自定义的校验。(抛弃原有的校验)

springMVC的校验依赖比较多,而且对于一个VO来说,多次重复使用且校验不同的参数,会造成很多麻烦。这些天做的就是怎么能多次利用一个VO对象来适应于不同的校验环境。

springmvc会拦截以valid...开头的注解

一步一步来:

1.传统校验

@RequestMapping("/login")
	public ModelAndView login(HttpServletRequest request,User user) throws Exception {
		if(StringUtils.isBlank(user.getUsername())||StringUtils.isBlank(user.getPassword())){
			//如果用户名或密码是空,那么就返回视图
			ModelAndView view = new ModelAndView("error.jsp");
			view.addObject("errorMessgae", "用户名或密码为空");
		}
		return null;
	}

这个方式是比较恶心的.因为每次校验不成功,都得指定这个视图.一个方法里面有多个校验,多个方法里有成千上百个校验,这不得恶心死吗?

那怎么优化呢?答案是利用异常统一指定视图

2.异常校验

 

    异常如何指定视图不在这里赘述,异常处理器如何优化以及正确使用请看这里(待添加),我们来直接看通过异常怎么处理校验的

@RequestMapping("/login")
	public ModelAndView login(HttpServletRequest request,User user) throws Exception {
		if(StringUtils.isBlank(user.getUsername())||StringUtils.isBlank(user.getPassword())){
			throw new Exception("用户名或密码不能为空");
		}
		...
		return null;
	}

这样的话就简单多了.我们只需要抛个异常就行了,可读性也非常棒,但是即时这样,校验的代码和视图层的处理代码耦合性太高. 不利于以后的维护或者扩展.

那怎么办呢??使用springMVC/javax/hibanate的Valid校验规则什么JSR303什么玩意的.   

 

3.@Valid校验

    想使用这个校验,要在springmvc的配置文件里添加一个validator.

 <mvc:annotation-driven validator="validator"/>

  <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>

    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="useCodeAsDefaultMessage" value="false"/>  
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>

然后在看controller视图层代码,上面参数里多加了一个@Valid的注解

 

@RequestMapping("/login")
	public ModelAndView login(HttpServletRequest request, @Valid User user) throws Exception {
		return null;
	}

    再看下User类里,username上面加了个NotNull

public class User {

	private Integer userid;
	@NotNull
	private String username;
	
	private String password;

	private String registtime;

	private String openid;

	private String headpic;
...
...
...

那么这时候我们访问controller的时候,如果我们没有传username,那么@RequestMapping注解的视图逻辑方法就不会起作用的.
当然,不止@NotNull这一个注解,自带的就有很多注解.
用起来确实不错,校验的过程我们完全不用写,直接用.但是我依然不满足.
为什么呢?
原因有三个,第一个是这样写需要springmvc,javax,hibanate这三个jar包,整的整个都觉得代码不干净.第二个是我希望有更高的需求,我希望有特有的校验姿势.第三个是最重要的,假如说我在Login方法里校验了username和password,又想在补全信息方法里面校验性别是否只为1或0.那即时我自定义了校验处理注解,我也不敢在user上加啊,因为我如果添加了,那么Login方法里也会校验性别,会导致原有的Login方法失效的.那么怎么办呢?看下面

4.自定义校验,抛弃前面所有,造轮子

先看下轮子结构

OK.按顺序来,先看ValidRegist这个注解.

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidRegist {
	Class<? extends ValidDriver> value() ;
}

这个注解是校验驱动的注册注解,里面就只有一个Class类,但是有要求,就是这个class必须是实现了ValidDriver接口的.

我们接下来看看这个ValidDriver是什么?

 
/**
 * 校验驱动接口
 * @author Administrator
 *
 */
public interface ValidDriver {
	/**
	 * 校验的具体方法
	 * @param filedName 需要校验的属性名
	 * @param filedValue 需要校验的属性值
	 * @param annotation 属性上面打的那个注解
	 * 
	 * @return 如果校验正确,返回true,如果校验错误,返回false并走下面的ifNotPassValid()
	 * @throws RuntimeException
	 */
	boolean doValid(String filedName,Object filedValue,Annotation annotation)throws RuntimeException;
	/**
	 * 如果校验失败走这个方法
	 * @throws RuntimeException
	 */
	void ifNotPassValid() throws RuntimeException;
}

解释是不解释了.都有注释了.

然后我们先别急,先别看具体处理逻辑,我们先注册一个注解试试,就试试那个Equals注解

@Equals注解类:并且注册了EqualsValidDriver.class作为它的驱动处理,并且有个value值

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@ValidRegist(EqualsValidDriver.class)
public @interface Equals {
	String value();
}

EqualsValidDriver是@Equals的注解处理类

public class EqualsValidDriver implements ValidDriver{
	private String filedName;
	@Override
	public boolean doValid(String filedName,Object filedValue, Annotation annotation) {
		this.filedName=filedName;
		Equals equalsObject=(Equals) annotation;
		if(equalsObject.value().equals(filedValue)){
			//如果属性的值和注解里的那个值相同,就返回true
			return true;
		}
		return false;
	}
	
	@Override
	public void ifNotPassValid() throws RuntimeException {

		throw new RuntimeException(filedName+"属性和@Equals里的值不相同");
	}
}

这个驱动虽然写出来了,但实际上和那个注解半毛钱关系都没有,还需要有个最为核心的处理类

public class ValidManager {

	private String[] includeFieldNames;
	
	private boolean includeFieldNamesIsEmpty=false;
	
	public void validObject(Object object){
		try {
			Class<? extends Object> clazz = object.getClass();
			for (Field field : clazz.getDeclaredFields()) {
				if (isNeedValid(field.getName())) {
					field.setAccessible(true);
					Annotation[] fieldAnnotations = field.getAnnotations();
					for (Annotation fieldAnnotation : fieldAnnotations) {
						ValidRegist validRegist = fieldAnnotation.annotationType().getAnnotation(ValidRegist.class);
						if (validRegist != null) {
							Object value = field.get(object);
							Class<? extends ValidDriver> validDriverClass = validRegist.value();
							ValidDriver validDriver = validDriverClass.newInstance();
							boolean doValid = validDriver.doValid(field.getName(),value, fieldAnnotation);
							if (!doValid) {
								validDriver.ifNotPassValid();
							}
						}
					}
				}
			}
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch(InstantiationException e){
			throw new RuntimeException(e);
		}
	}

	private boolean isNeedValid(String fieldName) {
		if(includeFieldNamesIsEmpty){
			return true;
		}
		if(includeFieldNames==null||includeFieldNames.length==0){
			includeFieldNamesIsEmpty=true;
			return true;
		}
		for (String name : includeFieldNames) {
			if(fieldName.equals(name)){
				return true;
			}
		}
		return false;
	}

	public String[] getIncludeFieldNames() {
		return includeFieldNames;
	}

	public void setIncludeFieldNames(String[] includeFieldNames) {
		this.includeFieldNames = includeFieldNames;
	}
}

 

如果你们在看本篇文章,那真是太好了,我决定要坑你们一下.这个类不解释的.自己看吧,我曾经因为老是写多层逻辑判断被别人批评过阅读性太差了!!!哈哈哈哈

 

我们测试一下:我们把用户名设定为aaa

public class User {
	public static void main(String[] args) throws Exception {
		User user=new User();
		user.setUsername("aaaa");
		new ValidManager().validObject(user);
	}
	
	private Integer userid;
	@Equals("bbb")
	private String username;
	public Integer getUserid() {
		return userid;
	}
	public void setUserid(Integer userid) {
		this.userid = userid;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	
}

发现报了异常:

Exception in thread "main" java.lang.RuntimeException: username属性和@Equals里的值不相同
	at com.alisita.valid.extend.equals.EqualsValidDriver.ifNotPassValid(EqualsValidDriver.java:23)
	at com.alisita.valid.core.ValidManager.validObject(ValidManager.java:27)
	at com.alisita.valid.User.main(User.java:11)

正和我意!!!

然后我再写个notNull的注解和驱动.基本上就是在里面判断是否为null的

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@ValidRegist(NotNullValidDriver.class)
public @interface NotNull {
	
}
public class NotNullValidDriver implements ValidDriver{

	@Override
	public boolean doValid(String filedName,Object filedValue, Annotation annotation) {
		if(filedValue!=null){
			return true;
		}
		return false;
	}

	@Override
	public void ifNotPassValid() {
		throw new RuntimeException("靠,为Null了,这可不行");
	}
}

 

我再把这个notNull放到那个userid上.

 

public class User {
	public static void main(String[] args) throws Exception {
		User user=new User();
		user.setUsername("bbb");
		new ValidManager().validObject(user);
	}
	@NotNull
	private Integer userid;
	@Equals("bbb")
	private String username;
	public Integer getUserid() {
		return userid;
	}
	public void setUserid(Integer userid) {
		this.userid = userid;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	
}

报异常

Exception in thread "main" java.lang.RuntimeException: 靠,为Null了,这可不行
	at com.alisita.valid.extend.notnull.NotNullValidDriver.ifNotPassValid(NotNullValidDriver.java:19)
	at com.alisita.valid.core.ValidManager.validObject(ValidManager.java:27)
	at com.alisita.valid.User.main(User.java:11)

非常满意!

这样很不错,可是,我现在已经在这个user里俩属性都加上了注解,但是我将来在某个方法里面调用的时候,我只需要校验username,不需要校验userid怎么办呢?

别急,还有这个选择校验属性的功能.

public static void main(String[] args) throws Exception {
		User user=new User();
		user.setUsername("bbb");
		ValidManager manager = new ValidManager();
		/**
		 * 设置校验的属性名,是个数组,可以添加.代表着只校验这个数组里面的
		 * 如果不设置,代表着该校验就校验,全部校验
		 */
		manager.setIncludeFieldNames(new String[]{"username"});
		manager.validObject(user);
	}

very good!

多好的方法,我们以后在controller里就可以这样玩了.

@RequestMapping("/login")
	public ModelAndView login(User user) throws Exception {
		new ValidManager().validObject(user);
		return null;
	}

但是!!!!!

但是我不满足,我要继续作.我这个代码我都不想写.那怎么办呢?

额,博客不想写了,已经半夜12点了.我把剩下的类粘来吧,并且把配置方法给大家.

首先,参数的校验注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidParam {
	/**
	 * 执行校验的属性名
	 * 
	 * @return
	 */
	String[] value() default {};
}

 

然后是和springMVC结合的校验器

 

public class SpringmvcValidator implements Validator{
	private static Logger log=Logger.getLogger(SpringmvcValidator.class);
	public static final String REQUEST_INCLUDEFIELDNAMES_ARRAY=SpringmvcValidator.class.getName()+".validIncludeArray";
    @Override  
    public boolean supports(Class<?> clazz) { 
         return true;
    }  
  
    @Override  
    public void validate(Object object, Errors arg1) {
    	log.info(object);
    	RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
    	String[] includeFieldNames = (String[]) requestAttributes.getAttribute(REQUEST_INCLUDEFIELDNAMES_ARRAY, RequestAttributes.SCOPE_REQUEST); 
        ValidManager manager=new ValidManager();              
        manager.setIncludeFieldNames(includeFieldNames);  
        manager.validObject(object);
        
    }  
    
}  

 

 

然后是spring拦截器

 

 

public class ValidInterceptor implements HandlerInterceptor{
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		HandlerMethod handlerMethod=(HandlerMethod) handler;
		MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
		for (MethodParameter methodParameter : methodParameters) {
			if(methodParameter.hasParameterAnnotation(ValidParam.class)){
				ValidParam param = methodParameter.getParameterAnnotation(ValidParam.class);
				request.setAttribute(SpringmvcValidator.REQUEST_INCLUDEFIELDNAMES_ARRAY, param.value());
				break;
			}
		}
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		
		
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		
		
	}

}

 

然后是springMVC的配置文件

 

<mvc:annotation-driven validator="validator"/>
<bean id="validator" class="com.alisita.valid.core.SpringmvcValidator">
</bean>
<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/**" />
			<bean class="com.heping.community.common.spring.springmvc.interceptor.ValidInterceptor"></bean>
		</mvc:interceptor>
	</mvc:interceptors>

然后使用情况如下:

@RequestMapping("/login")
	public ModelAndView login(@ValidParam("userid") User user) throws Exception {
		
		return null;
	}


大家可以测试一下.非常完美.只是像我这样做需要驱动的ifNotPassValid()方法里面抛出异常的.只有抛出异常才能不会进入真正的login方法.而直接跳到了统一异常处理器里,然后做视图处理.

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值