基本介绍
过滤器,顾名思义就是对事物进行过滤的,在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)
-
我们先来感受一下如何使用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进行过滤"); } }
-
如果是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 {}
-
如果是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]
urlPatterns
和value
是一样的。urlPatterns
和value
只能配置一个,不能两个都配置,两个都配置会报错。
对于使用@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个阶段,分别是初始化、拦截和过滤、销毁
- init(初始化阶段): 当服务器启动时,我们的服务器(Tomcat)就会读取配置文件,扫描注解,然后来创建我们的Filter。
- doFilter(拦截和过滤阶段): 只要请求资源的路径和拦截的路径相同,那么过滤器就会对请求进行过滤,这个阶段在服务器运行过程中会一直循环。
- 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>
控制台输出
[!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的顺序
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)详解,是时候该把过滤器彻底搞懂了(万字说明)感觉写的很好,我自己就跟着整理了一份。