# Spring MVC 的拦截器

Spring MVC 中的拦截器( Interceptor )类似于 Servlet 中的过滤器Filter,它主要用于拦截用户请求并做出相应的处理。

# 拦截器的定义

通过直接(或间接)实现 HandlerInterceptor 接口,并向 Spring MVC 注册既可使用自定义拦截器。

我们一般都是通过继承 HandlerInterceptorAdapter 类来间接实现 HandlerInterceptor 接口。

我们可以去重写 HandlerInterceptorAdapter 的 3 个方法:

  • .preHandle 方法在业务处理器处理请求之前被调用

    • 该方法会在 Controller 方法前执行

    • 返回值表示是否继续后续操作:

    • 返回 true 时,表示继续向下执行;

    • 返回 false 时,会中断后续所有操作(包括调用下一个拦截器和 Controller 中的方法执行)

  • .postHandle 在业务处理器处理请求执行完成后,生成视图之前执行

    • 在 Controller 方法调用之后,且解析视图之前执行。

    • 可以通过此方法对模型和视图做出进一步修改。

  • .afterCompletion 在 DispatcherServlet 完全处理完请求后被调用,可用于清理资源等。

    • 该方法会在整个请求完成(即,视图渲染结束)之后执行。

    • 可以通过此方法实现一些资源清理、记录日志信息等工作。

# 拦截器的配置

要使自定义的拦截器类生效,还需要在 Spring MVC 的配置中进行配置:

  • 配置文件版:spring-web.xml

    <mvc:interceptors>
      <bean class="com.hemiao.web.interceptor.MyInterceptor1"/> <!-- 1 -->
    
      <mvc:interceptor> <!-- 2 -->
        <mvc:mapping path="/**"/> <!-- 3 -->
        <mvc:exclude-mapping path=""/> <!-- 4 -->
        <bean class="com.hemiao.web.interceptor.MyInterceptor2"/> <!-- 5 -->
      </mvc:interceptor>
    
      <mvc:interceptor> <!-- 6 -->
        <mvc:mapping path="/hello"/> <!-- 7 -->
        <bean class="com.hemiao.web.interceptor.MyInterceptor3"/> <!-- 8 -->
      </mvc:interceptor>
    
    </mvc:interceptors>
    
    • 1 所示,在 interceptors 下注册的拦截器是全局拦截器,会拦截所有请求

    • 2 | 6 所示,在 interceptor 下注册的拦截器是局部拦截器,需要明确配置该拦截器拦截哪些请求。

    • 3 所示,表示拦截所有路径

    • 4 所示,表示在拦截某些请求的前提下,排除、不拦截某些请求

    • 7 所示,表示拦截所有以 /hello 开头的路径

    当设置多个拦截器时,先按顺序调用 .preHandle 方法,然后逆序调用每个拦截器的 .postHandle.afterCompletion 方法,即:

    • A-pre

    • B-pre

    • C-pre

    • C-post, C-after

    • B-post, B-after

    • A-post, A-after

  • 代码配置版:SpringWebConfig.java

    @Configuration
    @EnableWebMvc 
    @ComponentScan("xxx.yyy.zzz.web")
    public class SpringWebConfig implements WebMvcConfigurer {
    
      ...
    
      // 暂未验证
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor1());
    
        registry.addInterceptor(new MyInterceptor2())
                  .addPathPatterns("/**")
                  .order(1);
    
        registry.addInterceptor(new MyInterceptor3())
                  .addPathPatterns("/hello")
                  .order(2);
      }
    
      ...
    }
    

# Spring MVC POST 请求乱码问题

Spring MVC 专门提供了一个 Filter 用于解决 POST 请求乱码问题,只需要在 Java Web 配置中配置使用即可:

  • .xml 配置版:webx.ml

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  • 代码配置版:MyWebInitializer.java

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter("UTF-8", true);
    
        return new Filter[] {
            characterEncodingFilter
        };
    }
    

TIP

关于 get 请求问题,一般是通过修改 tomcat 配置设置文件解决。

# 拦截器和自定义注解实现认证

  • 自定义注解

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequiresAuthentication {
    
    }
    
  • 拦截器

    public class LoginInterceptor implements HandlerInterceptor  {
    
      private static final String USERNAME_KEY = "username";
    
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        // 如果拦截的不是方法则直接通过(有可能用户请求的是 JSP)
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
    
        // 从 session 请求头中取出 username
        String username = (String) request.getSession().getAttribute(USERNAME_KEY);
    
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
    
        // 代表,需要登录认证后才能进行的操作。即,游客无法进行该操作。
        if (method.isAnnotationPresent(RequiresAuthentication.class)) {
            return !Strings.isNullOrEmpty(username);
        }
        // 游客/匿名用户 可以进行的操作
        else {
            return true;
        }
    
        // return false;
      }
    
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
      }
    
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      }
    
    }