Filter&Listener

今日目标

  1. 1. filter(过滤器)
  2. 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的作用

  1. 拦截客户端对web资源的请求 (重要!)
  2. 拦截web资源对客户端的响应

应用场景

如:登录验证、统一编码处理、敏感字符过滤

1592011832218.png

二 快速入门

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先执行...

1592017660642.png

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 需求分析

1612233540515.png

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 需求分析

  1. 用户想要访问购物车
  2. 我们可以设置一个过滤器来拦截用户对购物车的请求,在过滤器判断当前用户是否登录

    在用户登录的时候,将当前用户数据存到session中

    在过滤器中校验的时候,从session中获取用户数据,获取的到表示用户登录

  1. 如果当前用户登录的话,直接放行
  2. 如果当前用户没有登录,那么就重定向到登录页面

5.2.2 需求实现

1649768533268.png

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");
    }
}