Java Filter(过滤器)

基本介绍

过滤器,顾名思义就是对事物进行过滤的,在Web中的过滤器,当然就是对请求进行过滤,我们使用过滤器,就可以对请求进行拦截,让后做相应的处理,实现许多特殊功能。如登录控制,权限控制,过滤敏感词汇等。


过滤器原理

当我们使用过滤器时,过滤器会对浏览器的请求进行过滤,过滤器可以动态的分为3个部分,1.放行之前的代码,2.放行,3.放行后的代码,这3个部分分别会发挥不同作用。

  • 第一部分代码会对浏览器请求进行一次过滤,然后继续执行
  • 第二部分代码就是将浏览器请求放行,如果还有过滤器,那么就继续交给下一个过滤器
  • 第三部分代码就是对返回的Web资源再次进行过滤处理

我们使用过滤器,也就是说,不支请求会经过过滤器,我们的相应也会经过过滤器。
在这里插入图片描述


过滤器(Filter)接口

我们学习过滤器,肯定就要先看一下官方给我们提供的过滤器接口。下面我们使用idea来查看Filter的源码

package jakarta.servlet;

import java.io.IOException;

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

[!NOTE]

我们通过官方提供的过滤器可以看出来过滤器(Filter)使用起来还是比较简单的,下面我们就来学习如何使用过滤器(Filter)


使用过滤器(Filter)

我们使用过滤器肯定要导入相应的jar包才行,Filter就在servlet-api.jar中,我们将该jar包放到WEB-INF下的lib目录下面,然后加入项目。


创建过滤器(Filter)

[!NOTE]

我们创建Filter,只需要继承Filter接口就行

public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        
    }
}

[!NOTE]

Filter接口有3个方法,但是只有一个方法没有实现,我们只需要实现这个方法就行。我们可以发现上面的代码实现了doFilter方法,这个方法即使我们写过滤器的地方,具体逻辑就是和上面介绍的过滤器原理一样的。


使用过滤器(Filter)

  1. 我们先来感受一下如何使用Filter,细节我们后面慢慢说明。我们在上面创建的类中写入以下代码,并且加入一个WebFilter注解

    import jakarta.servlet.*;
    import jakarta.servlet.annotation.WebFilter;
    import java.io.IOException;
    
    @WebFilter(urlPatterns = "/*")
    public class MyFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("对request进行过滤");
            //放行
            filterChain.doFilter(servletRequest, servletResponse);
            System.out.println("对response进行过滤");
        }
    }
    
  2. 如果是SpringBoot项目则需要再启动类上添加@SevletComponentScan注解

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;
    
    @SpringBootApplication
    @ServletComponentScan(basePackages = "com.jesse.springbootmybatis.filter")
    public class SpringbootMybatisApplication {}
    
  3. 如果是Spring项目则需要在web.xml文件中添加配置

    <filter>
      <filter-name>myFilter</filter-name>
      <filter-class>com.clucky.filter.MyFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>myFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    

[!NOTE]

我们简单介绍上面的代码,@WebFilter(urlPatterns = "/*")表示对所有请求的进行过滤,而在doFilter中的放行代码,也就是filterChain.doFilter(servletRequest, servletResponse);这行代码就是对拦截进行放行,细节我们后面讲,现在先这么理解就行。

启动服务,然后我们在浏览器输入http://localhost:8080/filter/random,注意,filter是我们自己配置的web工程路径,后面的random随便输入的。我们下面来查看浏览器和控制台输出

浏览器输出:

在这里插入图片描述

控制台输出:
在这里插入图片描述

[!NOTE]

现在,我们就已经可以得出两个结论了,过滤器并不会判断资源是否存在,而只会对配置拦截路径进行拦截。拦截不仅会对请求进行拦截,而且还对响应进行拦截。


配置过滤器(Filter)拦截路径

配置Filter的拦截路径有2中方式,一种是注解,一种是xml方式,我们分别进行讲解。


注解方式

如果使用注解来进行配置,那么我们就需要使用@WebFilter,我们不说废话,直接看该注解的源码。

package jakarta.servlet.annotation;

import jakarta.servlet.DispatcherType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebFilter {
    String description() default "";

    String displayName() default "";

    WebInitParam[] initParams() default {};

    String filterName() default "";

    String smallIcon() default "";

    String largeIcon() default "";

    String[] servletNames() default {};

    String[] value() default {};

    String[] urlPatterns() default {};

    DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};

    boolean asyncSupported() default false;
}

里面的配置项还是有很多的,下面我们对常用的配置项进行说明:

  • filterName: 该filter的名字
  • initParams: 初始化参数
  • displayName: filter显示名称
  • servletNames: 指定对哪些sevlet进行过滤
  • asyncSupported: 是否支持异步模式
  • urlPatterns: 指定拦截路径
  • value: 指定拦截路径

[!CAUTION]

urlPatternsvalue是一样的。urlPatternsvalue只能配置一个,不能两个都配置,两个都配置会报错。

对于使用@WebFilter,里面有多个参数使用 , 进行分隔。

@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {}

[!TIP]

说明:如果我们仅仅需要配置一个拦截路径,那么我们可以直接简写@WebFilter("/filter/*"),如:@WebFilter("/*")就是拦截所有请求


xml方式

xml方式可以说是和Servlet使用xml配置方式一样了,这里就不废话,直接配置一个

<filter>
  <filter-name>myFilter</filter-name>
  <filter-class>com.clucky.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>myFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

这个就是xml配置方式,只不过吧注解换成了xml标签配置,里面属性都是一样的,这个和Servlet的配置方式基本一样,这里就不在赘述了。


过滤器(Filter)生命周期

我们都知道Servlet有一个生命周期,当然Filter也有一个生命周期,下面我们就来探讨一下Filter的生命周期。

Filter的生命周期和Servlet也十分相似,如果大家对Servlet的生命周期不怎么熟悉,那么可以看一下Servlet生命周期。

我们创建一个类,实现Filter的所有方法。

import jakarta.servlet.*;

import java.io.IOException;

public class LifeCycleFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    }

    @Override
    public void destroy() {
        
    }
}

理论说明

Filter有3个阶段,分别是初始化、拦截和过滤、销毁

  1. init(初始化阶段): 当服务器启动时,我们的服务器(Tomcat)就会读取配置文件,扫描注解,然后来创建我们的Filter。
  2. doFilter(拦截和过滤阶段): 只要请求资源的路径和拦截的路径相同,那么过滤器就会对请求进行过滤,这个阶段在服务器运行过程中会一直循环。
  3. destroy(销毁阶段): 当Tomcat(服务器)关闭时,服务器创建的Filter也会随之销毁

代码演示

Filter的三个阶段就对应着Filter的3个方法,init方法会在Filter创建时调用,doFilter方法会在请求和拦截匹配时调用,destroy方法会在Filter销毁时调用。我们来对这些方法进行编写验证。

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns="/filter")
public class LifeCycleFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter   ----->   init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter   ----->   doFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("Filter   ----->   destroy");
    }
}

控制台输出:
在这里插入图片描述

[!NOTE]

上面的代码和控制台输出验证了我们对Filter生命周期的预想。


FilterConfig和FilterChain说明

FilterConfig和FilterChain这2个对象是由Tomcat(服务器)在创建和调用Filter对象时所传入的,这2个对象十分有用,FilterConfig对象可以读取我们配置的初始参数,FilterChain可以实现多个Filter之间的连接。


FilterConfig

老规矩,我们要学习一个对象,首先查看源代码

package jakarta.servlet;

import java.util.Enumeration;

public interface FilterConfig {
    String getFilterName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
}

里面的方法有4个,下面我们分别进行讲解

  • getFilterName(): 获取Filter的名称
  • getServletContext(): 获取ServletContext
  • getInitparamter(String var1): 获取配置的初始参数的值
  • getInitParamterNames(): 获取配置的所有参数名称

FilterConfig实例运用

我们在Filter.init()方法中使用FilterConfig来读取配置的数据库的信息,让后输出

java代码

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;
import java.util.Enumeration;

@WebFilter(urlPatterns="/filter")
public class LifeCycleFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter   ----->   init");
        System.out.println("-----------获取全部key:value------------");
        //得到所有配置参数的名字
        Enumeration<String> names = filterConfig.getInitParameterNames();
        while (names.hasMoreElements()) {
            //得到每一个名字
            String name = names.nextElement();
            System.out.println(name+" = "+filterConfig.getInitParameter(name));
        }
        System.out.println("-----------end.....------------");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter   ----->   doFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("Filter   ----->   destroy");
    }
}

xml配置

<filter>
  <filter-name>myFilterConfig</filter-name>
  <filter-class>com.clucky.filter.MyFilterConfig</filter-class>
  <init-param>
    <param-name>driver</param-name>
    <param-value>com.mysql.jdbc.Driver</param-value>
  </init-param>
  <init-param>
    <param-name>url</param-name>
    <param-value>jdbc:mysql://localhost:3306/equip_employ_manage?serverTimezone=GMT</param-value>
  </init-param>
  <init-param>
    <param-name>username</param-name>
    <param-value>root</param-value>
  </init-param>
  <init-param>
    <param-name>password</param-name>
    <param-value>root</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>myFilterConfig</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

控制台输出

img

[!NOTE]

我们使用FilterConfig提供的方法就成功实现了功能,FilterConfig就是用来读取配置文件的。


FilterChain

我们在Filter.doFilter()方法中可以找到这个对象,还是先看源代码

package jakarta.servlet;

import java.io.IOException;

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

[!NOTE]

我们查看源码,可以发现FilterChain就只有一个方法,其实这个方法即使用来对拦截进行放行的,如果有多个拦截器,那么就会继续调用下一个Filter进行拦截。doFilter()方法需要传入一个参数,一个是ServletRequest,一个是ServletResponse参数,这个直接传入进行。

Tomcat在调用过滤器时,默认就会传入Request和Response,这个参数封装了请求和响应,我们直接使用就行。

ServletRequest和ServletResponse可以直接转成HttpServletRequest和HttpServletResponse,然后使用相应的方法。

将ServletRequest和ServletResponse转成HttpServletRequest和HttpServletResponse

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
  HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
}

FilterChain应用实例

我们前面一直都是一个Filter,现在我们来配置2个Filter,通过FilterChain来进行多个过滤

第一个Filter

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/filter")
public class MyFilter01 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter01.doFilter()   --->   对request进行过滤");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("MyFilter01.doFilter()   --->   对response进行过滤");
    }
}

第二个Filter

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/filter")
public class MyFilter02 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter02.doFilter()   --->   对request进行过滤");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("MyFilter02.doFilter()   --->   对response进行过滤");
    }
}

控制台输出
在这里插入图片描述

[!NOTE]

我们可以看见MyFilter01先进行过滤,然后交给MyFilter02,然后访问资源,然后MyFilter02对相应进行过滤,然后MyFilter01对响应进行过滤。图示如下:
在这里插入图片描述

[!NOTE]

我们先使用MyFilter01对请求进行过滤,那么很自然的,我们就是使用Filter02先对响应进行过滤


多个Filter的执行顺序

上面我们配置了2个过滤器,那么我们怎么知道那个过滤器先执行呢?其实大家可以直接使用代码进行验证,培养独立思考的习惯,这里我就直接给出答案了。

  • 如果我们是在web.xml中配置的过滤器,那么过滤器的执行顺序就是<fiter-mapping>在web配置的顺序,配置在上面那么就会先执行。
  • 如果我们是使用@WebFilter进行配置的,那么执行顺序就是字符比较顺序来执行,例如有2个过滤器AFilter和BFilter,那么AFilter就会先执行
  • 如果注解和xml混用,那么在web.xml中配置的会先执行

执行顺序验证

我这里就验证第一条,也就是web.xml中配置的顺序和<filter-mapping>顺序一样,其他大家感兴趣自己验证。

xml配置顺序 3 —> 1 —> 2

    <filter>
        <filter-name>filter03</filter-name>
        <filter-class>com.clucky.filter.MyFilter03</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter03</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>filter01</filter-name>
        <filter-class>com.clucky.filter.MyFilter01</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter01</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>filter02</filter-name>
        <filter-class>com.clucky.filter.MyFilter02</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter02</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

MyFilter01

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/filter")
public class MyFilter01 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter01.doFilter()   --->   对request进行过滤");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("MyFilter01.doFilter()   --->   对response进行过滤");
    }
}

MyFilter02

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/filter")
public class MyFilter02 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter02.doFilter()   --->   对request进行过滤");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("MyFilter02.doFilter()   --->   对response进行过滤");
    }
}

MyFilter03

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/filter")
public class MyFilter03 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter03.doFilter()   --->   对request进行过滤");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("MyFilter03.doFilter()   --->   对response进行过滤");
    }
}

[!NOTE]

我们启动服务器,浏览器访问,然后查看控制台输出是不是我们配置的3 -> 1 -> 2的顺序

img


Filter应用实例(实现敏感词汇过滤)

我们学了那么多,现在来做一个实例,可以根据ip地址统计当前ip访问filter几次


代码实现

Filter代码

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;

@WebFilter(urlPatterns = "/filter")
public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("对request进行过滤");
        //强转成HttpServletRequest
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //通过HttpServletRequest拿到ip
        String ip = request.getRemoteAddr();
        //通过HttpServletRequest拿到Session
        HttpSession session = request.getSession();
        //通过Session拿到当前ip访问服务的次数
        Integer count = (Integer) session.getAttribute("count");
        //判断是否第一次访问,如果是则返回1,不是则返回登录次数自增1
        count = count == null ? 1 : ++count;
        //写入到Session中
        session.setAttribute("count", count);
        //在控制台输出访问次数
        System.out.println("ip:" + ip + "访问次数:" + count);
        //放行
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("对response进行过滤");
    }
}

SpringBoot 启动类代码

@SpringBootApplication
@ServletComponentScan(basePackages = "com.jesse.springbootmybatis.filter")
public class SpringbootMybatisApplication {

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

}

[!IMPORTANT]

前往别忘了在启动类上加上要扫描的包路径,不然Filter不生效


验证

[!NOTE]

我们输入http://localhost:8080/filter和http://127.0.0.1:8080/filter来访问服务

控制台输出
在这里插入图片描述


总结

[!IMPORTANT]

鉴于Filter会对所有请求进行拦截,实际应用场景还是对暴露的地址进行拦截。


感谢

这篇文档是在学习过程中看到CSDN中秃头披风侠.的JavaWeb过滤器(Filter)详解,是时候该把过滤器彻底搞懂了(万字说明)感觉写的很好,我自己就跟着整理了一份。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值