Filter(过滤器)
Filter 介绍
过滤器的基本概念
Servlet 过滤器从字面可理解为经过一层层的过滤处理才达到使用的要求,而其实 Servlet 过滤器就是服务器与客户端请求与响应的中间层组件。
在实际项目开发中 Servlet 过滤器主要用于对浏览器的请求进行过滤处理,将过滤后的请求再转给下一个资源。
过滤器是以一种组件的形式绑定到 Web 应用程序当中的,与其他的 Web 应用程序组件不同的是,过滤器是采用了“链”的方式进行处理的,如下所示:
Filter
Filter(过滤器)是 JavaWeb 三大组件之一(另外两个是 Servlet 和 Listener),是在 2000 年发布的 Servlet2.3 规范中加入的一个接口,是 Servlet 规范中非常实用的技术。
当需要限制用户访问某些资源或者在处理请求时提前处理某些资源的时候,就可以使用过滤器(Filter)完成。
- 当一个请求访问服务器资源时,服务器首先判断会是否有过滤器与请求资源相关联,如果有,过滤器会先将请求拦截下来,完成一些特定的功能,再由过滤器决定是否继续交给请求资源进行处理。
- 响应也是类似的。
Filter 应用场景:
- URL 级别的权限控制
- 过滤敏感词汇
- 中文乱码问题
- ...
Servlet 的 Filter 特点:
-
声明式的
通过在 web.xml 配置文件中声明,允许添加、删除过滤器,而无须改动任何应用程序代码或 JSP 页面。 -
灵活的
过滤器可用于客户端的直接调用执行预处理和后期的处理工作,通过过滤链可以实现一些灵活的功能。 -
可移植的
由于现今各个 Web 容器都是以 Servlet 的规范进行设计的,因此 Servlet 过滤器同样是跨容器的。 -
可重用的
基于其可移植性和声明式的配置方式,Filter 是可重用的。
总的来说,Servlet 的过滤器是通过一个配置文件来灵活的声明的模块化可重用组件。过滤器动态的截获传入的请求和传出的响应,在不修改程序代码的情况下,透明的添加或删除他们。其独立于任何平台和 Web 容器。
Filter API
Filter
Filter 是一个接口。如果想实现过滤器的功能,则必须实现该接口。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | init(FilterConfig config) | 初始化方法 |
void | doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | 对请求资源和响应资源进行拦截 |
void | destroy() | 销毁方法 |
配置方式:
-
方式一:使用注解
@WebFilter("拦截路径")
-
方式二:web.xml 配置
FilterChain
-
FilterChain 是一个接口,代表过滤器链对象,由 Servlet 容器提供实现类对象,我们直接使用即可。
-
过滤器可以定义多个,就会组成过滤器链。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | doFilter(ServletRequest request, ServletResponse response) | 放行方法 |
- 如果有多个过滤器,则会在第一个过滤器中再调用下一个过滤器,依次类推,直到到达最终访问资源。
- 如果只有一个过滤器,放行时,就会直接到达最终访问资源。
FilterConfig
FilterConfig 是一个接口,代表过滤器的配置对象,可以加载一些初始化参数。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
String | getFilterName() | 获取过滤器对象名称 |
String | getInitParameter(String key) | 根据 key 获取 value |
Enumeration<String> | getInitParameterNames() | 获取所有参数的 key |
ServletContext | getServletContext() | 获取应用上下文对象 |
Filter 工作原理
Filter 的体系结构
如其名字所暗示的一样,Servlet 过滤器用于拦截传入的请求和传出的响应,并监视、修改处理 Web 工程中的数据流。过滤器是一个可插入的自由组件。Web 资源可以不配置过滤器、也可以配置单个过滤器,也可以配置多个过滤器,形成一个过滤器链。Filter 接受用户的请求,并决定将请求转发给链中的下一个组件,或者终止请求直接向客户端返回一个响应。如果请求被转发了,它将被传递给链中的下一个过滤器(以 web.xml 过滤器的配置顺序为标准)。这个请求在通过过滤链并被服务器处理之后,一个响应将以相反的顺序通过该链发送回去。这样,请求和响应都得到了处理。
Filter 可以应用在客户端和 Servlet 之间、Servlet 和 Servlet 或 JSP 之间,并且可以通过配置言息,灵活的使用那个过滤器。
基于上述体系结构的描述,Filter 工作原理如下图所示:
- 客户端浏览器在访问 Web 服务器的某个具体资源的时候,经过过滤器 1 中 code l 代码块的相关处理之后,将请求传递给过滤链中的下一个过滤器 2(过滤链的顺序以配置文件中的顺序为基准)
- 过滤器 2 处理完之后,请求就根据传递的 Servlet 完成相应的逻辑。
- 返回响应的过程类似,只是过滤链的顺序相反。
Filter 生命周期
- 出生:当应用加载时,执行初始化方法。
- 活着:只要应用一直提供服务,对象就一直存在。
- 死亡:当应用卸载时(执行销毁方法)或服务器宕机时,对象消亡。
Filter 的实例对象在内存中也只有一份,所以 Filter 也是单例的。
import javax.servlet.*;
import java.io.IOException;
/*
过滤器生命周期
*/
//@WebFilter("/*")
public class FilterDemo03 implements Filter{
/*
初始化方法
*/
@Override
public void init(FilterConfig filterConfig) {
System.out.println("对象初始化成功了...");
}
/*
提供服务方法
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filterDemo03执行了...");
//处理乱码
servletResponse.setContentType("text/html;charset=UTF-8");
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
/*
对象销毁
*/
@Override
public void destroy() {
System.out.println("对象销毁了...");
}
}
Filter 实现案例
Filter 的创建过程:
要编写一个过滤器必须实现 Filter 接口,实现其接口规定的方法。
- 实现 javax.Servlet.Filter 接口;
- 实现 init() 方法,读取过滤器的初始化参数;
- 实现 doFilter()方法,完成对请求或响应的过滤;
- 调用 FilterChain 接口对象的 doFilter() 方法,向后续的过滤器传递请求或响应。
1)编写接收和处理请求的 Servlet:
public class ServletDemo1 extends HttpServlet {
/**
* 处理请求的方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletDemo1接收到了请求");
req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
2)配置 Servlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">
<!--配置Servlet-->
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/ServletDemo1</url-pattern>
</servlet-mapping>
</web-app>
3)编写 index.jsp:
<%-- Created by IntelliJ IDEA. --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主页面</title>
</head>
<body>
<a href="${pageContext.request.contextPath}/ServletDemo1">访问ServletDemo1</a>
</body>
</html>
4)编写 success.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>成功页面</title>
</head>
<body>
<% System.out.println("success.jsp执行了"); %>
执行成功!
</body>
</html>
5)编写 Filter:
public class FilterDemo1 implements Filter {
/**
* 过滤器的核心方法
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/**
* 如果不写此段代码,控制台会输出两次:FilterDemo1拦截到了请求。
*/
HttpServletRequest req = (HttpServletRequest) request;
String requestURI = req.getRequestURI();
if (requestURI.contains("favicon.ico")) {
return;
}
System.out.println("FilterDemo1拦截到了请求");
}
}
6)配置 Filter:
<!--配置过滤器-->
<filter>
<filter-name>FilterDemo1</filter-name>
<filter-class>com.itheima.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
运行结果分析:
当我们启动服务并在地址栏输入访问地址后,发现浏览器任何内容都没有,控制台却输出了【FilterDemo1拦截到了请求】,也就是说在访问任何资源的时候,都先经过了过滤器。
这是因为,我们在配置过滤器的拦截规则时,使用了/*
,表示访问当前应用下的任何资源,此过滤器都会起作用。
除了这种全部过滤的规则之外,它还支持特定类型的过滤配置。我们可以稍作调整,修改的方式如下:
新的问题是,我们拦截下来了,但点击链接发送请求,运行结果是:
对此,需要对过滤器执行放行操作,才能让它继续执行,那么如何放行的?
我们需要使用FilterChain
中的doFilter
方法放行。
继续修改:在FilterDemo1
的doFilter
方法后添加一行代码
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/**
* 如果不写此段代码,控制台会输出两次:FilterDemo1拦截到了请求。
HttpServletRequest req = (HttpServletRequest) request;
String requestURI = req.getRequestURI();
if (requestURI.contains("favicon.ico")) {
return;
}*/
System.out.println("FilterDemo1拦截到了请求");
// 过滤器放行
chain.doFilter(request,response);
// 新增一行代码
System.out.println("FilterDemo1放行之后,又回到了doFilter方法");
}
运行结果如下,我们发现过滤器放行之后执行完目标资源,最后仍会回到过滤器中。
FilterConfig:过滤器配置对象
1)新增过滤器 FilterDemo2 :
public class FilterDemo2 implements Filter {
private FilterConfig filterConfig;
/**
* 初始化方法
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("FilterDemo2的初始化方法执行了");
// 给过滤器配置对象赋值
this.filterConfig = filterConfig;
}
/**
* 过滤器的核心方法
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo2拦截到了请求");
// 根据名称获取过滤器的初始化参数
String paramValue = filterConfig.getInitParameter("filterInitParamName");
System.out.println(paramValue);
// 获取过滤器初始化参数名称的枚举
Enumeration<String> initNames = filterConfig.getInitParameterNames();
while(initNames.hasMoreElements()){
String initName = initNames.nextElement();
String initValue = filterConfig.getInitParameter(initName);
System.out.println(initName+","+initValue);
}
// 获取ServletContext对象
ServletContext servletContext = filterConfig.getServletContext();
System.out.println(servletContext);
// 获取过滤器名称
String filterName = filterConfig.getFilterName();
System.out.println(filterName);
// 过滤器放行
chain.doFilter(request, response);
}
/**
* 销毁方法
*/
@Override
public void destroy() {
System.out.println("FilterDemo2的销毁方法执行了");
}
}
2)配置 FilterDemo2 :
<filter>
<filter-name>FilterDemo2</filter-name>
<filter-class>com.itheima.web.filter.FilterDemo2</filter-class>
<!--配置过滤器的初始化参数-->
<init-param>
<param-name>filterInitParamName</param-name>
<param-value>filterInitParamValue</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterDemo2</filter-name>
<url-pattern>/ServletDemo1</url-pattern>
</filter-mapping>
运行效果:
Filter 五种拦截行为
<filter>
<filter-name>filterDemo05</filter-name>
<filter-class>com.itheima.filter.FilterDemo05</filter-class>
<!-- 配置开启异步支持,当dispatcher配置ASYNC时,需要配置此行 -->
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>filterDemo05</filter-name>
<!-- <url-pattern>/error.jsp</url-pattern> -->
<url-pattern>/index.jsp</url-pattern>
<!-- 过滤请求(默认值)-->
<dispatcher>REQUEST</dispatcher>
<!-- 过滤全局错误页面:当由服务器调用全局错误页面时,过滤器工作 -->
<dispatcher>ERROR</dispatcher>
<!-- 过滤请求转发:当请求转发时,过滤器工作 -->
<dispatcher>FORWARD</dispatcher>
<!-- 过滤请求包含:当请求包含时,过滤器工作。它只能过滤动态包含,jsp的include指令是静态包含,过滤器不会起作用 -->
<dispatcher>INCLUDE</dispatcher>
<!-- 过滤异步类型,它要求我们在filter标签中配置开启异步支持 -->
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
<!-- 配置全局错误页面 -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error.jsp</location>
</error-page>
</web-app>