spring aop攔截controller方法


背景 

開發的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.表單提交需要配置回調函數名稱,有可能回調函數被重構改成另外一個名稱,攔截功能也會失敗。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM