九、Listener 监听器& Filter过滤器

一、 Listener监听器

Javaweb中的监听器是用于监听web常见对象HttpServletRequest,HttpSession,ServletContext
作用:
监听web对象的创建与销毁
监听web对象的属性变化
监听session绑定javaBean操作
监听机制的相关概念
事件----一件事情
事件源---产生这件事情的源头
注册监听---将监听器与事件绑定,当事件产生时,监听器可以知道,并进行处理。
监听器---对某件事情进行处理监听的一个对象

Servlet的监听器:

  • 监听ServletContext,HttpSession,ServletRequest
  • 事件源和监听器绑定的过程:通过配置完成.

Servlet中的监听器:提供了8个监听器.
一类:监听三个域对象的创建和销毁的监听器.3个
二类:监听三个域对象的属性变更的监听器.(属性添加,属性移除,属性替换)3个.
三类:监听HttpSession对象中的JavaBean的状态的改变.(绑定,解除绑定,钝化和活化)2个

1. 一类监听器:监听三个域对象的创建和销毁的监听器

1.1 ServletContextListener:监听ServletContext对象的创建和销毁.

【方法】

ServletContextListener.png

【问题】
ServletContext对象何时创建和销毁:

  • 创建:服务器启动时候,服务器可以为每个WEB应用创建一个单独的ServletContext.
  • 销毁:服务器关闭的时候,或者项目从服务器中移除.

【入门案例】
1.编写一个类实现监听器的接口.

public class MyServletContextListener implements ServletContextListener{

    @Override
    /**
     * 监听ServletContext对象的创建的方法:
     * @param sce
     */
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext对象被创建了...");
    }

    @Override
    /**
     * 监听ServletContext对象的销毁的方法:
     * @param sce
     */
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext对象被销毁了...");
    }

}
  1. 通过配置完成监听器和事件源的绑定.
 <!-- 配置监听器 -->
  <listener>
    <listener-class>com.itheima.weblistener.MyServletContextListener</listener-class>
  </listener>

【企业中应用】

  • 初始化工作.
  • 加载配置文件:Spring框架.
    ContextLoaderListener:
  • 定时任务调度:
    Timer,TimerTask.
1.2 HttpSessionListener:监听HttpSession对象的创建和销毁的监听器.

【方法】

HttpSessionListener.png

【问题】

  • HttpSession对象何时创建和销毁的?
    • 创建:服务器第一次调用getSession()方法的时候.
    • 销毁:
      • 非正常关闭服务器(正常关闭序列化到硬盘)
      • session过期了(默认30分钟)
      • session.invalidate()

【入门】

  1. 编写监听器:
public class MyHttpSessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("HttpSession对象被创建了...");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("HttpSession对象被销毁了...");
    }

}

  1. 配置监听器:
<listener>
    <listener-class>com.itheima.weblistener.MyHttpSessionListener</listener-class>
 </listener>

【问题】
1.访问html是否创建session对象? 不会
2.访问一个Servlet是否创建session对象? 不会
3.访问一个jsp是否创建session对象? 会

1.3 ServletRequestListener:监听ServletRequest对象的创建和销毁的监听器

【方法】

ServletRequestListener.png

【问题】
ServletRequest对象何时创建和销毁?

  • 创建:客户端向服务器发送请求的时候.
  • 销毁:服务器为这次请求作出了响应时候.

【入门】
1.编写一个监听器

public class MyServletRequestListener implements ServletRequestListener {
    public void requestInitialized(ServletRequestEvent sre)  { 
        System.out.println("ServletRequest被创建了...");
    }
    public void requestDestroyed(ServletRequestEvent sre)  { 
        System.out.println("ServletRequest被销毁了...");
    }
    
}
  1. 配置监听器
 <listener>
    <listener-class>com.itheima.weblistener.MyServletRequestListener</listener-class>
  </listener>

【问题】
1.访问html是否创建request对象? 会
2.访问一个Servlet是否创建request对象? 会
3.访问一个jsp是否创建request对象? 会

2. 二类:监听三个域对象属性变更的监听器

2.1 ServletContextAttributeListener:监听ServletContext对象中的属性变更的监听器
ServletContextAttributeListener.png
2.2 HttpSessionAttributeListener:监听HttpSession对象中的属性变更的监听器
HttpSessionAttributeListener:监听HttpSession对象中的属性变更的监听器.png
2.3 ServletRequestAttributeListener:监听ServletRequest对象中的属性变更的监听器
ServletRequestAttributeListener.png

3. 三类:监听HttpSession中的JavaBean的状态改变的监听器.(绑定,解决绑定,钝化,活化)

三类监听器非常特殊
监听器作用在JavaBean上.JavaBean可以自己感知在session中状态.
这类监听器不用配置.

3.1 HttpSessionBindingListener:监听HttpSession中的JavaBean的绑定和解除绑定的状态.
HttpSessionBindingListener.png
3.2 HttpSessionActivationListener:监听HttpSession中的JavaBean的钝化和活化的状态.
HttpSessionActivationListener.png

sessionDidActivate(HttpSessionEvent se); -- 活化
SessionWillPassivate(HttpSessionEvent se); -- 钝化

通过配置序列化session:

context.xml

  • tomcat/conf/context.xml: 对tomcat中的所有虚拟主机和虚拟路径生效.
  • tomcat/conf/Catalina/localhost/context.xml: 对tomcat下的localhost虚拟主机中的所有路径生效.
  • 工程的META-INF/context.xml: 对当前的工程生效.
<?xml version="1.0" encoding="UTF-8"?>
<!--
    maxIdleSwap :1分钟 如果session不使用就会序列化到硬盘.
    directory   :itheima 序列化到硬盘的文件存放的位置.
 -->
<Context>
    <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
        <Store className="org.apache.catalina.session.FileStore" directory="itheima"/>
    </Manager>
</Context>

4. 监听器的总结:

Servlet的监听器分成三类8个:

  • 一类:监听三个域对象的创建和销毁的监听器.
  • 二类:监听三个域对象的属性的变更.
  • 三类:监听HttpSession中JavaBean的状态的改变

5. 案例-定时删除过时订单分析

功能描述:
若一个订单从下单开始超过30分钟未支付,则删除该订单。
分析:
为了这个操作我们需要拿到订单的下单时间和支付状态.然后判断订单是否超过30分钟未支付,若未支付则取消该订单,想实现此功能,还需要使用任务调度功能.要求在项目一启动的时候就可以扫描订单.比如每分钟查找一下,将满足条件的删除掉.

在java中有一个Timer定时器类
Timer.png

步骤分析:
创建一个ServletContext创建与销毁监听器,在ServletContext对象创建时,启动定时扫描器.
在定时器内部实现查询订单及删除订单操作

public void contextInitialized(ServletContextEvent sce) {
        //一旦服务器启动 该定时器就开始扫描
        Timer t=new Timer();
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                //完成订单查询和删除操作即可
                System.out.println(".........");
            }
        }, 1000, 2000);//延迟1秒开始执行,每2秒执行一次
    }

二、Filter过滤器

1. filter介绍及其作用介绍

Filter是sun公司中servlet2.3后增加的一个新功能.
Servlet规范中三个技术 Servlet Listener Filter
在javaEE中定义了一个接口 javax.servlet.Filter来描述过滤器
作用:
通过Filter可以拦截访问web资源的请求与响应操作.
WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

常用api:

常用api.png

filter入门案例
创建步骤:

  1. 编写filter
    a. 创建一个类实现javax.servlet.Filter接口
    b. 重写接口方法
  2. 编写配置文件
    a. 注册filter
    b. 绑定路径

Filter在web.xml文件中配置的目的:配置拦截什么样的资源。

Filter初始化

<filter>
    <filter-name>demo1Filter</filter-name>
    <filter-class>cn.itcast.web.filter.Demo1Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>demo1Filter</filter-name>
    <url-pattern>/demo1</url-pattern>
</filter-mapping>

拦截分析:

拦截分析.png

注意:
在Filter的doFilter方法内如果没有执行,那么资源是不会被访问到的。

FilterChain功能介绍
FilterChain 是 servlet 容器为开发人员提供的对象,它提供了对某一资源的已过滤请求调用链的视图。过滤器使用 FilterChain 调用链中的下一个过滤器,如果调用的过滤器是链中的最后一个过滤器,则调用链末尾的资源。

FilterChain.png

2. filter链与生命周期

filter链介绍
多个Filter对同一个资源进行了拦截,那么当我们在开始的Filter中执行 chain.doFilter(request,response)时,是访问下一下Filter,直到最后一个Filter执行时,它后面没有了Filter,才会访问web资源。

如果有多个Filter形成了Filter链,那么它们的执行顺序是怎样确定的?
它们的执行顺序取决于<filter-mapping>在web.xml文件中配置的先后顺序。

filter生命周期
当服务器启动,会创建Filter对象,并调用init方法,只调用一次.
当访问资源时,路径与Filter的拦截路径匹配,会执行Filter中的doFilter方法,这个方法是真正拦截操作的方法.
当服务器关闭时,会调用Filter的destroy方法来进行销毁操作.

4. FilterConfig介绍

Filter功能介绍
在Filter中的init方法上有一个参数叫FilterConfig,是Filter的配置对象
作用:
获取初始化参数
获取filter的名称
获取全局管理者(SerlvetContext对象)

常用api

FilterConfig.png

5. filter配置详解

Filter基本配置介绍

<filter>
    <filter-name>filter名称</filter-name>
    <filter-class>filter类全名</filter-class>
</filter>
<filter-mapping>
    <filter-name>filter名称</filter-name>
    <url-pattern>映射路径</url-pattern>
</filter-mapping>

url-pattern配置
完全匹配 :
要求必须以"/"开始.

目录匹配:
要求必须以"/"开始,以*结束.

扩展名匹配:
不能以"/"开始,以.xxx结束.例如:.jsp *.do

关于servlet-name配置
针对于servlet拦截的配置 <servlet-name>配置
在Filter中它的url-pattern配置项上有一个标签
<servlet-name>它用于设置当前Filter拦截哪一个servlet。
是通过servlet的name来确定的。

关于dispatcher配置
可以取的值有 REQUEST FORWARD ERROR INCLUDE
作用:
当以什么方式去访问web资源时,进行拦截操作.

REQUEST
当是从浏览器直接访问资源,或是重定向到某个资源时进行拦截方式配置的 它也是默认值
FORWARD
它描述的是请求转发的拦截方式配置
ERROR
如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
INCLUDE
如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。

7.Filter案例

7.1自动登录功能

之前的登录流程:
login.jsp-->loginServlet-->UserService-->UserDao
现在分析下自动登录的原理
a.当用户登录成功之后,判断一下用户是否勾选了自动登录,若勾选了,将用户名和密码通过cookie持久化到浏览器上.
b.做一个filter,filter的作用为:判断cookie中是否有用户名和密码,若有拿过来调用service完成登录操作
c.filter放行

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1.强转
        HttpServletRequest req=(HttpServletRequest) request;
        HttpServletResponse resp=(HttpServletResponse) response;
        
        //2.逻辑
        //首先判断session中是否有user  若没有再继续操作
        User user = (User) req.getSession().getAttribute("user");
        
        if(user==null){//session中没有用户 需要继续操作
            //获取请求路径 若是login和regist的不需要自动登录
            String uri = req.getRequestURI();
            String contextPath = req.getContextPath();
            String path=uri.substring(contextPath.length());
            //System.out.println(path);
            if(!("/login.jsp".equals(path)||"/login".equals(path)||"/regist.jsp".equals(path)||"/regist".equals(path))){
                //查找是否有自动登录的cookie
                Cookie c = CookieUtils.getCookieByName("autoLogin", req.getCookies());
                if(c!=null){
                    
                    //若有 调用service 自动登录
                    //拿到用户名和密码
                    String username=c.getValue().split("-")[0];
                    String password=c.getValue().split("-")[1];
                    
                    //调用service登录
                    user = new UserService().login(username, password);
                    //若登录成功 将user放入session中
                    if(user!=null){
                        req.getSession().setAttribute("user", user);
                        System.out.println("自动登录..........");
                    }
                }
            }
            
        }
        
        //3.放行
        chain.doFilter(req, resp);
    }

注意:
cookie中不能存放中文,若出现中文还需编码解决

7.2编码过滤器

思路:在Filter中对request进行功能增强,让它处理了乱码问题,再将request传递到servlet中,这样在servlet中获取请求参数就不会乱码。
如何进行功能加强?

  • 三种方式:
    继承
    装饰者模式
    动态代理

装饰模式实现步骤:
a.装饰类与被装饰类要继承同一个父类或实现同一个接口。
b.在装饰类中重写方法,进行功能增强
c.在装饰类中持有一个被装饰类的对象

编码过滤器分析.png

在EncodingFilter中已经创建了一个MyReqeust,它是一个HttpServletRequest的装饰类,而我们在chain.doFilter(MyRequest,response);也就是在servlet中使用的request其实是装饰类。

对于我们通过reqeust对象获取请求参数有三种方式:
getParameter getParameterValues getParameterMap

我们不需要将这三个都进行编码处理,只需要对getParameterMap进行乱码处理,而getParameter及getParameteValues可以依赖于getParametreMap的实现

class MyRequest extends HttpServletRequestWrapper{
    private HttpServletRequest request;
    private boolean flag=true;
    
    
    public MyRequest(HttpServletRequest request) {
        super(request);
        this.request=request;
    }
    
    @Override
    public String getParameter(String name) {  
        if(name==null || name.trim().length()==0){
            return null;
        }
        String[] values = getParameterValues(name);
        if(values==null || values.length==0){
            return null;
        }
        
        return values[0];
    }
    
    @Override
    /**
     * hobby=[eat,drink]
     */
    public String[] getParameterValues(String name) {
        if(name==null || name.trim().length()==0){
            return null;
        }
        Map<String, String[]> map = getParameterMap();
        if(map==null || map.size()==0){
            return null;
        }
        
        return map.get(name);
    }
    
    @Override
    /**
     * map{ username=[tom],password=[123],hobby=[eat,drink]}
     */
    public Map<String,String[]> getParameterMap() {  
        
        /**
         * 首先判断请求方式
         * 若为post  request.setchar...(utf-8)
         * 若为get 将map中的值遍历编码就可以了
         */
        String method = request.getMethod();
        if("post".equalsIgnoreCase(method)){
            try {
                request.setCharacterEncoding("utf-8");
                return request.getParameterMap();
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else if("get".equalsIgnoreCase(method)){
            Map<String,String[]> map = request.getParameterMap();
            if(flag){
                for (String key:map.keySet()) {
                    String[] arr = map.get(key);
                    //继续遍历数组
                    for(int i=0;i<arr.length;i++){
                        //编码
                        try {
                            arr[i]=new String(arr[i].getBytes("iso8859-1"),"utf-8");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                }
                flag=false;
            }
            //需要遍历map 修改value的每一个数据的编码
            
            return map;
        }
        
        return super.getParameterMap();
    }
    
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容