Filter&Listener
今日目标
1. filter(过滤器)
2. listener(监听器)
JavaWeb的三大组件
# 概念
1. 组件: 是一个系统的组成部件
2. javaweb组件 : javaweb项目的组成部件
1). servlet
2). filter
3). listener
组件 | 作用 | 实现接口 |
---|---|---|
Servlet | 小应用程序,在JavaWeb中主要做为控制器来使用 可以处理用户的请求并且做出响应 | javax.servlet.Servlet |
Filter | 过滤器,对用户发送的请求或响应进行集中处理,实现请求的拦截 | javax.servlet.Filter |
Listener | 监听器,在Web执行过程中,监听一些事件,当相应事件发生时, 进行处理 | javax.servlet.XxxListener 每个事件有一个接口 |
一 概述
生活中的过滤器
净水器、空气净化器、地铁安检
web中的过滤器
当用户访问服务器资源时,过滤器将请求拦截下来,完成一些通用的操作
Filter的作用
- 拦截客户端对web资源的请求 (重要!)
- 拦截web资源对客户端的响应
应用场景
如:登录验证、统一编码处理、敏感字符过滤
二 快速入门
package com.itheima02.filter;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
//servlet的虚拟路径
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MyServlet被访问了");
}
}
2.1 xml配置
① 编写java类,实现filter接口
package com.itheima02.filter;
import javax.servlet.*;
import java.io.IOException;
/*
TODO: Filter入门
1. 编写一个类实现Filter接口
(千万注意: 别导错包了 javax.servlet.Filter)
2. 配置
1). web.xml配置
2). 注解配置
*/
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("请求被拦截了");
//TODO: 请求放行: 相当于请求转发吧
chain.doFilter(request,response);
}
@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-name>MyFilter</filter-name>
<filter-class>com.itheima02.filter.MyFilter</filter-class>
</filter>
<!--
TODO 设置过滤器拦截的路径
-->
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/MyServlet</url-pattern>
</filter-mapping>
</web-app>
2.2 注解配置
package com.itheima02.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/*
TODO: Filter入门
1. 编写一个类实现Filter接口
(千万注意: 别导错包了 javax.servlet.Filter)
2. 配置
1). web.xml配置
2). 注解配置
(两个配置只能用一个,都用会冲突!!!!)
*/
@WebFilter("/MyServlet")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("请求被拦截了");
//TODO: 请求放行: 相当于请求转发吧
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
三 工作原理
1. 用户发送请求,请求Web资源(包括html,jsp,servlet等)
2. 如果Web资源的地址,是filter要拦截的地址,请求将先经过filter,并执行doFilter()
3. doFilter()方法中如果调用filterChain.doFilter(),则允许请求访问下一个Web资源。
4. 访问Web资源,响应回来会再次经过filter,执行过滤器中的代码,到达浏览器端。
四 使用细节
4.1 生命周期
生命周期:指的是一个对象从生(创建)到死(销毁)的一个过程
// 初始化方法
public void init(FilterConfig config);
// 执行拦截方法
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain);
// 销毁方法
public void destroy();
* 创建
服务器启动项目加载,创建filter对象,执行init方法(只执行一次)
* 运行(过滤拦截)
用户访问被拦截目标资源时,执行doFilter方法
* 销毁
服务器关闭项目卸载时,销毁filter对象,执行destroy方法(只执行一次)
* 补充:
过滤器一定是优先于servlet创建的,后于Servlet销毁
package com.itheima03.life;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/*
TODO Filter的生命周期方法
0. Filter对象由tomcat创建
天然启动加载的,tomcat一启动Filter对象就会创建,优先Servlet对象创建
1. init : 对象创建时,执行一次
优先于servlet的init执行
作用: 初始化数据
2. doFilter : 浏览器每访问一次,就会执行一次
优先于servlet的service执行
作用: 执行业务
3. destroy : tomcat关闭时,会销毁servlet对象时,此方法执行一次
后于servlet的destroy执行
作用: 保存数据,释放资源
*/
@WebFilter("/LifeServlet")
public class LifeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("LifeFilter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("LifeFilter doFilter");
//请求放行,类似于请求转发
chain.doFilter(request,response);
}
@Override
public void destroy() {
System.out.println("LifeFilter destroy");
}
}
package com.itheima03.life;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
/*
Servlet的生命周期方法
1. init : 对象创建时,执行一次
loadOnStartup >=0, tomcat启动时就会创建对象
2. service : 浏览器每访问一次,就会执行一次
3. destroy : tomcat关闭时,会销毁servlet对象时,此方法执行一次
*/
@WebServlet(value = "/LifeServlet",loadOnStartup = 0)
public class LifeServlet implements Servlet{
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("LifeServlet init");
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("LifeServlet service");
}
@Override
public void destroy() {
System.out.println("LifeServlet destroy");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
}
4.2 拦截路径(重点)
在开发时,我们可以指定过滤器的拦截路径来定义拦截目标资源的范围
* 精准匹配
用户访问指定目标资源(/show.jsp)时,过滤器进行拦截
* 目录匹配
用户访问指定目录下(/user/*)所有资源时,过滤器进行拦截
* 后缀匹配
用户访问指定后缀名(*.html)的资源时,过滤器进行拦截
* 匹配所有
用户访问该网站所有资源(/*)时,过滤器进行拦截
package com.itheima04.path;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/*
TODO: 路径匹配模式 urlPattern
1. 精准匹配
用户访问指定目标资源(/MyServlet)时,过滤器进行拦截
2. 目录匹配
用户访问指定目录下(/user/*)所有资源时,过滤器进行拦截
3. 后缀匹配
用户访问指定后缀名(*.html)的资源时,过滤器进行拦截
4. 匹配所有
用户访问该网站所有资源(/*)时,过滤器进行拦截
补充: servlet也有这些匹配模式,一般用不着(只用精准匹配即可),filter用的多
*/
//@WebFilter("/MyServlet")
//@WebFilter("/user/*")
//@WebFilter("*.html")
//@WebFilter("/*")
//@WebFilter(value = "/user/*") // 注解中, 有且仅有一个属性并名为value,可以省略
//@WebFilter(urlPatterns = "/user/*") // value和urlPatterns互为别名
//@WebFilter(urlPatterns = "/user/*") // value和urlPatterns互为别名
//@WebFilter(urlPatterns = {"/AServlet","/BServlet"})
//@WebFilter(urlPatterns = {"/AServlet"})
@WebFilter(urlPatterns = "/AServlet") // 注解中, 数组只有一个元素时,大括号可以省略
public class PathFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("PathFilter执行了");
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
4.3 过滤器链
在一次请求中,若我们请求匹配到了多个filter,通过请求就相当于把这些filter串起来了,形成了过滤器链
* 需求
用户访问目标资源 show.jsp时,经过 FilterA FilterB
* 过滤器链执行顺序 (先进后出)
1.用户发送请求
2.FilterA拦截,放行
3.FilterB拦截,放行
4.执行目标资源 show.jsp
5.FilterB增强响应
6.FilterA增强响应
7.封装响应消息格式,返回到浏览器
* 过滤器链中执行的先后问题....
配置文件
谁先声明,谁先执行
<filter-mapping>
注解【不推荐】
根据过滤器类名进行排序,值小的先执行
FilterA FilterB 进行比较, FilterA先执行...
package com.itheima05.chain;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/user/a.html")
public class AFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("AFilter执行了");
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
package com.itheima05.chain;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/user/a.html")
public class BFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("BFilter执行了");
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
五 Filter案例
5.1 统一网站编码
需求
tomcat8.5版本中已经将get请求的中文乱码解决了,但是post请求还存在中文乱码
浏览器发出的任何请求,通过过滤器统一处理中文乱码
5.1.1 需求分析
5.1.2 代码实现
- 真实场景中,过滤器不会统一响应mime类型
package com.itheima06.example;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
//拦截所有路径
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/*
ServletRequest : 底层接口 (兼容大部分协议)
HttpServletRequest : 子接口(只支持http协议)
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//1. 获取请求中的请求方式
//只要是http协议,强转成功
HttpServletRequest req = (HttpServletRequest) request;
String method = req.getMethod();
//2. 如果是post请求方式,则解决中文乱码
if("post".equalsIgnoreCase(method)){
req.setCharacterEncoding("utf-8");
}
//3. 请求放行
chain.doFilter(req,response);
}
@Override
public void destroy() {
}
}
@WebServlet("/Servlet01")
public class Servlet01 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
System.out.println("01:" + username);
}
}
@WebServlet("/Servlet02")
public class Servlet02 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
System.out.println("02:" + username);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>注册表单</h1>
<form action="Servlet01" method="post">
<input type="text" name="username" placeholder="请输入用户名"> <br>
<input type="password" name="password" placeholder="请输入密码"> <br>
<input type="submit">
</form>
<h1>登录表单</h1>
<form action="Servlet02" method="post">
<input type="text" name="username" placeholder="请输入用户名"> <br>
<input type="password" name="password" placeholder="请输入密码"> <br>
<input type="submit">
</form>
</body>
</html>
5.2 登录权限验证
需求
访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面
5.2.1 需求分析
- 用户想要访问购物车
- 我们可以设置一个过滤器来拦截用户对购物车的请求,在过滤器判断当前用户是否登录
在用户登录的时候,将当前用户数据存到session中
在过滤器中校验的时候,从session中获取用户数据,获取的到表示用户登录
- 如果当前用户登录的话,直接放行
- 如果当前用户没有登录,那么就重定向到登录页面
5.2.2 需求实现
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
某东首页 <a href="login.html">登录</a> <br>
<a href="user/buycar.html">购物车</a>
<h1>商品列表....</h1>
</body>
</html>
buycar.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
TODO 需求: 这个页面用户登录了才能访问,如果用户没有登录,要跳转到登录
1. 登录时候的逻辑: (LoginServlet)
在用户成功登录的时候, 往session中存用户信息
2. 在访问购物车页面的时候逻辑: (LoginFilter)
1). 会获取session中的用户信息
2). 如果有,说明登录过了,正常访问
3). 如果没有, 说明该用户还没登录,就跳转到登录页面
-->
<h1>购物车...页面</h1>
</body>
</html>
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决post请求参数中文乱码问题
request.setCharacterEncoding("utf-8");
//TODO: 验证码校验 (在用户名和密码之前)
//1). 获取服务器之前生成的验证码
//2). 获取用户输入的验证码
String code_session = (String) request.getSession().getAttribute("code_session");
String checkcode = request.getParameter("checkcode");
if(!code_session.equals(checkcode)){
//验证码比对失败
request.setAttribute("errorMsg","验证码错误");
request.getRequestDispatcher("FailedServlet").forward(request,response);
return; // 中断后续运行
}
//1. 获取请求
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username +"," + password);
//2. 业务处理: 判定用户名和密码是否存在
// 理论上: 查询数据库
// 现在: 伪数据 (jack,123)
if("jack".equals(username) && "123".equals(password)){
//登录成功
//TODO : 用session存用户信息
request.getSession().setAttribute("username",username);
request.getRequestDispatcher("SuccessServlet").forward(request,response);
}else{
//登录失败
request.setAttribute("errorMsg","用户名不存在或密码错误");
request.getRequestDispatcher("FailedServlet").forward(request,response);
}
}
}
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//TODO 访问user目录下的资源,都必须登录了才行
//目录匹配
@WebFilter("/user/*")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
Object username = req.getSession().getAttribute("username");
if(username == null){
/*
* 当前路径: http://localhost:8080/user/buycar.html
* 相对路径 A:
* login.html
* 最终路径 A:
* http://localhost:8080/user/login.html
* 相对路径B:
* ../login.html
* 最终路径 B:
* http://localhost:8080/login.html
* */
//说明用户没有登录,就跳转到登录页面
res.sendRedirect("../login.html");
// res.sendRedirect("http://localhost:8080/login.html");
}else{
//说明用户登录了
chain.doFilter(req,response);
}
}
@Override
public void destroy() {
}
}
六 Listener
1.1 概述
# 侦探的例子
1. 雇佣侦探的人 : 富豪的太太
2. 侦探
3. 侦探监听的目标: 富豪
4. 侦探的工作: 观察富豪的动作,如果有不法行为,就汇报给富豪太太
侦探的工作是被动的, 富豪做了某事, 才会触发侦探行动
# 监听器
1. 雇佣监听器的人 : 我们,开发者
2. 监听器: ServletContextListener
3. 监听的目标 : ServletContext
4. 监听器的工作:
ServletContext创建了, ServletContextListener会执行一个方法
ServletContext销毁了, ServletContextListener也会执行一个方法
# ServletContext
1. 介绍: servlet上下文对象
2. 什么时候创建: tomcat一启动, tomcat会为项目创建唯一的ServletContext对象
ServletContext对象比Filter对象更早创建
3. 什么时候销毁: tomcat关闭,就会销毁ServletContext对象
ServletContext对象比Filter对象更晚创建
# ServletContextListener
1. tomcat启动时, ServletContextListener执行方法contextInitialized (开机启动)
比Filter的init还早
2. tomcat关闭是, ServletContextListener执行方法contextDestroyed
比Filter的destroy还晚
# 作用: springmvc底层用到这个ServletContextListener
springmvc在tomcat一启动的时候,会通过ServletContextListener的方法加载配置文件
生活中的监听器
我们很多商场有摄像头,监视着客户的一举一动。如果客户有违法行为,商场可以采取相应的措施。
javaweb中的监听器
在我们的java程序中,有时也需要监视某些事情,一旦被监视的对象发生相应的变化,我们应该采取相应的操作。
监听web三大域对象:HttpServletRequest、HttpSession、ServletContext (创建和销毁)
场景
历史访问次数、统计在线人数、系统启动时初始化配置信息
监听器的接口分类
事件源 | 监听器接口 | 时机 |
---|---|---|
ServletContext | ServletContextListener | 上下文域创建和销毁 |
ServletContext | ServletContextAttributeListener | 上下文域属性增删改的操作 |
HttpSession | HttpSessionListener | 会话域创建和销毁 |
HttpSession | HttpSessionAttributeListener | 会话域属性增删改的操作 |
HttpServletRequest | ServletRequestListener | 请求域创建和销毁 |
HttpServletRequest | ServletRequestAttributeListener | 请求域属性增删改的操作 |
1.2 快速入门
监听器在web开发中使用的比较少,见的机会就更少了,今天我们使用**ServletContextListenner**来带领大家学习下监听器,因为这个监听器是监听器中使用率最高的一个,且监听器的使用方式都差不多。
我们使用这个监听器可以在项目启动和销毁的时候做一些事情,例如,在项目启动的时候加载配置文件。
步骤分析
1. 创建一个普通类,实现ServletContextListenner
2. 重写抽象方法
监听ServletContext创建
监听ServletContext销毁
3. 配置
web.xml
注解
① xml版本
package com.itheima07.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/*
TODO: Listener入门
1. 实现接口
2. 配置
1). web.xml
2). 注解
*/
//@WebListener
public class MyListener implements ServletContextListener {
//tomcat启动时,此方法就会运行
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("项目启动了: contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("项目销毁了: contextDestroyed");
}
}
<listener>
<listener-class>com.itheima07.listener.MyListener</listener-class>
</listener>
② 注解版本
@WebListener
public class MyListener implements ServletContextListener {
//tomcat启动时,此方法就会运行
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("项目启动了: contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("项目销毁了: contextDestroyed");
}
}