SpringMVC中过滤器和拦截器的区别

导读:本篇文章讲解 SpringMVC中过滤器和拦截器的区别,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1、引言

我们在开发过程中都会遇到需要统一处理接口或者参数的场景,这个时候我们就会用到过滤器(Filter)或者拦截器(Intercepter)。
测试代码参考 chapter-2-springmvc-quickstart:
https://gitee.com/leo825/spring-framework-learning-example.git

2、共同点

1、都可以拦截请求和过滤请求
2、都用了责任链设计模式,并且都可以对请求进行预处理和后处理

3、区别

3.1、拦截器
  • 依赖于web框架实现,在我们使用的SpringMVC这种就是依赖于SpringMVC框架
  • 在实现上基于Java的反射机制,属于面向切面(AOP)的一种应用
  • 可以在一个Controller生命周期内进行多次调用,但是只能对Controller进行拦截
  • 主要作用:由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,并且一个拦截器可以在Controller生命周期内进行多次调用。
3.2、过滤器
  • 过滤器依赖于Servlet容器
  • 过滤器实现上基于函数回调,可以几乎对所有请求进行过滤(包括静态资源过滤)
  • 过滤器实例只能在容器初始化的时候调用一次
  • 主要作用:执行过滤操作,比如敏感信息、特殊请求、xss方漏洞、统一加解密参数等

4、具体实现

4.1、拦截器
4.1.1、拦截实现方式

SpringMVC拦截器(Interceptor)实现对每一个请求处理前后进行相关业务是通过HandlerInterceptor来实现的。定义一个拦截器,可以通过以下3种方式:

  1. 实现Spring提供的的HandlerInterceptor接口;
  2. 继承Spring中的抽象类HandlerInterceptorAdapter,此抽象类实现了HandlerInterceptor接口;
  3. 实现Spring提供的WebRequestInterceptor接口;
4.1.2、拦截实现

具体实现举一个例子,测试代码如下:

package com.leo.interceptor;

import com.leo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;

/**
 * @ClassName: HandlerInterceptor2
 * @Description: 测试拦截器2
 * 简单说明一下:本工程是采用xml方式注册的拦截器,因此可以直接依赖注入,
 * 如果是SpringBoot方式注册拦截器,不要使用new的方式创建拦截器对象,
 * 要把拦截器通过@Autowired注入进来,然后注册到Spring容器中,
 * 不然这个拦截器中通过依赖注入到的userInfoService永远是null
 * @Author: leo825
 * @Date: 2020-02-03 16:05
 * @Version: 1.0
 */
public class HandlerInterceptor2 extends HandlerInterceptorAdapter {

    @Autowired
    UserInfoService userInfoService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("HandlerInterceptor2 preHandle");
        HttpSession session = request.getSession();
        session.setAttribute("startTime",System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("HandlerInterceptor2 postHandle");
        //访问数据库
        List userInfoList = userInfoService.getUserInfoList();
        System.out.print("HandlerInterceptor2 信息: ");
        System.out.println(userInfoList);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("HandlerInterceptor2 afterCompletion");
        HttpSession session = request.getSession();
        long startTime = (long)session.getAttribute("startTime");
        System.out.println("HandlerInterceptor2 过滤的接口耗时:" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

spring配置文件要添加相应的配置,如下所示:

        <mvc:interceptor>
            <!-- /* 是一级目录下的路径; /** 不分目录等级, 即所有请求 -->
            <mvc:mapping path="/test2/**"/>
            <bean class="com.leo.interceptor.HandlerInterceptor2"></bean>
        </mvc:interceptor>

访问地址:http://localhost:8080/springmvc/test2/hello,运行结果如下所示:

HandlerInterceptor1 preHandle
HandlerInterceptor2 preHandle
HandlerInterceptor3 preHandle
HandlerInterceptor4 preHandle
HandlerInterceptor5 preHandle
使用配置实现 hello controller 跳转到 success
HandlerInterceptor5 postHandle
HandlerInterceptor4 postHandle
HandlerInterceptor3 postHandle
HandlerInterceptor2 postHandle
HandlerInterceptor2 信息: [UserInfo{id=3, name='晓玲', gender='女', age='22', remarks='工程师'}, UserInfo{id=4, name='晓玲', gender='女', age='24', remarks='工程师'}]
HandlerInterceptor1 postHandle
HandlerInterceptor5 afterCompletion
HandlerInterceptor5 过滤的接口耗时:330ms
HandlerInterceptor4 afterCompletion
HandlerInterceptor4 过滤的接口耗时:330ms
HandlerInterceptor3 afterCompletion
HandlerInterceptor3 过滤的接口耗时:330ms
HandlerInterceptor2 afterCompletion
HandlerInterceptor2 过滤的接口耗时:330ms
HandlerInterceptor1 afterCompletion
HandlerInterceptor1 过滤的接口耗时:330ms

具体代码可以自行下载阅览。

4.2、过滤器
4.2.1、过滤器实现方式
  1. 直接实现Filter接口,这一类过滤器只有CompositeFilter
  2. 继承抽象类GenericFilterBean,该类实现了Filter,这一类的过滤器只有一个,即DelegatingFilterProx;
  3. 继承抽象类OncePerRequestFilter,该类为GenericFilterBean的直接子类,这一类过滤器包括CharacterEncodingFilter、HiddenHttpMethodFilter、HttpPutFormContentFilter、RequestContextFilter和ShallowEtagHeaderFilter;
  4. 继承抽象类AbstractRequestLoggingFilter,该类为OncePerRequestFilter的直接子类,这一类过滤器包括CommonsRequestLoggingFilter、Log4jNestedDiagnosticContextFilter和ServletContextRequestLoggingFilter。

这些类之间的关系就略了,可以参考这篇文章Spring MVC过滤器-超类

4.2.2、过滤器实现

过滤器的实现方法就举一个例子了,其他的可自行参考测试

public class MyFilter3 extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("MyFilter3 执行过滤");
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

还有莫忘了在web.xml中增加过滤器的拦截配置

    <filter>
        <filter-name>MyFilter3</filter-name>
        <filter-class>com.leo.filter.MyFilter3</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFilter3</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

还可以使用注解方式去实现例如在类上添加如下注解也可以实现

@WebFilter(urlPatterns = "/*", filterName = "myFilter3")

5、过滤器中依赖注入(深坑)

5.1、问题描述

在项目中需要在filter中注入@Service注解的Service服务,但是尝试了很多方法都无法实现(笔者也是),使用@Autowired注解注入的Service对象一致都是null,请问是这怎么回事?

分析:既然是注入的Service对象一直是null,那就是考虑原因是否filter的创建要早于@Service注解的对象。
尝试:如果是Filter比Service的Bean实例更早创建,那就改变创建的顺序,将@Service早一步创建不就行了。尝试之后仍然报null,那就不是这方面的问题了。

再次从网上查阅资料,这块涉及web启动的原理,web应用启动的顺序是:Listener->Filter->Servlet,因为我们在web项目中一般都会用到两个配置文件applicationContext.xml和springmvc-servlet.xml,配置spring的时候会添加一个Listener,它会读取applicationContext.xm配置信息对Spring Context进行配置。因此在applicationContext.xml中的bean首先被初始化和注入,然后在对Filter进行初始化,在接着对DispatcherServlet进行初始化。因此我们在Filter中注入的Bean会失败。这里提供两种方式获取Bean

5.2、Filter依赖注入实现
5.2.1、方法一:web.xml配置实现

通过配置代理,将自定义的Filter注入,配置代码如下:

    <filter>
        <filter-name>DelegatingFilterProxy</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>myFilter</param-value>
        </init-param>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value><!-- 此参数必需设置-->
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>DelegatingFilterProxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

过滤器的代码如下:

@Component("myFilter")
public class MyFilter implements Filter {

    @Autowired
    UserInfoService userInfoService;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter过滤器初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter过滤器执行过滤");
        //访问数据库
        List userInfoList = userInfoService.getUserInfoList();
        System.out.println(userInfoList);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("MyFilter过滤器销毁了");
    }
}

以上代码都是经过测试,真实可用的。

5.2.2、方法二:继承WebApplicationInitializer,并注册Filter

实现代码示例:

public class MyFilterConfig implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
        //Bean实例名称
        String BeanId = "myFilter5";
        //代理的过滤器的Bean
        delegatingFilterProxy.setTargetBeanName(BeanId);
        //设置"targetFilterLifecycle"为True,则spring来管理Filter.init()和Filter.destroy();若为false,则这两个方法失效!
        delegatingFilterProxy.setTargetFilterLifecycle(true);
        //注册过滤器
        FilterRegistration filterRegistration = servletContext.addFilter(BeanId, delegatingFilterProxy);
        filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
        System.out.println("代理拦截器,将Bean注入到Web容器中");
    }
}

其中的myFilter5就是过滤器的Bean实例,如下:

@Component
public class MyFilter5 implements Filter {

    @Autowired
    UserInfoService userInfoService;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter5 执行过滤");
        //访问数据库
        List userInfoList = userInfoService.getUserInfoList();
        System.out.print("MyFilter5 信息: ");
        System.out.println(userInfoList);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }
}
5.2.3、方法三:实现ApplicationContextAware接口的工具

首先编写ApplicationContextUtil工具类实现ApplicationContextAware接口

@Component
public class ApplicationContextUtil implements ApplicationContextAware{
    private static ApplicationContext applicationContext;

    /**
     * 通过bean的id获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }

    /**
     * 根据bean的id和类型获取bean对象
     * @param beanName
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String beanName,Class<T> clazz){
        return clazz.cast(getBean(beanName));
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

其次在拦截器中通过这个工具获取Bean对象

@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter6")
public class MyFilter6 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter6 执行过滤");
        //访问数据库,这个地方要注意使用的是“userInfoServiceImpl”,因为默认是按照类名首字符小写注入Spring中的
        UserInfoService userInfoService = (UserInfoService) ApplicationContextUtil.getBean("userInfoServiceImpl");
        List userInfoList = userInfoService.getUserInfoList();
        System.out.print("MyFilter6 信息: ");
        System.out.println(userInfoList);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }
}
5.2.4、方法四:获取WebApplicationContext对象

获取WebApplicationContext对象从而获取相应的Bean对象

@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter7")
public class MyFilter7 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter7 执行过滤");
        //访问数据库,这个地方要注意使用的是“userInfoServiceImpl”,因为默认是按照类名首字符小写注入Spring中的
        WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        UserInfoService userInfoService = (UserInfoService) webApplicationContext.getBean("userInfoServiceImpl");
        List userInfoList = userInfoService.getUserInfoList();
        System.out.print("MyFilter7 信息: ");
        System.out.println(userInfoList);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }
}

通过WebApplicationContext获取Bean对象的方式有很多,上面只是举了一个普通的常见方式。
例如:WebApplicationContextUtilsContextLoader方式
可以参考《Spring容器中获取Bean实例的七种方式(附实战源码》这篇文章里面的示例。

注意:平时都是使用 @Autowired按照类型注入UserInfoService,但是实际上Spring中注入的id=userInfoServiceImpl的Bean

5、拦截器执行顺序

拦截器和过滤器执行顺序可以根据下图来说明
Filter执行顺序

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72744.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!