目录
一、Filter快速入门
1.基本介绍 :
Filter,过滤器,是JavaWeb的三大组件之一 (Servlet 程序、 Listener 监听器、 Filter过滤器 )。Listener和Filter本质也属于Servlet规范,但由于其独立的功能而单独作为了JavaWeb三大组件。Filter是接口,使用频率很高。Filter除了可以解决传统验证方式造成的代码冗余,功能重复的问题,还可以应用于日志操作,权限检查,事务管理等场景。
2.基本原理 :
(1) 当浏览器端向服务器端发送过来HTTP请求时,Tomcat会根据web.xml配置文件中配置的过滤器和指定过滤器的url-pattern规则——来判断当前请求是否需要走过滤器。PS : 过滤的规则可以由程序员手动指定。
(2) 如果判断不需要走过滤器,就直接访问Web资源(servlet,web静态页面等)。
(3) 如果判断需要,Tomcat就会根据业务需求进行验证,如果验证合法,就继续访问;如果验证不合法,就进行返回。PS : 具体返回的URL也可以由程序员手动指定。
(4) Tomcat在调用servlet等Web资源之前,会先进行Filter的匹配——即根据请求的URL(由req对象封装),到管理Filter的URL的容器中去匹配,若匹配成功,再去管理Filter的容器中找到对应的Filter实例,并调用它的doFilter方法;若没有匹配成功,就直接访问web资源。(联系手写Tomcat底层中用于管理servlet的两个Map容器)
3.入门实例 :
定义login.jsp页面,用于用户登录的操作,login表单的数据提交到LoginCheckServlet,在LoginCheckServlet的doPost方法中进行判断,若password等于233,认为是管理员登录,就请求重定向到administration.jsp页面(用户管理页面)。
administration.jsp页面定义在target包下,定义过滤器LoginFilter,过滤规则为/target/*。
(1) 当我们在浏览器地址栏直接访问target包下的administration.jsp页面时,就会被配置的LoginFilter过滤器所拦截。过滤器中会进行判断,发现当前session域中不存在username属性,说明属于非法访问,就请求转发到login.jsp页面。
(2) 当我们在login.jsp页面输出用户名和密码并点击“submit”后,表单会提交到LoginCheckServlet中,进行判断,若密码正确,就将username放入到session域中,并请求重定向到用户管理页面,注意,由于此时是请求重定向的方式,所以请求会再次被LoginFilter过滤器拦截,此时session域中已经存在username属性,说明属于合法访问,此时过滤器会通过doFilter()方法放行,继续访问administration.jsp页面。
login.jsp页面代码如下 :
<%--
User : Cyan_RA9
Version : 21.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>login</title>
<style>
table, td {
border: cornflowerblue 2px solid;
background-color: lightcyan;
border-collapse: collapse;
padding: 5px;
}
</style>
</head>
<body>
<form action="<%= request.getContextPath() %>/loginCheckServlet" method="post">
<table>
<tr>
<th colspan="2">Manage the login</th>
</tr>
<tr>
<td>Username : </td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>Password : </td>
<td><input type="password" name="password"/></td>
</tr>
<tr>
<td><input type="submit" value="submit"/></td>
<td><input type="reset" value="reset"/></td>
</tr>
</table>
</form>
</body>
</html>
页面效果如下 :
LoginCheckServlet类代码如下 :
package filter;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
@WebServlet(urlPatterns = {"/loginCheckServlet"})
public class LoginCheckServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
RequestDispatcher requestDispatcher = null;
if ("233".equals(password)) {
//若验证合法,向session中放入用户名(LoginFilter会用到)
req.getSession().setAttribute("username", username);
//requestDispatcher = req.getRequestDispatcher("/target/administration.jsp");
//requestDispatcher.forward(req, resp);
//请求重定向到管理页面(因为是请求重定向,所以此时会走LoginFilter)
resp.sendRedirect("/Listener_Filter/target/administration.jsp");
} else {
requestDispatcher = req.getRequestDispatcher("/login.jsp");
requestDispatcher.forward(req, resp);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
administration.jsp页面代码如下 :
<%--
User : Cyan_RA9
Version : 21.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>target</title>
<%--<base href="<%= request.getContextPath() %>/img/"/>--%>
<style>
table, td {
border: 2px lightpink solid;
background-color: lightcyan;
border-collapse: collapse;
padding: 10px;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<table width="500px">
<tr>
<th>ID</th>
<th>Username</th>
<th>Function</th>
</tr>
<tr>
<td>100011</td>
<td>Cyan</td>
<td><a href="none">删除用户</a></td>
</tr>
<tr>
<td>100023</td>
<td>Rain</td>
<td><a href="none">删除用户</a></td>
</tr>
<tr>
<td>100033</td>
<td>Ice</td>
<td><a href="none">删除用户</a></td>
</tr>
<tr>
<td>100041</td>
<td>Five</td>
<td><a href="none">删除用户</a></td>
</tr>
<tr>
<td>100099</td>
<td>Irving</td>
<td><a href="none">删除用户</a></td>
</tr>
</table>
</body>
</html>
页面效果如下 :
定义LoginFilter过滤器对用户的非法访问进行拦截,防止非管理员非法访问用户管理界面。LoginFilter类代码如下 :
package filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("LoginFilter is initialized~");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("LoginFilter's doFilter method is invoked~");
/**
* (1) 每次调用该Filter对象时,都会动态绑定调用doFilter方法;
* (2) 若doFilter方法中没有调用继续请求的方法,那么对请求的资源的访问就会卡在这里。
* (3) Tomcat在调用Filter之前,就已经创建好了req和resp对象,并且req中已经封装好了
* HTTP请求的相关信息。因此,可以通过req对象来获取到这些信息,例如URL,session,
* 等等,从而实现日志操作,权限检查,事务管理等业务需求。
* (4) 可以通过filterChain对象的doFilter方法将servletRequest对象和
* servletResponse对象传递下去。
* (5) 特别注意:请求转发不经过过滤器!(在服务器端)
*/
//多态---动态绑定的使用
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
//session中的属性可能之后会用到(eg : log),因此可以单独做接收。
String username = (String) session.getAttribute("username");
if (username != null) {
System.out.println("username = " + username);
filterChain.doFilter(servletRequest, servletResponse);
} else {
RequestDispatcher requestDispatcher = servletRequest.getRequestDispatcher("/login.jsp");
requestDispatcher.forward(servletRequest, servletResponse);
}
/**
* 关于filterChain.doFilter方法 :
* (1) 该方法执行,会继续访问URL的目标资源;
* (2) 创建好的servletRequest对象和servletResponse对象会传递给目标资源(servlet/jsp,etc)
* (3) 因此,目标资源中获得的这两个对象是相同的对象 (同一次HTTP请求中)
*/
}
@Override
public void destroy() {
System.out.println("LoginFilter is destroyed~");
}
}
在web.xml配置文件中配置Filter,代码如下 :
<?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_4_0.xsd"
version="4.0">
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/target/*</url-pattern>
</filter-mapping>
<!--
此处的<url-pattern>即指过滤规则,当请求的URL满足该条件时,
都需要走该过滤器。这时Tomcat会调用该Filter的doFilter()方法。
(回顾URL四种匹配模式)
-->
</web-app>
运行效果 : (如下GIF)
4.生命周期 :
(1) 当Web工程启动时,Tomcat会根据反射机制创建对应的Filter实例(一个Filter只创建一次),并放入Tomcat维护的容器中保存。
(2) Tomcat会执行对应Filter的默认无参构造器和init方法(一次HTTP请求中,init方法只会调用一次)。Filter实例会常驻内存。
(3) 在创建Filter实例时,Tomcat会同时创建一个FilterConfig对象,并通过init方法传入该对象。程序员可以通过FilterConfig对象获取到该Filter的相关配置信息。
(4) 当一个HTTP请求从客户端发来时,Tomcat会判断该HTTP请求的URL是否与某个过滤器的<url-pattern>相匹配,若匹配,就会调用对应过滤器的doFilter方法。并且,Tomcat会同时创建ServletRequest对象 和 ServletResponse对象,以及FilterChain对象,并通过doFilter方法传入。
(5) Web工程停止时,销毁Filter实例,并调用destroy方法。
二、FilterConfig和FilterChain
1.FilterConfig :
1° 基本介绍
FilterConfig是Filter过滤器的配置类。FilterConfig对象的作用是获取Filter过滤器的配置内容。
2° 应用实例
在web.xml中重新配置一个filter,web.xml代码如下 : (拦截规则为 /Cyan_RA9/*)
<?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_4_0.xsd"
version="4.0">
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/target/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>FilterConfig_Demo</filter-name>
<filter-class>filter.FilterConfig_Demo</filter-class>
<init-param>
<param-name>color</param-name>
<param-value>cyan</param-value>
</init-param>
<init-param>
<param-name>sport</param-name>
<param-value>basketball</param-value>
</init-param>
<init-param>
<param-name>fruit</param-name>
<param-value>grape</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterConfig_Demo</filter-name>
<url-pattern>/Cyan_RA9/*</url-pattern>
</filter-mapping>
</web-app>
FilterConfig_Demo类代码如下 :
package filter;
import jakarta.servlet.*;
import java.io.IOException;
import java.util.Enumeration;
public class FilterConfig_Demo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("FilterConfig_Demo's init is invoked~");
//获取当前过滤器的名称
String filterName = filterConfig.getFilterName();
System.out.println("filterName = " + filterName);
//获取当前过滤器中配置的参数(根据name获取指定参数)
String color = filterConfig.getInitParameter("color");
String fruit = filterConfig.getInitParameter("fruit");
String sport = filterConfig.getInitParameter("sport");
System.out.println("color = " + color);
System.out.println("fruit = " + fruit);
System.out.println("sport = " + sport);
//获取当前过滤器中配置的全部参数
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
System.out.println("parameter's name = " + initParameterNames.nextElement());
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("FilterConfig_Demo's doFilter is invoked~");
RequestDispatcher requestDispatcher = servletRequest.getRequestDispatcher("/login.jsp");
requestDispatcher.forward(servletRequest, servletResponse);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
运行效果 : (如下GIF)
2.FilterChain :
1° 基本介绍
FilterChain,过滤器链。在处理某些复杂业务时,一个过滤器不够,可以设计多个过滤器共同完成过滤任务,形成过滤器链。
2° 基本原理
如下图所示 :
3° 应用实例
定义两个过滤器,分别为Filter_Demo1和Filter_Demo2,并且在web.xml配置文件中按照1--->2的顺序配置。
web.xml配置文件代码如下 :
<?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_4_0.xsd"
version="4.0">
<filter>
<filter-name>Filter_Demo1</filter-name>
<filter-class>filter.Filter_Demo1</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter_Demo1</filter-name>
<url-pattern>/img/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>Filter_Demo2</filter-name>
<filter-class>filter.Filter_Demo2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter_Demo2</filter-name>
<url-pattern>/img/cornflower.jpg</url-pattern>
</filter-mapping>
</web-app>
Filter_Demo1类代码如下 :
package filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter_Demo1 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter_Demo1 ———— doFilter's fore code");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter_Demo1 ———— doFilter's end code");
}
}
Filter_Demo2类代码如下 :
package filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter_Demo2 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter_Demo2 ———— doFilter's fore code");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter_Demo2 ———— doFilter's end code");
}
}
运行效果 : (GIF)
4° 使用细节
(1) 同一次HTTP请求中,由同一线程负责通过多个Filter以及对目标资源的访问。(且多个Filter使用同一个request对象)
(2) 只有当HTTP请求的URL与配置的过滤器的url-pattern匹配时,过滤器的doFilter方法才会被执行;并且如果同一次HTTP请求中有多个Filter被匹配成功,就会顺序执行,形成一个Filter调用链。(多个Filter的执行顺序,与web.xml配置文件中配置的顺序一致)
(3) filterChain.doFilter()方法执行时,将执行下一个匹配到的过滤器的doFilter方法。如果当前过滤器之后已经没有其他匹配到的过滤器,就执行到目标资源。
System.out.println("END---------------------------------------------------------------------------------------");