springMVC的校验依赖比较多,而且对于一个VO来说,多次重复使用且校验不同的参数,会造成很多麻烦。这些天做的就是怎么能多次利用一个VO对象来适应于不同的校验环境。
一步一步来:
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方法.而直接跳到了统一异常处理器里,然后做视图处理.