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



  • 相关阅读:
    [摘]MongoDB范围查询的索引优化
    python ftp 暴破
    写mongodb日志
    [转]使用 Python 实现跨平台的安装程序
    HDOJ 1008 Elevator
    第一个数字
    反转串
    HDOJ 1108 最小公倍数
    HDOJ 1096 A+B for InputOutput Practice (VIII)
    报数游戏
  • 原文地址:https://www.cnblogs.com/pmh905001/p/4430623.html
Copyright © 2011-2022 走看看