背景
開發的web應用程序涉及到校驗采用的spring校驗框架,使用@Valid注解進行校驗, 在controller的方法中到處都要寫校驗處理,異常處理,能否減少這部分冗余代碼。
問題:
這是表單提交的處理,需指定跳轉到某個指定的頁面.首先檢查formBean里面的數據數據是否非法bindingResult.hasErrors()
,如果數據非法則在model中填充錯誤信息(下拉列表的數據),直接返回到原來的編輯頁面。隨后執行業務邏輯,如果有業務異常則捕獲異常BindingResultUtil.reject(bindingResult, e); 回填數據fillModel(model);
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請求的處理:我們同樣需要首先判斷輸入的數據是否合法。如果非法把轉化成Map數據返回。然后執行業務邏輯,如果異常則捕獲,將錯誤信息轉化成Map對象返回。
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層到處都是這樣的相同結構的代碼,那么沒有辦法對這種情況進行改善?能不能統一進行處理校驗信息、異常錯誤信息,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(),需要告訴攔截器需要返回哪個頁面,所以我們需要定義注解 @HandleFormSubmitValid指定出錯時跳轉到哪個頁面,出錯時需要回調哪個方法的名稱。
對於ajax請求的處理相對簡單,直接判斷是否帶有@ResponseBody注解則表明是ajax請求。由於對於ajax的處理返回值類型是不同,可能是Map,可能是void,沒有辦法返回錯誤信息的Map類,因此需要把的校驗信息或者業務異常直接寫入到HttpServletResponse中。
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.表單提交需要配置回調函數名稱,有可能回調函數被重構改成另外一個名稱,攔截功能也會失敗。
