1、Filter 什么是过滤器
1、Filter 过滤器它是 JavaWeb 的三大组件之一。三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器
2、Filter 过滤器它是 JavaEE 的规范。也就是接口
3、Filter 过滤器它的作用是:拦截请求(常用)
,过滤响应(不常用
)。
拦截请求常见的应用场景有:
1、权限检查
2、日记操作
3、事务管理
……等等
2、Filter 的初体验
需求:在你的动态web 工程下,有一个 /web/admin 目录。这个 admin 目录下的所有资源(html 页面、jpg 图片、jsp 文件、等等)都必须是用户登录之后才允许访问。
思考:根据之前我们学过内容。我们知道,用户登录之后都会把用户登录的信息保存到 Session 域中。所以要检查用户是否登录,可以判断 Session 中否包含有用户登录的信息即可!!!
2.1、不使用过滤器之前如何实现(不进行登录)
缺点:判断用户是否登录的代码只能写在jsp页面中,对图片、html页面无法过滤。
因为:只有在jsp页面中才能写java代码获取session的数据进行判断。
案例步骤:
- 创建动态web项目,进行相关配置,tomact实例名 项目路径。
- 创建目录结构文件。
a.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
我是a.html文件
</body>
</html>
a.jsp:
<%--
Created by IntelliJ IDEA.
User: Think
Date: 2022/3/3
Time: 16:23
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
Object user = session.getAttribute("user");
//如果等于null,说明用户还没有登录
if(user==null){
//为什么需要用转发跳转页面???
//1.因为重定向会多一次发送请求会造成访问压力大
//2.这个地方是为了判断session中是否有数据,用的是写在jsp中的
// java代码,在java代码中跳转页面可以是转发和重定向(没学框架之前),用
// html标签跳转就和业务没有啥关联了。
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
}
%>
我是a.jsp文件
</body>
</html>
2.jpg图片:
login.jsp:
<%--
Created by IntelliJ IDEA.
User: Think
Date: 2022/3/3
Time: 16:34
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
这是登录页面,login.jsp页面。
</body>
</html>
业务执行流程:启动tomact服务器,在浏览器输入访问a.jsp页面的地址,由于写在a.jsp页面中的session中没有获取到用户的信息,说明用户还没有登录,所以转发给登录页面。
2.2、使用Filter过滤器(不进行登录),缓存
Filter 过滤器的使用步骤:
1、编写一个类去实现 Filter 接口
2、实现过滤方法 doFilter()---->方法中写请求拦截后的权限检查。
3、到 web.xml 中去配置 Filter 的拦截路径
Filter 的工作流程图:
客户端发送请求,过滤器通过在web.xml中配置的访问路径对请求进行拦截(不在拦截路径范围内的请求继续执行),在拦截范围内的请求会通过web.xml中配置的全称限定类名访问到具体的Filter过滤器,之后会调用doFilter()方法对拦截请求中的数据进行处理,如果session没有数据说明用户没有登录 跳转到登录页面进行登录。如果有数据说明登录成功,程序继续执行原本的业务。
Filter 过滤器的代码:
package com.cn.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class AdminFilter implements Filter {//注意不要导错包
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/**
* doFilter方法,专门用于拦截请求,可以用来做权限检查
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//注意:session会话(域对象)是通过HttpServletRequest 对象获取的,这个实现过滤器
// 的类所重写的方法里面是ServletRequest类型,所以这里需要强转
// 大类型ServletRequest 强转为小类型HttpServletRequest
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//如果等于null,说明用户还没有登录
if(user==null){
//注意:这个地方重写方法参数是servletRequest servletResponse
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest, servletResponse);
return;
}else{
// 让程序继续向下访问用户的目标资源(用户访问啥,它就接着继续向下访问)
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
}
}
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 过滤器-->
<filter>
<!--给filter 起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置filter 的全类名-->
<filter-class>com.cn.filter.AdminFilter</filter-class>
</filter>
<!--filter-mapping 配置Filter 过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给哪个filter 使用-->
<filter-name>AdminFilter</filter-name>
<!--url-pattern 配置拦截路径:
/ 表示请求地址为:
http://ip:port/工程路径/ 映射到IDEA 的 web 目录
/admin/* 表示请求地址为:
http://ip:port/工程路径/admin/*) 拦截admin目录下的所有资源
-->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
</web-app>
启动tomact服务器再次访问admin目录下的文件,发现跳转的是登录页面。
注意:图片有可能访问到,因为是使用的之前访问时浏览器的缓存,相当于此时没有走服务器而是用的是缓存的图片。
解决:
方式一:可以在请求地址上添加请求参数,它会辨认是新的请求,就不会再使用缓存了。
方式二:清除缓存
2.3、完整的用户登录(进行登录)
其它页面同上:a.html,a.jsp,2.jpg,AdminFilter过滤器
修改login.jsp 页面添加登录表单:
<%--
Created by IntelliJ IDEA.
User: Think
Date: 2022/3/3
Time: 16:34
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
这是登录页面,login.jsp页面。<br/>
<form action="http://localhost:8080/dongtai-web-Filter/loginServlet" method="get">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/>
<input type="submit" value="登录">
</form>
</body>
</html>
LoginServlet 程序:
package com.cn.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = -8202176576584541949L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=UTF-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if ("wzg168".equals(username) && "123456".equals(password)) {
req.getSession().setAttribute("user",username);
resp.getWriter().write("登录 成功!!!");
} else {
req.getRequestDispatcher("/login.jsp").forward(req,resp);
}
}
}
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 过滤器-->
<filter>
<!--给filter 起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置filter 的全类名-->
<filter-class>com.cn.filter.AdminFilter</filter-class>
</filter>
<!--filter-mapping 配置Filter 过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给哪个filter 使用-->
<filter-name>AdminFilter</filter-name>
<!--url-pattern 配置拦截路径
/ 表示请求地址为:http://ip:port/工程路径/ 映射到IDEA 的 web 目录
/admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
-->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
<!--配置servlet:-->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.cn.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/loginServlet</url-pattern>
</servlet-mapping>
</web-app>
简略执行流程:
第一次发送请求:客户端发送请求被过滤器拦截,在过滤器的doFilter方法中获取session的数据,如果有用户名信息说明登陆成功程序放行,如果没有信息说明登陆失败跳转到登录页面。
第二次发送请求:在登录页面填写用户信息发送请求,在servlet中获取请求参数,如果输入的用户名密码错误,则继续返回给登录页面进行登录。如果用户名 密码正确则保存到session中,提示登陆成功。
第三次发送请求:请求被过滤器拦截后发现有session拦截后发现有session数据,则请求放行。
完整的执行流程:
- 启动tomact服务器,在浏览器输入访问admin下的a.html页面的url,由于这个浏览器地址处于web.xml中配置的拦截范围内,所以这个请求会被过滤器Filter拦截(不在拦截范围内的请求放行)。
- 之后通过配置文件中的配置的全称限定类名会定位到具体哪个Fileter过滤器类,之后会自动调用doFilter方法。
- 在doFilter方法中获取拦截请求中的session是否有用户的数据,有说明登录成功继续执行原来的请求(请求放行)。
- 如果session中没有数据 说明用户没有登录,跳转到登录页面进行登录。
- 在登录页面输入用户名、密码等数据,发送请求给服务器 (不在拦截范围内所以不会拦截),根据在web.xml中配置的信息定位到servlet。
- 之后在servlet的get/post方法中,通过request对象获取请求参数的信息(用户名,密码)是否为数据库中保存的用户名密码,如果是 则把用户的信息保存到sessin作用域中 提示登录成功,之后浏览器再次发送请求被过滤器拦截,发现session中有用户的信息,则放行。如果用户名,密码是错误的则 跳转到登录页面进行登录。
第二次在浏览器中输入访问admin.html的地址,由于登录成功了,所以可以进行访问。
3、Filter 的生命周期(类似于servlet)
Filter 的生命周期包含几个方法:
1、构造器方法
2、init 初始化方法
第 1,2 步,在 web 工程启动的时候执行(此时Filter 已经创建成功)
3、doFilter 过滤方法
第 3 步,每次拦截到请求,就会执行
4、destroy 销毁
第 4 步,停止 web 工程的时候,就会执行(停止 web 工程,也会销毁 Filter 过滤器)
测试:
web.xml略:
AdminFilter过滤器:
package com.cn.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class AdminFilter implements Filter {//注意不到导错包
public AdminFilter(){
System.out.println("1.Filter的构造器方法AdminFilter()");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2.Filter的初始化方法init()");
}
/**
* doFilter方法,专门用于拦截请求,可以用来做权限检查
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("3.Filter的过滤方法doFilter()");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//如果等于null,说明用户还没有登录
if(user==null){
//注意:这个地方重写方法参数是servletRequest servletResponse
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest, servletResponse);
return;
}else{
// 让程序继续往下访问用户的目标资源(用户访问啥你就让他访问了)
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
System.out.println("4.Filter的销毁方法destroy()");
}
}
分别启动tomact服务器,访问a.html,停止tomact服务器进程测试,可以看到对应的方法执行了。
4、FilterConfig 类(类似于ServletConfig)
FilterConfig 类见名知义,它是 Filter 过滤器的配置文件类。
Tomcat
每次创建 Filter 的时候,也会同时创建一个 FilterConfig 类
,这里包含了 Filter 配置文件的配置信息。
说明:FilterConfig 类的作用是获取 filter 过滤器的web.xml文件的配置内容
1、获取 Filter 的名称 filter-name 的内容
2、获取在 Filter 中配置的 init-param 初始化参数
3、获取 ServletContext 对象
说明:servlet是通过ServletConfig 类(配置信息类)来获取ServletContext 对象,而过滤器是通过FilterConfig (配置信息类)来获取ServletContext 对象。
java 代码:
package com.cn.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class AdminFilter implements Filter {//注意不到导错包
public AdminFilter(){
System.out.println("1.Filter的构造器方法AdminFilter()");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2.Filter的初始化方法init()");
// 1、获取 Filter 的名称 filter-name 的内容 (在web.xml中配置的别名)
System.out.println("filter-name的值是:"+filterConfig.getFilterName());//AdminFilter
// 2、获取在 web.xml中配置的 init-param 初始化参数
System.out.println("初始化参数username的值是:"+filterConfig.getInitParameter("username"));//root
System.out.println("初始化参数url的值是:"+filterConfig.getInitParameter("url"));//jdbc:mysql://localhost:3306/test
// 3、获取 ServletContext 对象
System.out.println("ServletContext对象是:"+filterConfig.getServletContext());
}
/**
* doFilter方法,专门用于拦截请求,可以用来做权限检查
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("3.Filter的过滤方法doFilter()");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//如果等于null,说明用户还没有登录
if(user==null){
//注意:这个地方重写方法参数是servletRequest servletResponse
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest, servletResponse);
return;
}else{
// 让程序继续往下访问用户的目标资源(用户访问啥你就让他访问了)
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
System.out.println("4.Filter的销毁方法destroy()");
}
}
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 过滤器-->
<filter>
<!--给filter 起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置filter 的全类名-->
<filter-class>com.cn.filter.AdminFilter</filter-class>
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/test</param-value>
</init-param>
</filter>
<!--filter-mapping 配置Filter 过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给哪个filter 使用-->
<filter-name>AdminFilter</filter-name>
<!--url-pattern 配置拦截路径
/ 表示请求地址为:http://ip:port/工程路径/ 映射到IDEA 的 web 目录
/admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
-->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
</web-app>
5、FilterChain 过滤器链
Filter 过滤器
Chain 链,链条
FilterChain 就是过滤器链(多个过滤器如何一起工作)
5.1、多个过滤器链正常执行顺序
说明:多个Filter共同执行的时候,它们共用一个Request对象,同一个requtst对象存的数据共享。
注意:Filter是在目标资源执行前执行的。
注意:在doFilter方法中提供这个过滤器链对象
测试:
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 过滤器-->
<filter>
<!--给filter 起一个别名-->
<filter-name>Filter1</filter-name>
<!--配置filter 的全类名-->
<filter-class>com.cn.filter.Filter1</filter-class>
</filter>
<!--filter-mapping 配置Filter 过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给哪个filter 使用-->
<filter-name>Filter1</filter-name>
<!--url-pattern 配置拦截路径
/ 表示请求地址为:http://ip:port/工程路径/ 映射到IDEA 的 web 目录
-->
<url-pattern>/target.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>com.cn.filter.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter2</filter-name>
<url-pattern>/target.jsp</url-pattern>
</filter-mapping>
</web-app>
target.jsp:
<%--
Created by IntelliJ IDEA.
User: Think
Date: 2022/3/4
Time: 12:34
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
System.out.println("target.jsp页面执行了");
<%--获取目标资源的线程--%>
System.out.println("target.jsp的线程名是:"+Thread.currentThread().getName());
System.out.println("target.jsp通过request对象获取请求参数:"+request.getParameter("username"));
%>
</body>
</html>
Filter1:
package com.cn.filter;
import javax.servlet.*;
import java.io.IOException;
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter1 前置代码");
//获取Filter1的线程名
System.out.println("Filter1的线程名是:"+Thread.currentThread().getName());
//这个地方能获取请求参数是因为,输入的url地址上携带了参数 username
System.out.println("Filter1通过request对象获取请求参数:"+servletRequest.getParameter("username"));
//向request域对象中存入name值
servletRequest.setAttribute("name", "小明");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter1的线程名是:"+Thread.currentThread().getName());
System.out.println("Filter1 后置代码");
}
@Override
public void destroy() {
}
}
Filter2:
package com.cn.filter;
import javax.servlet.*;
import java.io.IOException;
public class Filter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter2 前置代码");
//获取Filter2的线程名
System.out.println("Filter2的线程名是:"+Thread.currentThread().getName());
//获取的是username
System.out.println("Filter2通过request对象获取请求参数:"+servletRequest.getParameter("username"));
//获取的是name
System.out.println("Filter2中取Filter1中保存的数据:"+servletRequest.getAttribute("name"));
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter2的线程名是:"+Thread.currentThread().getName());
System.out.println("Filter2 后置代码");
}
@Override
public void destroy() {
}
}
启动tomact服务器,在浏览器输入访问target.jsp的路径并添加请求参数进行测试:
5.2、多个过滤器链不正常执行顺序
FilterChain.dofilter()方法的作用:
- 执行下一个Filter过滤器(如果有Filter)
- 执行目标资源(没有Filter)
说明:如果把这个过滤器中的方法删除,则不管你有没有过滤器 有没有目标资源,都不会执行。
5.2.1、删除filter2的chain方法
执行顺序:filter1的前置代码1------>filter2的前置代码1----->filter1的后置代码2
5.2.2、删除filter1的chain方法
执行顺序:filter1的前置代码1
6、Filter 的三种拦截路径
说明:拦截路径是在web.xml中进行的配置。
6.1、精确匹配
<url-pattern>/target.jsp</url-pattern>
以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/target.jsp
6.2、目录匹配
<url-pattern>/admin/*</url-pattern>
以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/*
6.3、后缀名匹配
注意:
- 后缀名匹配不能以
/斜杠
开始。 - Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在!!!
演示一:
<url-pattern>*.html</url-pattern>
以上配置的路径,表示请求地址必须以 .html
结尾才会拦截到
演示二:
<url-pattern>*.do</url-pattern>
以上配置的路径,表示请求地址必须以.do
结尾才会拦截到
演示三:
<url-pattern>*.action</url-pattern>
以上配置的路径,表示请求地址必须以.action
结尾才会拦截到
说明:后缀名不一定是以上格式,可以随便写,哪怕是*.abc
也可以。