背景
开发的web应用程序涉及到校验采用的spring校验框架,在controller的方法中到处都要写校验处理,异常处理,能否减少这部分冗余代码。
问题:
这是表单提交的处理
1 @RequestMapping(value = "/edit", method = RequestMethod.POST) 2 public String edit(@Valid FormBean formBean, BindingResult bindingResult, Model model) { 3 if (bindingResult.hasErrors()) { 4 fillModel(model); 5 return "resource"; 6 } 7 try { 8 service.edit(formBean); 9 } catch (BusinessException e) { 10 fillModel(model); 11 BindingResultUtil.reject(bindingResult, e); 12 return "resource"; 13 } 14 return "redirect:/resources"; 15 }
校验检查到出错,需要些代码判断出错,返回到原页面;而且还需要把有些必要的信息回填,比如下拉列表。
这是ajax请求的处理:
1 @ResponseBody 2 @RequestMapping(value = "/resource", method = RequestMethod.POST) 3 public Map<String, Object> save(@Valid FormBean formBean, BindingResult bindingResult) { 4 Map<String, Object> resultMap = new HashMap<String, Object>(); 5 if (bindingResult.hasErrors()) { 6 return BindingResultUtil.convertToMap(bindingResult); 7 } 8 try { service.save(formBean); 9 return resultMap; 10 } catch (BusinessException e) { 11 log.error("save resource failed", e); 12 BindingResultUtil.reject(bindingResult, e); 13 return BindingResultUtil.convertToMap(bindingResult); 14 } 15 }
本来只需要返回一个void就好,但是为了把校验信息输出,不得不返回一个Map,把检验的结果放入到map中。 到处都是这样的相同结构的代码,那么没有办法对这种情况进行改善?能不能统一进行处理校验信息、异常错误信息,controller的方法程序只需要写主业务逻辑。
方案1:
使用Filter拦截处理,但仔细观察其方法public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain)
,只传递request,response,无法利用spring已有的校验框架BindingResult的获取校验结果,所以该方案不可行。
方案2:
spring aop自带的拦截功能,找到这篇文章http://www.uroot.com/archives/490 充分利用Spring3 MVC AOP和Annotation特性,自定义拦截和注解,实现登陆验证的最佳实践。
方案3:
利用aspectJ进行拦截。本文也重点介绍方案3的配置:
需要进行如下配置:
1.pom.xml文件配置相关依赖:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.1</version> </dependency>
2.spring-servlet.xml
<beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <aop:aspectj-autoproxy proxy-target-class="true"/> <beans:bean id="validInterceptor" class="com.test.interceptor.ValidInterceptor" /> <aop:config> <aop:pointcut id="validPoint" expression="execution(public * com.test.controller.*.*(..)) "/> <aop:advisor pointcut-ref="validPoint" advice-ref="validInterceptor"/> </aop:config>
注意红色部分的配置,里面配置拦截哪些controller的哪些方法可以被拦截
3.自定义拦截器
public class ValidInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Valid validAnnotation = findAnnotation(invocation.getMethod().getParameterAnnotations(), Valid.class); if (validAnnotation != null) { handleValidationError(); } return invocation.proceed(); }
思路是拦截@Valid注解,检查BindingResult是否有错误,有错误就提前返回。对于表单提交与ajax提交,是有不同的处理方式。表单提交可能有回调动作,比如fillModel(),需要告诉拦截器需要返回哪个页面。对于ajax请求呢,返回值类型是不同的,可能是Map,可能是void,没有办法返回错误信息的Map类,因此需要把json的校验错误信息写入到Response中。
4. 对处理表单提交 定义注解,配置一旦出错,应该返回到哪个错误页面。ajax请求则没有必要处理。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface HandleFormSubmitValid { String view(); String callback() default ""; }
5.最后修改controller的方法
对于表单提交:
@RequestMapping(value = "/edit", method = RequestMethod.POST) @HandleFormSubmitValid(view="resource", callback="fillModel") public String edit(@Valid FormBean formBean, BindingResult bindingResult, Model model) { service.edit(courseFormBean); return "redirect:/resources"; }
ajax请求:
@ResponseBody @RequestMapping(value = "/resource", method = RequestMethod.POST) public void save(@Valid FormBean formBean, BindingResult bindingResult) { service.save(formBean); }
Controller代码是不是清爽了很多?没有丑陋的异常处理,检验处理,没有莫名其妙的Map作为返回值, 世界清静多了!
当然在实现的过程还遇到了其他的问题:
比如spring框架,居然没有办法获得HttpServletResponse,最后我不得不写了一个Filter或者mvc的interceptors,将其放到ThreadLocal里面。
写ajax请求写Response的时候,需要注意编码和contentType,如下:
HttpServletResponse response = WebContext.getResponse(); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().write(ajaxJsonResponse);
需要改进的地方:
1.controller方法中有参数bindingResult在拦截器中有被使用,但在controller方法中没有被用到,有可能被认为是无用参数,给去掉,则检验拦截功能会失败。理想的情况应该是去掉bindingResult,在拦截器中对formBean进行检验。2.表单提交需要配置回调函数名称,有可能回调函数被重构改成另外一个名称,拦截功能也会失败。