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.表单提交需要配置回调函数名称,有可能回调函数被重构改成另外一个名称,拦截功能也会失败。



原文地址:https://www.cnblogs.com/pmh905001/p/4430623.html