JavaWeb三大组件之过滤器
过滤器说明
过滤器介绍
- Filter 过滤器它是 JavaWeb 的三大组件之一(Servlet 程序、Listener 监听器、Filter 过滤器)
- Filter 过滤器是 JavaEE 的规范,是接口
-
Filter 过滤器它的作用是:拦截请求,过滤响应
-
应用场景
- 权限检查
- 日记操作
- 事务管理
Filter 过滤器基本原理
一图胜千言
Filter 过滤器快速入门
需求:在 web 工程下,有后台管理目录 manage,要求该目录下所有资源(html、图片、jsp、Servlet等)用户登录后才能访问
代码实现:
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>管理后台登录</title>
</head>
<body>
<h1>管理后台登录</h1>
<%
System.out.println("login.jsp:request=" + request);
%>
<form action="<%=request.getContextPath() %>/loginCheckServlet" method="post">
u:<input type="text" name="username"/> <br/><br/>
p:<input type="password" name="password"/> <br/><br/>
<input type="submit" value="用户登录"/></form>
</body>
</html>
LoginCheckServlet
public class LoginCheckServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取到用户名和密码->DB
//假设密码是123456, 就可以通过
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("LoginCheckServlet:request=" + request);
if("123456".equals(password)) {
//合法, 将用户名,加入session
request.getSession().setAttribute("username", username);
//请求转发到admin.jsp
request.getRequestDispatcher("/manage/admin.jsp")
.forward(request,response);
} else {
//不合法, 返回登录页面
request.getRequestDispatcher("/login.jsp")
.forward(request,response);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
创建ManageFilter过滤器
- filter 在 web 项目启动时, 由 tomcat 来创建 filter 实例, 只会创建一个
- 会调用 filter 默认的无参构造器, 同时会调用 init 方法, 只会调用一次
- 在创建 filter 实例时,同时会创建一个 FilterConfig 对象,并通过 init 方法传入
- 通过 FilterConfig 对象,程序员可以获取该 filter 的相关配置信息
- 当一个 http 请求和该 filter 的的 url-patter 匹配时,就会调用 doFilter 方法
- 在调用 doFilter 方法时, tomcat 会同时创建 ServletRequest 和 ServletResponse 和 FilterChain 对象并通过 doFilter 传入.
- 如果后面的请求目标资源(jsp,servlet…) 会使用到 request,和 response,那么会继续传递
public class ManageFilter implements Filter {
private int count = 0;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//当 Tomcat 创建 Filter 创建,就会调用该方法,进行初始化
//提醒:回忆我们自己实现 tomcat 底层机制 + servlet 程序, 就会了然
System.out.println("ManageFilter init被调用...");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("ManageFilter doFilter() 被调用=" + (++count));
//每次调用该 filter 时,doFilter 就会被调用
//如果这里,没有调用继续请求的方法,则就停止
//如果继续访问目标资源-> 等价于放行
//说明:在调用过滤器前,servletRequest 对象 = request 已经被创建并封装
//所以:我们这里就可以通过 servletRequest 获取很多信息, 比如访问 url , session
//比如访问的参数 ... 就可以做事务管理,数据获取,日志管理等
//获取到 session
//可以继续使用 httpServletRequest 方法.
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
System.out.println("输入密码=" + httpServletRequest.getParameter("password"));
HttpSession session = httpServletRequest.getSession();
//获取 username session 对象, 还可以继续使用
Object username = session.getAttribute("username");
if (username != null) {
//解读 filterChain.doFilter(servletRequest, servletResponse)
//1. 继续访问目标资源 url
//2. servletRequest 和 servletResponse 对象会传递给目标资源/文件
//3. 一定要理解 filter 传递的两个对象,再后面的 servlet/jsp 是同一个对象(指的是在一次http请求)
System.out.println("servletRequest=" + servletRequest);
System.out.println("日志信息==");
System.out.println("访问的用户名=" + username.toString());
System.out.println("访问的url=" + httpServletRequest.getRequestURL());
System.out.println("访问的IP=" + httpServletRequest.getRemoteAddr());
filterChain.doFilter(servletRequest, servletResponse);
} else {//说明没有登录过..回到登录页面
servletRequest.getRequestDispatcher("/login.jsp").
forward(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
//当filter被销毁时,会调用该方法
System.out.println("ManageFilter destroy()被调用..");
}
}
在 web.xml 配置过滤器
说明:filter 一般写在其它 servlet 的前面
- 通过观察我们发现 filter 配置和 servlet 非常相似. filter 也是被 tomcat 管理和维护
- url-pattern 就是当请求的 url 和 匹配的时候,就会调用该filter
- /manage/* 第一个 / 解析成 http://ip:port/工程路径
- 完整的路径就是 http://ip:port/工程路径/manage/* 当请求的资源 url 满足该条件时
都会调用 filter , /manage/admin.jsp
<filter>
<filter-name>ManageFilter</filter-name>
<filter-class>com.bestpig.filter.ManageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManageFilter</filter-name>
<url-pattern>/manage/*</url-pattern>
</filter-mapping>
转发到manage目录下的admin.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>后台管理</title>
<base href="<%=request.getContextPath() %>/manage/"/>
</head>
<body>
<h1>后台管理</h1>
<%
// 验证request对象是和前面的 filter 是一个对象
System.out.println("request=" + request);
%>
<a href="#">用户列表</a>||<a href="#">添加用户</a>||<a href="#">删除用户</a>
<hr/>
<img src="tx.png" height="300"/>
</body>
</html>
从 LoginCheckServlet 到 manage 目录下的 admin.jsp,因为是请求转发,故不会经过过滤器,而在 admin.jsp 页面,去请求 tx.png 时则要经过过滤器
控制台输出如下
ManageFilter init被调用...
login.jsp:request=org.apache.catalina.connector.RequestFacade@22921e71
LoginCheckServlet:request=org.apache.catalina.connector.RequestFacade@22921e71
admin.jsp:request=org.apache.catalina.core.ApplicationHttpRequest@75a34792
ManageFilter doFilter() 被调用=1
输入密码=null
servletRequest=org.apache.catalina.connector.RequestFacade@22921e71
日志信息==
访问的用户名=bestpig
访问的url=http://localhost:8080/filter/manage/tx.png
访问的IP=0:0:0:0:0:0:0:1
Filter 过滤器 url-patter
- url-pattern : Filter 的拦截路径, 即浏览器在请求什么位置的资源时,过滤器会进行拦截过滤
- 精确匹配 /a.jsp 对应的请求地址 http://ip[域名]:port/工程 路径/a.jsp 会拦
- 目录匹配 /manage/*对应的 请求地址 http://ip[域名]:port/ 工程路径/manage/xx , 即 web 工程 manage 目录下所有资源会拦
- 后缀名匹配 *.jsp 后缀名可变,比如 *.action *.do 等等对应 的 请求地址 http://ip[域名]:port/工程路径/xx.jsp , 后缀名为 .jsp 请求会拦
- Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在
Filter 过滤器生命周期
FilterConfig
- FilterConfig 是 Filter 过滤器的配置
- Tomcat 每次创建 Filter 的时候,也会创建一个 FilterConfig 对象,这里包含了 Filter 配 置文件的配置信息
- FilterConfig 对象作用是获取 filter 过滤器的配置内容
应用实例
FilterConfig1
public class FilterConfig1 implements Filter {
private String ip; //从配置获取的ip
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("HspFilterConfig init() 被调用..");
//通过filterConfig 获取相关的参数
String filterName = filterConfig.getFilterName();
ip = filterConfig.getInitParameter("ip");
ServletContext servletContext = filterConfig.getServletContext();
//可以获取到该filter所有的配置参数名
Enumeration<String> initParameterNames =
filterConfig.getInitParameterNames();
//遍历枚举
while (initParameterNames.hasMoreElements()) {
System.out.println("名字=" + initParameterNames.nextElement());
}
System.out.println("filterName= " + filterName);
System.out.println("ip= " + ip);
System.out.println("servletContext= " + servletContext);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
配置 web.xml
<filter>
<filter-name>FilterConfig1</filter-name>
<filter-class>com.bestpig.filter.FilterConfig1</filter-class>
<!--这里就是给该filter配置的参数-有程序员根据业务逻辑来设置-->
<init-param>
<param-name>ip</param-name>
<param-value>127.0</param-value>
</init-param>
<init-param>
<param-name>port</param-name>
<param-value>8888</param-value>
</init-param>
<init-param>
<param-name>email</param-name>
<param-value>bestpig@qq.com</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterConfig1</filter-name>
<url-pattern>/abc/*</url-pattern>
</filter-mapping>
控制台输出
FilterConfig1 init() 被调用..
名字=port
名字=ip
名字=email
filterName= FilterConfig1
ip= 127.0
servletContext= org.apache.catalina.core.ApplicationContextFacade@5ad791ce
FilterChain 过滤器链
在处理某些复杂业务时,一个过滤器不够,可以设计多个过滤器共同完成过滤任务,形成过滤器链
应用实例
AFilter
public class AFilter 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("AFilter---> 线程id=" +
Thread.currentThread().getId());
System.out.println("AFilter doFilter 的前置代码...");
System.out.println("执行 AFilter doFilter()");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("AFilter doFilter 的后置代码...");
}
@Override
public void destroy() {
}
}
BFilter
public class BFilter 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("BFilter---> 线程id=" +
Thread.currentThread().getId());
System.out.println("BFilter doFilter 的前置代码...");
System.out.println("执行 BFilter doFilter()");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("BFilter doFilter 的后置代码...");
}
@Override
public void destroy() {
}
}
admin 下的 hi.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>hi</title>
</head>
<body>
<h1>admin目录下的 hi.jsp</h1>
<h1>后台管理</h1>
<a href="#">用户列表</a>||<a href="#">添加用户</a>||<a href="#">删除用户</a>
<hr/>
</body>
</html>
web.xml 配置
<filter>
<filter-name>AFilter</filter-name>
<filter-class>com.bestpig.filter.AFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>BFilter</filter-name>
<filter-class>com.bestpig.filter.BFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>BFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
浏览器方位 hi.jsp,控制台输出如下
AFilter---> 线程id=38
AFilter doFilter 的前置代码...
执行 AFilter doFilter()
BFilter---> 线程id=38
BFilter doFilter 的前置代码...
执行 BFilter doFilter()
BFilter doFilter 的后置代码...
AFilter doFilter 的后置代码...
注意事项和细节
- 多个 filter 和目标资源在一次 http 请求,在同一个线程
- 当一个请求 url 和 filter 的 url-pattern 匹配时, 才会被执行, 如果有多个匹配上,就会 顺序执行,形成一个 filter 调用链(底层可以使用一个数据结构搞定)
- 多个 filter 共同执行时,因为是一次 http 请求, 使用同一个 request 对象
- 多个 filter 执行顺序,和 web.xml 配置顺序保持一致
- chain.doFilter(req, resp)方法 将执行下一个过滤器的 doFilter 方法, 如果后面没有过滤器, 则执行目标资源
- 小结:注意执行过滤器链时, 顺序是(用前面的案例分析) Http请求 -> A 过滤器 dofilter() -> A 过滤器前置代码 -> A 过滤器 chain.doFilter() -> B 过滤器 dofilter() -> B 过滤器前置代 码 -> B过滤器 chain.doFilter() -> 目标文件 -> B过滤器后置代码 -> A过滤器后置代码 -> 返回给浏览器页面/数据