在 Java Servlet 规范中,过滤器是一个非常重要的组成部分。
它允许开发者拦截传入的请求和传出的响应,从而在请求到达Servlet之前或响应返回客户端之前执行一些预处理或后处理的操作。
Filter
接口定义
查看代码
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default void destroy() {
}
}
接口方法
1. init(FilterConfig filterConfig)
- 作用:此方法用于初始化过滤器。它在过滤器被Web容器实例化之后,并且在它开始服务请求之前被调用。
- 参数:
FilterConfig
对象,它包含了过滤器的配置信息,如初始化参数。 - 异常:
ServletException
,如果初始化过程中发生错误,可以抛出此异常。 - 默认实现:从Java Servlet 3.0开始,
init
方法有一个默认实现,它什么也不做。如果过滤器不需要初始化操作,可以不覆盖此方法。
2. doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- 作用:此方法是过滤器的核心,它负责处理传入的请求和传出的响应。在这个方法中,过滤器可以实现预处理请求、后处理响应或完全阻止请求的处理。
- 参数:
ServletRequest
:封装了客户端请求信息的对象。ServletResponse
:封装了服务器响应信息的对象。FilterChain
:表示当前请求的过滤器链,通过调用chain.doFilter(request, response)
方法,可以让请求继续传递到下一个过滤器或目标Servlet。
- 异常:
IOException
和ServletException
,如果处理请求或响应时发生I/O错误或Servlet相关错误,可以抛出这些异常。 - 注意:如果过滤器决定不调用
chain.doFilter
,则请求将被阻止,不会传递到过滤器链中的下一个组件。
3. destroy()
- 作用:此方法用于在过滤器被Web容器从服务中移除之前执行清理操作。它可以用来释放过滤器占用的资源,如关闭数据库连接、文件句柄等。
- 异常:
destroy
方法没有抛出任何异常。 - 默认实现:从Java Servlet 3.0开始,
destroy
方法有一个默认实现,它什么也不做。如果过滤器不需要执行清理操作,可以不覆盖此方法。
使用过滤器
要创建和使用过滤器,需要以下步骤:
- 实现
Filter
接口:创建一个类实现Filter
接口,并覆盖init
、doFilter
和destroy
方法。 - 配置过滤器:在
web.xml
文件中或通过注解配置过滤器和过滤器的映射。 - 部署到Web容器:将包含过滤器的Web应用程序部署到支持Servlet技术的Web容器中。
Filter
生命周期
过滤器的生命周期是指从创建到销毁的整个过程。
初始化阶段
当Web应用程序启动时,或者当过滤器第一次被请求时,Web容器会实例化过滤器并调用其init
方法。
- 方法:
void init(FilterConfig filterConfig)
- 参数:
FilterConfig
对象,它包含了过滤器的配置信息,如初始化参数。 - 作用:
- 初始化过滤器实例。
- 读取配置参数,这些参数可以在
web.xml
中定义,或者在注解配置中设置。 - 打开资源,如数据库连接。
- 执行任何一次性设置操作。
服务阶段
在初始化之后,过滤器进入服务阶段,在这个阶段,过滤器会处理所有匹配其URL模式的请求。
- 方法:
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- 参数:
ServletRequest
:封装了客户端请求信息的对象。ServletResponse
:封装了服务器响应信息的对象。FilterChain
:用于调用下一个过滤器或目标Servlet的对象。
- 作用:
- 检查和修改请求对象(
ServletRequest
)。 - 检查和修改响应对象(
ServletResponse
)。 - 决定是否继续调用过滤链中的下一个组件(通过调用
chain.doFilter(request, response)
)。 - 可以在调用
chain.doFilter
之前或之后执行操作,甚至在某些情况下不调用它来阻止请求的进一步处理。
- 检查和修改请求对象(
销毁阶段
当Web应用程序停止,或者当容器决定要卸载过滤器时,容器会调用过滤器的destroy
方法。
- 方法:
void destroy()
- 作用:
- 释放过滤器所占用的资源,如关闭数据库连接、文件句柄等。
- 执行清理操作,确保过滤器的资源得到妥善处理。
- 这个方法只被调用一次,即在过滤器被销毁之前。
FilterConfig
接口
FilterConfig
接口用于在上述Filter
的init
初始化期间向过滤器传递配置信息。
接口定义
package jakarta.servlet;
import java.util.Enumeration;
public interface FilterConfig {
String getFilterName();
ServletContext getServletContext();
String getInitParameter(String name);
Enumeration<String> getInitParameterNames();
}
接口方法
1. getFilterName()
- 返回类型:
String
- 作用:返回在部署描述符(如
web.xml
)中定义的过滤器的名称。这个名称是唯一的,用于在Web应用程序中标识过滤器。 - 用途:通常用于日志记录或在需要引用过滤器名称的上下文中。
2. getServletContext()
- 返回类型:
ServletContext
- 作用:返回
ServletContext
对象,它代表了当前的Web应用程序。通过这个对象,过滤器可以与它的Servlet容器进行交互。 - 用途:允许过滤器访问Web应用程序的资源和属性,如初始化参数、日志、监听器、文件等。
3. getInitParameter(String name)
- 参数:
name
- 初始化参数的名称。 - 返回类型:
String
- 作用:返回指定名称的初始化参数的值。如果参数不存在,则返回
null
。 - 用途:用于获取在部署描述符中为过滤器设置的配置参数,如数据库连接字符串、文件路径等。
4. getInitParameterNames()
- 返回类型:
Enumeration<String>
- 作用:返回一个枚举,包含了过滤器所有初始化参数的名称。
- 用途:用于遍历所有初始化参数,通常在初始化过滤器时使用,以便读取所有配置的参数。
GenericFilter
抽象类
GenericFilter
是 Java Servlet API 中的一个抽象类,它实现了 Filter
和 FilterConfig
接口,并提供了一些方便的方法来减少编写新过滤器时的样板代码。
类定义
查看代码
package jakarta.servlet;
import java.io.Serializable;
import java.util.Enumeration;
public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
private static final long serialVersionUID = 1L;
private volatile FilterConfig filterConfig;
@Override
public String getInitParameter(String name) {
return getFilterConfig().getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return getFilterConfig().getInitParameterNames();
}
public FilterConfig getFilterConfig() {
return filterConfig;
}
@Override
public ServletContext getServletContext() {
return getFilterConfig().getServletContext();
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
init();
}
public void init() throws ServletException {
// NO-OP
}
@Override
public String getFilterName() {
return getFilterConfig().getFilterName();
}
}
类实现说明
实现的接口
Filter
: 定义了过滤器的基本行为。FilterConfig
: 提供了访问过滤器配置信息的方法。Serializable
: 表示该类可以被序列化。
类变量
private volatile FilterConfig filterConfig;
: 这个变量用于存储传递给过滤器的FilterConfig
对象。
方法说明
getInitParameter(String name)
- 作用: 调用
getFilterConfig().getInitParameter(name)
来获取指定名称的初始化参数。 - 用途: 方便子类获取配置参数。
getInitParameterNames()
- 作用: 调用
getFilterConfig().getInitParameterNames()
来获取所有初始化参数的名称。 - 用途: 方便子类遍历所有配置参数。
getFilterConfig()
- 作用: 返回
FilterConfig
对象。 - 用途: 子类可以调用此方法来直接访问
FilterConfig
。
getServletContext()
- 作用: 通过
getFilterConfig().getServletContext()
获取ServletContext
对象。 - 用途: 方便子类访问 Web 应用程序的上下文信息。
init(FilterConfig filterConfig)
- 作用: 将传入的
FilterConfig
对象赋值给filterConfig
变量,并调用init()
方法。 - 用途: 子类可以重写
init()
方法来执行初始化逻辑,而不需要调用super.init(config)
。
init()
- 作用: 默认情况下是一个空操作(NO-OP),子类可以重写此方法以执行初始化逻辑。
- 用途: 简化子类的初始化过程。
getFilterName()
- 作用: 通过
getFilterConfig().getFilterName()
获取过滤器的名称。 - 用途: 方便子类获取过滤器的名称。
FilterChain
过滤器链
FilterChain
接口是 Java Servlet API 的一部分,它提供了一种方式,让过滤器可以访问请求和响应对象,并决定是否将请求和响应传递给链中的下一个过滤器或目标资源。
接口定义
package jakarta.servlet;
import java.io.IOException;
public interface FilterChain {
void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
}
接口方法说明
1. doFilter(ServletRequest request, ServletResponse response)
- 作用: 执行过滤器的链式调用。
- 参数:
ServletRequest request
: 当前请求对象。ServletResponse response
: 当前响应对象。
- 用途:
- 如果调用过滤器不是链中的最后一个过滤器,它会调用
doFilter
方法,将请求和响应传递给链中的下一个过滤器。 - 如果调用过滤器是链中的最后一个过滤器,它会调用链中的目标资源(通常是Servlet或JSP页面)来处理请求和响应。
- 如果调用过滤器不是链中的最后一个过滤器,它会调用
使用 FilterChain
当一个过滤器接收到请求时,它会使用 FilterChain
来决定如何处理这个请求。如果过滤器需要继续传递请求,它会调用 doFilter
方法,将请求和响应传递给链中的下一个过滤器。如果过滤器是链中的最后一个过滤器,它会调用链中的目标资源来处理请求和响应。
通过使用 FilterChain
,可以构建一个过滤器链,其中每个过滤器都可以执行自己的逻辑,并决定是否继续传递请求。这样可以实现更复杂的过滤器功能,例如,一个过滤器可以进行身份验证,另一个过滤器可以进行日志记录,而第三个过滤器可以压缩响应数据。
Spring过滤器
FilterRegistrationBean
FilterRegistrationBean
是 Spring Boot 提供的一个类,用于在 Servlet 3.0+ 容器中注册 Filter
。
类定义
public class FilterRegistrationBean<T extends Filter> extends AbstractFilterRegistrationBean<T> {
// ...
}
FilterRegistrationBean
是一个泛型类,它继承自AbstractFilterRegistrationBean
。泛型参数T
必须是一个Filter
的子类,这意味着FilterRegistrationBean
用于封装任何类型的Filter
。
构造函数
public FilterRegistrationBean() {
}
public FilterRegistrationBean(T filter, ServletRegistrationBean<?>... servletRegistrationBeans) {
super(servletRegistrationBeans);
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
}
- 第一个构造函数是一个无参构造函数,用于创建一个空的
FilterRegistrationBean
,稍后可以通过setFilter
方法设置Filter
。 - 第二个构造函数接受一个
Filter
实例和一个可变参数的ServletRegistrationBean
数组。这个构造函数在创建FilterRegistrationBean
时直接设置Filter
,并且可以与一个或多个ServletRegistrationBean
相关联。
getFilter 方法
@Override
public T getFilter() {
return this.filter;
}
这个方法返回FilterRegistrationBean
中封装的Filter
实例。
setFilter 方法
public void setFilter(T filter) {
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
}
这个方法允许我们设置FilterRegistrationBean
中的Filter
。在调用此方法之前,必须确保filter
参数不为null
。
类的用途
FilterRegistrationBean
类用于在Spring Boot应用程序中注册Servlet过滤器。以下是它的主要用途:
Spring Bean友好设计
它允许我们将Filter
作为一个Spring Bean来配置,这样就可以利用Spring的依赖注入和其他特性。
配置过滤器的URL模式
我们可以通过FilterRegistrationBean
来指定过滤器应该应用到哪些URL模式上。
如果不指定URL模式或Servlet,过滤器将默认与'/*' URL模式关联。
如果不指定过滤器名称,Spring Boot会自动生成一个名称。
关联到Servlet
在Web应用程序中,Servlet是用来处理HTTP请求的Java类,而Filter是用来在请求到达Servlet之前或之后执行某些操作的组件。有时候,我们可能希望一个特定的Filter只针对某个或某些特定的Servlet起作用。
FilterRegistrationBean
提供了这样的功能:
通过名称关联:我们可以指定一个或多个Servlet的名称,然后告诉
FilterRegistrationBean
将我们的Filter与这些Servlet关联起来。这样,只有当请求匹配到这些Servlet时,Filter才会被调用。例如,如果我们有一个名为
myServlet
的Servlet,我们可以在FilterRegistrationBean
中设置与之关联的Servlet名称:javaFilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new MyFilter()); filterRegistrationBean.setServletNames("myServlet");
通过ServletRegistrationBean关联:如果我们已经有一个
ServletRegistrationBean
来注册Servlet,我们可以直接将这个ServletRegistrationBean
传递给FilterRegistrationBean
,从而将Filter与该Servlet关联起来。javaServletRegistrationBean<MyServlet> servletRegistrationBean = new ServletRegistrationBean<>(new MyServlet(), "/myServletPath"); FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new MyFilter()); filterRegistrationBean.setServletRegistrationBeans(servletRegistrationBean);
自动注册到Servlet容器
在传统的Servlet应用程序中,我们需要手动在web.xml
文件中配置Filter,或者在代码中使用Servlet API进行注册。Spring Boot简化了这个过程:
- 当我们使用
FilterRegistrationBean
来定义一个Filter时,Spring Boot会在应用程序启动时自动将该Filter注册到Servlet容器中。我们不需要直接与Servlet容器的API交互。 - 这是通过Spring Boot的自动配置机制实现的。
FilterRegistrationBean
实现了ServletContextInitializer
接口,这个接口的onStartup
方法会在应用程序启动时被调用,从而将Filter注册到Servlet容器。
@Bean
public FilterRegistrationBean<MyFilter> myFilterRegistration() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new MyFilter());
registration.addUrlPatterns("/myPath/*");
return registration;
}
在上面的例子中,当Spring Boot应用程序启动时,它会自动注册MyFilter
,并使其作用于所有匹配/myPath/*
的URL请求。这样,我们就不需要手动调用Servlet容器的注册方法,Spring Boot会为我们处理这一切。
如果我们不希望Spring Boot自动将过滤器注册到Servlet容器中(比如我们已经通过Spring Security或其他方式注册了过滤器),我们可以通过设置 FilterRegistrationBean
的 enabled
属性为 false
来禁用这种自动注册:
registration.setEnabled(false);
GenericFilterBean
GenericFilterBean
是一个抽象类,实现了jakarta.servlet.Filter
接口,提供了一个简单的基础实现,用于将过滤器的配置参数(init-param
)作为Bean属性处理。
Spring 可以注入依赖到过滤器中,并管理过滤器的生命周期,例如调用 afterPropertiesSet()
和 destroy()
方法。
Spring 提供了额外的生命周期回调,比如 InitializingBean
和 DisposableBean
接口中的方法。
类定义
package org.springframework.web.filter;
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {
// 类实现
}
类实现说明
实现的接口
Filter
: 定义了过滤器的基本行为。BeanNameAware
: 提供了访问过滤器Bean名称的方法。EnvironmentAware
: 提供了访问过滤器运行环境的方法。EnvironmentCapable
: 提供了访问过滤器环境的方法。ServletContextAware
: 提供了访问过滤器运行的Servlet上下文的方法。InitializingBean
: 提供了过滤器初始化逻辑的方法。DisposableBean
: 提供了过滤器清理逻辑的方法。
类变量
protected final Log logger = LogFactory.getLog(getClass());
: 用于记录日志。@Nullable private String beanName;
: 存储过滤器Bean的名称。@Nullable private Environment environment;
: 存储过滤器运行的环境。@Nullable private ServletContext servletContext;
: 存储过滤器运行的Servlet上下文。@Nullable private FilterConfig filterConfig;
: 存储过滤器的配置信息。private final Set<String> requiredProperties = new HashSet<>(4);
: 存储过滤器必需的配置参数名称。
OncePerRequestFilter
OncePerRequestFilter
是一个抽象类,它是 Spring 框架的一部分,用于确保每个请求只执行一次过滤器。这个类继承自 GenericFilterBean
,提供了过滤器配置参数与过滤器 Bean 属性之间的映射功能。
类定义
package org.springframework.web.filter;
public abstract class OncePerRequestFilter extends GenericFilterBean {
// 类实现
}
类实现说明
实现的接口
GenericFilterBean
: 提供了setFilterConfig
方法,用于设置FilterConfig
对象。
类变量
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
: 这是一个静态常量,用于在请求属性中标识请求是否已经被过滤过。
方法说明
doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
- 作用: 此方法是过滤器的核心,负责执行过滤逻辑。
- 用途: 它首先检查请求是否已经被过滤过,如果没有,则调用
doFilterInternal
方法执行实际的过滤逻辑。
doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
- 作用: 此方法是过滤器的内部实现,确保在同一个请求中只执行一次。
- 用途: 子类需要实现这个方法来执行具体的过滤逻辑。
skipDispatch(HttpServletRequest request)
- 作用: 判断是否应该跳过过滤器的执行。
- 用途: 如果请求是异步的或者是一个错误请求,并且过滤器不应该处理这种情况,则返回
true
,表示不需要执行过滤器。
isAsyncDispatch(HttpServletRequest request)
- 作用: 判断请求是否是异步请求。
- 用途: 用于确定是否在异步请求中执行过滤器。
isAsyncStarted(HttpServletRequest request)
- 作用: 判断请求是否已经启动了异步处理。
- 用途: 用于确定当前请求是否已经开始异步处理。
getAlreadyFilteredAttributeName()
- 作用: 返回用于标识请求是否已经被过滤过的请求属性名称。
- 用途: 用于在请求中存储过滤状态。
shouldNotFilter(HttpServletRequest request)
- 作用: 判断过滤器是否应该过滤当前请求。
- 用途: 子类可以重写这个方法来控制过滤器的执行。
shouldNotFilterAsyncDispatch()
- 作用: 判断过滤器是否不应该在异步请求中执行。
- 用途: 子类可以重写这个方法来控制过滤器对异步请求的处理。
shouldNotFilterErrorDispatch()
- 作用: 判断过滤器是否不应该在错误请求中执行。
- 用途: 子类可以重写这个方法来控制过滤器对错误请求的处理。
doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
- 作用: 过滤嵌套的错误请求。
- 用途: 用于过滤器在错误请求中的嵌套情况。
主要方法
doFilter
在OncePerRequestFilter
类中,doFilter
方法被声明为final
,这意味着子类无法重写这个方法,从而保证了过滤器逻辑的执行只会在请求首次到达时进行。该方法内部包含一个抽象方法doFilterInternal
,这是子类需要实现的地方,以定义具体的业务逻辑。
查看代码
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 确保请求和响应都是HttpServletRequest和HttpServletResponse类型,因为OncePerRequestFilter只支持HTTP请求。
if (!((request instanceof HttpServletRequest httpRequest) && (response instanceof HttpServletResponse httpResponse))) {
throw new ServletException("OncePerRequestFilter only supports HTTP requests");
}
// 获取过滤器名称,用于创建请求属性名。
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
// 检查请求中是否已经有"already filtered"的请求属性。
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
// 判断是否应该跳过过滤器的执行,例如异步请求或错误请求。
if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// 如果应该跳过,直接调用filterChain的doFilter方法,不执行过滤器的逻辑。
filterChain.doFilter(request, response);
}
else if (hasAlreadyFilteredAttribute) {
// 如果请求已经被过滤过,可能是错误请求,那么在处理嵌套错误请求时直接调用filterChain的doFilter方法。
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
// 如果请求已经被过滤过,直接调用filterChain的doFilter方法,不执行过滤器的逻辑。
filterChain.doFilter(request, response);
}
else {
// 如果请求还没有被过滤过,设置请求属性,表示请求已经被过滤过。
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 调用doFilterInternal方法执行过滤器的实际逻辑。
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// 执行完doFilterInternal方法后,移除请求属性,以便下一次调用时重新检查。
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
doFilterInternal
protected abstract void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException;
doFilterInternal
方法是 OncePerRequestFilter
类中用于执行过滤器业务逻辑的核心方法。由于它是抽象的,子类需要提供具体的实现,以确保过滤器的功能。这个方法在请求的生命周期内只会被调用一次,保证了每个请求只执行一次过滤器的逻辑。