Spring 拦截器和过滤器

在 Spring MVC 中,Interceprtor 与 Filter 两者的应用场景好像差不多,最大的区别可能是前者属于 Spring 的组件,而后者则是 Servlert 三剑客中的一个,它们本质的区别在于两者发生的时机不一致。

Filter 和 Interceprtor 对比:

  • Filter:在执行 Servlet#service 方法之前,会执行过滤器;执行完毕之后也会经过过滤器;

    Filter 操作 Request、Response。

  • Interceptor:对会话进行拦截,可以在调用 Handler 方法之前、视图渲染之前、方法返回之前,三个时机触发回调。

    Interceptor 操作 Request、Response、handler、modelAndView、exception。

Filter 和 Interceprtor 的执行顺序:

  • Filter 处理 -> Interceptor 前置 -> controller -> Interceptor 处理中 -> Interceptor 处理后 -> Filter 处理后

    image

过滤器基于函数回调方式实现,拦截器基于 Java 反射机制实现。实际开发中,拦截器的应用场景会比过滤器要更多,下面是拦截器和过滤器的主要应用场景:

  • 拦截器的应用场景:权限控制,日志打印,参数校验

  • 过滤器的应用场景:跨域问题解决,编码转换

Filter

它可以在 HTTP 请求到达 Servlet 之前,被一个或多个 Filter 处理。其工作流程如图所示:

image

Filter 实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,Filter 会依赖于 Tomcat 等容器,导致它只能在 web 程序中使用。

使 Spring 管理 Filter

方式一:@Component + @Order

在自定义 Filter 类上,加上 @Component@Order 注解,即可被 Spring 管理。

@Component
@Order(1)
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter");
        // 要继续处理请求,必须添加 filterChain.doFilter()
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

注意:

  • 如果 Filter 要使请求被继续处理,就一定要调用 filterChain.doFilter()

  • 这里我们可以通过 @Order 控制过滤器的级别,值越小级别越高越先执行。

优缺点:

  • 优点:注解方式配置简单,支持自定义 Filter 顺序。

  • 缺点:只能拦截所有 URL,不能通过配置去拦截指定的 URL。

方式二:通过 JavaConfig 配置

  • 定义一个过滤器
public class LogCostFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        long start = System.currentTimeMillis();
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("Execute cost="+(System.currentTimeMillis()-start));
    }
}
  • 通过 JavaConfig 配置方式,注册 Filter
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean registerMyFilter(){
        FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
        bean.setOrder(1);
        bean.setFilter(new LogCostFilter());
        // 匹配"/hello/"下面的所有url
        bean.addUrlPatterns("/hello/*");
        return bean;
    }

    @Bean
    public FilterRegistrationBean registerMyAnotherFilter(){
        FilterRegistrationBean<MyAnotherFilter> bean = new FilterRegistrationBean<>();
        bean.setOrder(2);
        bean.setFilter(new MyAnotherFilter());
        // 匹配所有url
        bean.addUrlPatterns("/*");
        return bean;
    }
}

优缺点:

  • 优点:功能强大,配置灵活。只需要把每个自定义的 Filter 声明成 Bean 交给 Spring 管理即可,还可以设置匹配的 URL 、指定 Filter 的先后顺序。

方式三: @WebFilter + @ServletComponentScan

在自定义 Filter 类上,添加 @WebFilter 注解,并在启动类上增加 @ServletComponentScan("com.athena.common.filter") 注解,参数就是 Filter 所在的包路径。

  • 定义一个 Filter
@Order(1)
@WebFilter(urlPatterns = "/*", filterName = "MyFilter")
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter");
        // 要继续处理请求,必须添加 filterChain.doFilter()
        filterChain.doFilter(servletRequest,servletResponse);
    }
}
  • 添加自定义过滤器扫描路径
@SpringBootApplication
@ServletComponentScan("com.athena.common.filter")
public class FilterDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(FilterDemoApplication.class, args);
    }

}

优缺点:

  • 优点:配置简单、集中,支持 Filter 的顺序,支持对 Filter 匹配指定 URL。

对比

方式一不能指定 Filter 的顺序,所以不太推荐。因此,实际使用的时候,推荐使用 方式二 或者 方式三。

应用场景

Filter 的这个特性在生产环境中有很广泛的应用,如:

  • 修改请求和响应;

  • 防止 xss 攻击;

  • 包装二进制流使其可以多次读等。

Interceptor

拦截器(Interceptor)是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application、Swing 等程序中。

自定义拦截器只需要实现接口 HandlerInterceptor 即可。

【示例】

  • 实现拦截器
@Order(2)
@Slf4j
public class SecurityInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 一个简单的安全校验,要求请求头中必须包含 req-name : yihuihui
        String header = request.getHeader("req-name");
        if ("yihuihui".equals(header)) {
            return true;
        }

        log.info("请求头错误: {}", header);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("执行完毕!");
        response.setHeader("res", "postHandler");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("回收");
    }
}
  • 注册拦截器
package com.demo.springboot2.web.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    @Autowired
    private SecurityInterceptor demoInterceptor1;

    @Autowired
    private DemoInterceptor2 demoInterceptor2;

    // 这个方法是用来配置静态资源的,比如html,js,css,等等
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    // 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns("/**") 表示拦截所有的请求,
        // excludePathPatterns("/login", "/register") 表示除了登陆与注册之外,因为登陆注册不需要登陆也可以访问
        registry.addInterceptor(demoInterceptor1).addPathPatterns("/**").excludePathPatterns("/login", "/register");
        registry.addInterceptor(demoInterceptor2).addPathPatterns("/**").excludePathPatterns("/login", "/register");
        System.out.println("************addInterceptors**********");
    }
}

preHandle

在 handler 方法执行之前(简单理解为 Controller 提供的服务调用之前)会被触发,如果返回 ture,表示拦截通过,可以执行;若果返回 false,表示不允许往后走。

因此在这里,通常可以用来做安全校验、用户身份处理等操作

注意,无论是拦截器,还是 Filter,在使用 Request 中的请求流的时候,要警惕,通常请求参数流的读取是一次性的,如果在这里实现了一个请求参数日志输出,把请求流的数据读出来了,但是又没有写回去,就会导致请求参数丢失了。

postHandler

这个是在 handler 方法执行之后,视图渲染之前被回调,简单来说,我们在这个时候,是可以操作 ModelAndView,往里面添加一下信息,并能被视图解析渲染的。

当然鉴于现在前后端分离的趋势,这个实际上用得也不多了。

afterCompletion

该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。

应用场景

Interceptor 的应用场景:

  • 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;

  • 权限检查:如登录检测,进入处理器检测是否登录;

  • 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache 也可以自动记录)

  • 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。


参考:

posted @ 2023-10-19 20:58  LARRY1024  阅读(183)  评论(0编辑  收藏  举报