Java Web Listener

https://zhuanlan.zhihu.com/p/65219353

终于轮到讲讲Filter、Listener,写完这两篇,关于JavaWeb我也就无话可说了。

转行开发一年多了,很心疼去年浪费了这么多时间在SSM上。有一段时间,觉得自己好像什么都懂,但又什么都不懂。这种感觉是非常难受的。仿佛浑身充满了力气,却每一拳都打在棉花上。我想更深入地了解Java,写出更好更优雅的代码,结果买了书翻开的那一瞬间就发现不是自己想要的,书中所讲并没有直指我心里的疑问。

我的疑问是什么呢?作为非科班,自知起步比别人已经晚了一大截。虽然希望最好能搞懂整个框架,却也知道欲速则不达。然而,哪怕窥得其中一二想必也会很满足。我以为这件事很简单,但却浪费了我大半年时间。

去年年底一次偶然的机会,促使我开始了复习JavaWeb基础的漫漫长路。从此一发不可收拾,从动态代理、注解、ThreadLocal以及JDBC,一路过关斩将,收获颇丰。才发现,原来学习框架时感到无力,是特么基础不牢靠...真的是基础不牢,地动山摇,这话一点没错。

如果当初学习JavaWeb时,我对反射、动态代理、注解、泛型、JDBC等知识点有现在这程度,学会使用SSM估计就是一星期的事情...基础实在太重要了(话虽这么说,JavaWeb对于零基础来说难度是客观存在的)。

真的恨不得坐时光机回到去年抽自己一巴掌:滚回去好好学JavaWeb!望大家引以为戒!

这篇先写Listener。看完这篇,你们就会发现,关于Listener,我讲解的切入点有多骚。

主要内容:

  • Listener复习
  • 观察者模式
  • 监听器案例

Listener复习

大致来说,常用的监听器就是“6+2”:

6个常规监听器
    |---ServletContext
            |---ServletContextListener(生命周期监听)
            |---ServletContextAttributeListener(属性监听)
    |---HttpSession
            |---HttpSessionListener(生命周期监听)
            |---HttpSessionAttributeListener(属性监听)
    |---ServletRequest
            |---ServletRequestListener(生命周期监听)
            |---ServletRequestAttributeListener(属性监听)

2个感知监听
    |---HttpSessionBindingListener
    |---HttpSessionActivationListener

感知监听都是Session相关的,我已经在Cookie与Session讲过,这里就不提了。

6个常规监听器,分属三类,分别对应JavaWeb三大域对象(除去JSP的Page域):ServletContext、HttpSession、ServletRequest。共三对,每一对都包括1个生命周期监听和1个属性监听。

所谓生命周期监听器,就是监听三大域对象的创建和销毁。每当Tomcat创建或销毁三大域对象,都会被这些监听器察觉,然后它们会做相应的操作(调用自身的特定方法)。

属性监听器则专门监听三大域对象get/setAttribute()。每当我们给域对象设置值或者从里面取值,都会被它们监听到,然后还是触发它们特定的方法。

先复习一下Servlet如何处理请求:

image.png

然后我们来看看监听器到底在什么时候做了什么事。

为了帮助大家理解接下来这张图的细节,我问几个问题,大家带着问题去看:

  • 三大生命周期监听器,各自在何时创建、销毁,顺序是怎么样的?
  • 访问一个Servlet,HttpSessionListener一定会触发吗?(换个角度就是,访问Servlet,Session一定会创建吗?)
  • 三大属性监听何时触发?

image.png

属性监听没画出来,自行脑补

答案:

问题一

  1. 在项目启动时ServletContextListener监听到ServletContext对象创建
  2. 每一次请求Tomcat都会创建一个Request,它的创建会被ServletRequestListener监听到
  3. 如果Servlet中调用了request.getSession(),则Tomcat会创建Session(如果根据JSESSIONID找不到对应的),这会被HttpSessionListener监听到
  4. 请求结束,Request销毁,被监听到
  5. 用户30分钟未访问,Session过期销毁,被监听到
  6. 项目关停,ServletContext销毁,被监听到

问题二

只有当在Servlet中调用request.getSession(),且根据JSESSIONID找不到对于的Session时,才会创建新的Session对象,才会被监听到。第二次请求,浏览器会带上JSESSIONID,此时虽然还是request.getSession(),但是会返回上次那个。根据JSESSIONID去Session这个过程是隐式的,我们看到的就是getSession()。

问题三

对于图中的步骤7/11/12,也就是get/setAttribute()时,会触发属性监听。

搞清楚每一种监听器的作用以及触发时机是最重要的,使用其实很简单。去看崔老师视频吧,讲得很清楚了。


观察者模式

大家觉得Listener难在哪?

是监听器种类太多记不清吗?不会吧,不就是“6+2”吗。

是各个监听器的触发时机扑朔迷离吗?也不会啊,上面的图讲得很清楚了。

其实!大家觉得监听器难,是因为根本不知道为啥它能起作用。监听器的底层原理其实涉及到一种设计模式:观察者模式(Observer)。

先来看监听器的定义:

监听器就是一个实现了特定接口的普通Java程序,这个程序专门用于监听另一个Java对象的方法调用或者属性改变。当被监听对象发生上述事件后,监听器某个方法将立即被执行。

image.png

三个概念:被监听对象、事件、监听器对象

道理都懂,但是还是没讲到点子上。为啥监听器能监听?

程序毕竟不是人,不像警察盯着小偷,看到他偷东西就动手抓人。在我看来,一个方法能执行,必然是被调用!有两种可能:

  • 定时任务
  • 对象调用/静态方法调用

监听器肯定不是通过定时任务实现的,毕竟它的方法调用时机是在“被监听对象特定行为发生时”。既然不是定时任务,那么肯定是被监听对象主动告诉监听器的!

是不是觉得很荒唐?被监听对象主动告诉监听对象,那还叫监听器?监听个鬼哦...

但是大家有没有想过,监听器英文名叫Listener,我翻译成“倾听者”有何不可?就是说,Listener对象一直在侧耳倾听,等待被监听对象发号施令。这个翻译骚不骚?

也就是说:被监听对象发生某个行为时,会主动告诉Listener(对象方法调用),让它执行对应的特定操作!

image.png

代码结构

image.png

被监听对象

// 被监听对象
public class Thief {

    private ThiefListener listener;

    public void registerListener(ThiefListener listener) {
        this.listener = listener;
    }

    public void steal() {
        // 偷之前,告诉警察
        if (listener != null) {
            Event event = new Event(this);
            // 喂,有胆开枪啊!
            listener.shot(event);
        }
        // 偷东西
        System.out.println("to steal money...");
    }

}

监听器接口

// 它的实现类就是监听器对象
public interface ThiefListener {
    void shot(Event event);
}

事件(就是包装后的被监听对象)

public class Event {

    private Thief thief;

    public Event() {
    }

    public Event(Thief thief) {
        this.thief = thief;
    }

    public Thief getThief() {
        return thief;
    }

    public void setThief(Thief thief) {
        this.thief = thief;
    }
}

测试

public class ObserverTest {
    public static void main(String[] args) {
        // 被监听对象
        Thief thief = new Thief();

        // 监听器,直接new一个接口的匿名类对象
        ThiefListener thiefListener = new ThiefListener() {
            public void shot(Event event) {
                System.out.println("啪啪啪!!!!");
            }
        };

        // 注册监听
        thief.registerListener(thiefListener);

        // 特定行为,触发监听器:内部调用listener.shot()
        thief.steal();
    }
}

标签: Java Web Listener

评论已关闭