一、什么是过滤器


Filter也称之为过滤器,它是Servlet技术中最激动人心的技术之一,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp,Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

在客户端到服务器的过程中,当发送请求时,如果有不符合的信息将会被filter进行拦截,如果符合则会进行放行,在服务器给客户端响应时也会进行判断 如果有不符合的信息将会被filter进行拦截,如果符合则会进行放行。

过滤器就是AOP的一种实现。

可以理解为一种预处理手段,对资源进行拦截后,将其中我们认为的杂质(用户自己定义的)过滤,符合条件的放行,不符合的则拦截下来。当然,过滤器既可以拦截request,也可以拦截返回的response,我们来看一张图:

SpringSecurity filter和gateway的filter spring filter原理_学习

过滤器主要的作用是过滤请求,可以通过Filter技术,web服务器管理的所有web资源:例如:JSP、Servlet、静态图片文件、或静态HTML文件进行拦截,从而实现一些特殊功能。

例如:实现URL级别的权限控制、资源访问权限控制、过虑敏感词汇、字符编码转换、压缩响应信息等一些高级功能。便于代码重用,不必每个servlet中还要进行相应的操作。

二、第一个过滤器程序


过滤器的本质就是一个实现了 Filter 接口的 Java 类,我们先自己创建一个类,实现Filter接口(javax.servlet),重写其中的所有方法。

@WebFilter("/*")
public class FilterDemo1 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //放行代码
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {
    }

}

三、 Filter使用


第一种:web.xml配置

在web.xml中配置过滤器。这里要谨记一条原则:在web.xml中,监听器>过滤器>servlet。也就是说web.xml中监听器配置在过滤器之前,过滤器配置在servlet之前,否则会出错。

<filter>
    <filter-name>filterDemo1</filter-name>
    <filter-class>package cn.ideal.web.filter.FilterDemo1</filter-class>
    <init—param> //可选 
       <param—name>参数名</param-name>//过滤器初始化参数
       <param-value>参数值</param-value>  
    </init—pamm>  
</filter>

<filter-mapping>
    <filter-name>filterDemo1</filter-name>
    <!-- 拦截路径 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>filterDemo1</filter-name>
    <!-- 拦截路径 -->
    <url-pattern>/test/*</url-pattern>
</filter-mapping>

filter
<filter-name></filter-name> :指定filter名字
<filter-class></filter-class> :指定filter全类名(带包名)

filter-mapping
<filter-name></filter-name> :这里的标签是为了与上面filter中的名字对应,从而指向到对应的文件中
<url-pattern></url-pattern> :设置filter所拦截的路径 ※ 这里决定了什么样的资源会被过滤器拦截处理.

注:如果一个过滤器需要过滤多种文件,则可以配置多个映射,一个mapping定义一个url-pattern来定义过滤规则,配置如下:

拦截路径设置

格式

解释

/test.jsp

只有访问test.jsp这个资源的时候才会执行过滤器

/test/*

过滤目录:访问test下所有资源你的时候,执行过滤器

*.jsp

扩展名来过滤:所有jsp格式的资源被访问的时候,执行过滤器

/*

任意资源被访问,均执行过滤器

由于过滤器内设置的是比较通用的一些设置,所以一般来说使用 /* 这种格式,不过也可以根据需求情况选择。

拦截方式配置:dispatcher
拦截方式配置也就是资源被访问的形式,有这么几个属性
REQUEST:默认值,浏览器直接请求资源
FORWARD:转发访问资源 : RequestDispatcher.forward();
INCLUDE:包含访问资源 : RequestDispatcher.include();
ERROR:错误跳转资源 : 被声明式异常处理机制调用的时候

补充:声明式异常处理即:在web.xml中通过配置来确定不同的异常类型将如何被处理,最后跳转到哪个页面,也就是我们常常看到的一些404错误页面

<error-page>
    <!--异常的类-->
         <exception-type>xxx</exception-type>
    <!--异常发生时跳转的页面-->
        <location>xxx</location>
</error-page>

第二种:使用@WebFilter注解配置

与servlet相似的配置 ,使用@WebFilter注解为声明当前类为filter,第一个参数filterName为过滤器名字,第二个参数urlPatterns 为说明要拦截的请求地址.

@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的.

@WebFilter("filterName="FilterDemo1",urlPatters="/*")

但是直接在类上声明注解,显然那我们是不需要指定其名字的,而通过查看源码又可以知道,urlPatters又可以被value指定,而value又可以省略,所以我们可以简写为

@WebFilter("/*")

若想在filter注解中配置dispatcher,我们需要设置dispatcherTypes属性

@WebFilter(value = "/*",dispatcherTypes ={DispatcherType.FORWARD,DispatcherType.FORWARD} )

@Order(1):表示过滤器的顺序,假设我们有多个过滤器,你如何确定过滤器的执行顺序?这个注解就是规定过滤器的顺序。数字越小优先级越高

package com.demo.springboot2.web.service;

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.annotation.WebFilter;
import org.springframework.core.annotation.Order;
import javax.servlet.FilterConfig;
import org.springframework.stereotype.Component;

@Order(1)
@WebFilter(filterName = "DemoFilter1", urlPatterns = "/test")
public class DemoFilter1 implements Filter{
    @Override
    public void destroy() {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        //在DispatcherServlet之前执行
        System.out.println("############DemoFilter1 doFilterInternal executed############");
        filterChain.doFilter(request, response);
        //在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后
        System.out.println("############DemoFilter1 doFilter after############");
    }

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

}

启动类中增加注解@ServletComponentScan注解,自动注册Filter
@ServletComponentScan :在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。

package com.demo.springboot2.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ImportResource(locations = {"classpath:applicationContext.xml"})
@ServletComponentScan
public class springBoot2Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(springBoot2Application.class, args);

    }

}

第三种:springboot使用@Bean注解方式

新建DemoFilter2类,不要加注解@WebFilter,使用注解@Component来让SpringBoot识别这个组件 . 代码如下:

使用这个方式,默认urlPatters="/*"  即 @WebFilter("filterName="FilterDemo2",urlPatters="/*")

package com.demo.springboot2.web.service;

import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import javax.servlet.*;
/**
 * Created by huangguisu on 2020/7/3.
 */
@Component
public class DemoFilter2  implements Filter{
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        //在DispatcherServlet之前执行
        System.out.println("############DemoFilter2 doFilterInternal executed############");
        filterChain.doFilter(request, response);
        //在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后
        System.out.println("############DemoFilter2 doFilter after############");
    }
}

五、Filter的生命周期


1、Filter的创建和初始化:

         Filter的创建和销毁由web服务器负责。 web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init(FilterConfig config)方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

2、Filter的销毁

  web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。

3、FilterConfig接口

  用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了
filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:
  String getFilterName():得到filter的名称。
  String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
  Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
  public ServletContext getServletContext():返回Servlet上下文对象的引用。

4、过滤器的核心方法doFilter()

void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)

     调用doFilter方法时有三个参数:

         reqest(ServletRequest)——》HttpServletRequest(注意强转:在使用HttpServletRequest这个独有的方法时要强转)

         response(ServletResponse)——》HttpServletResponse(注意强转:在使用HttpServletResponse这个独有的方法时要强转)

         filterchain    链  可以指向下一个地址(如果有过滤器则指向下一个过滤器,没有指向资源Servlet)过滤、拦截、放行

         chain.doFilter(req,resp);//放行

在doFilter方法中进行过滤操作,常用代码有:获取请求、获取响应、获取session、放行。

HttpServletRequest request=(HttpServletRequest) arg0;//获取request对象
 HttpServletResponse response=(HttpServletResponse) arg1;//获取response对象
 HttpSession session=request.getSession();//获取session对象过滤操作代码......
chain.doFilter(request, response);//放行,通过了当前过滤器,递交给下一个filter进行过滤

       doFilter方法就是我们真正进行拦截的方法,通过前两个参数我们可以知道,不论是Request亦或是Respone我们都可以对其 进行过滤操作,那么第三个参数是什么意思呢?我们打开FilterChain的源码

public interface FilterChain {
     void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
 }

4、多个过滤器执行顺序

FilterChain是一个接口,接口内也定义了一个doFilter方法.这是一种链式结构,我们在这里称作过滤器链,其作用就是为了配置多个过滤器,多个过滤器下的执行流程是这样的。

多个过滤器执行顺序与我们前面的配置有关:

  • 注解配置:按照类名字符串比较,值小的先执行。 比如:AFilterDemo 优先于 BFilterDemo
  • web.xml配置:<filter-mapping>中谁在上面,谁优先执行

多个过滤器:从用户访问——》第一个过滤器——》第二个过滤器——》Servlet

SpringSecurity filter和gateway的filter spring filter原理_初始化_02

五、OncePerRequestFilter


在spring中,filter都默认继承OncePerRequestFilter, OncePerRequestFilter顾名思义,他能够确保在一次请求只通过一次filter,而不需要重复执行。

查到的资料上说:

- 请求发向 servlet 时会被 Filter 拦截,如果 servlet 将请求转发给另一个 servlet,请求发向第二个 servlet 时,依旧会被相同的 Filter 拦截。结果就是一个请求被同一个 Filter 拦截了两次。

- `OncePerRequestFilter` 一个请求只被过滤器拦截一次。请求转发不会第二次触发过滤器。

阅读源码可知:
OncePerRequestFilter.doFilter方法中通过request.getAttribute判断当前过滤器是否已执行
若未执行过,则调用doFilterInternal方法,交由其子类实现

OncePerRequestFilter顾名思义,它能够确保在一次请求中只通过一次filter,而不会重复执行
是由Spring提供的抽象类。

常识上都认为,一次请求本来就只过一次,为什么还要由此特别限定呢,实际上此方式是为了兼容不同的web container,特意而为之(jsr168)

也就是说并不是所有的container都像我们期望的只过滤一次,servlet版本不同,表现也不同

/** 
 
 * Filter base class that guarantees to be just executed once per request, 
 
 * on any servlet container. It provides a {@link #doFilterInternal} 
 
 * method with HttpServletRequest and HttpServletResponse arguments. 
 
 * 
 
 * <p>The {@link #getAlreadyFilteredAttributeName} method determines how 
 
 * to identify that a request is already filtered. The default implementation 
 
 * is based on the configured name of the concrete filter instance. 
 
 * 
 
 * @author Juergen Hoeller 
 
 * @since 06.12.2003 
 
 */

如,servlet2.3与servlet2.4也有一定差异:

  1. 在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。
  2. 到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要 forward的时候也用到Filter。

因此,为了兼容各种不同的运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择

spring boot处理http 404错误:当发生404 错误时, SpringBoot 内部的机制会将页面转发 /error 中。这时候过滤器原始404url请求会被过滤,转发到/error接口不会被过滤。

 

六、应用


1、登录权限验证

没登录则驳回访问请求并重定向到登录页面。

public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {

        HttpServletRequest request=(HttpServletRequest) arg0;
        HttpServletResponse response=(HttpServletResponse) arg1;
        HttpSession session=request.getSession();

        String path=request.getRequestURI();

        Integer uid=(Integer)session.getAttribute("userid");

        if(path.indexOf("/login.jsp")>-1){//登录页面不过滤
            arg2.doFilter(arg0, arg1);//递交给下一个过滤器
            return;
        }
        if(path.indexOf("/register.jsp")>-1){//注册页面不过滤
            arg2.doFilter(request, response);
            return;
        }

        if(uid!=null){//已经登录
            arg2.doFilter(request, response);//放行,递交给下一个过滤器

        }else{
            response.sendRedirect("login.jsp");
        }

    }

2、设置字符编码

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
          HttpServletRequest request2=(HttpServletRequest) request;
          HttpServletResponse response2=(HttpServletResponse) response;

          request2.setCharacterEncoding("UTF-8");  
          response2.setCharacterEncoding("UTF-8"); 

          chain.doFilter(request, response); 

    }

3、过敏敏感词