作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
大致来说,常用的监听器就是“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如何处理请求:
然后我们来看看监听器到底在什么时候做了什么事。
为了帮助大家理解接下来这张图的细节,我问几个问题,大家带着问题去看:
属性监听没画出来,自行脑补
答案:
问题一
问题二
只有当在Servlet中调用request.getSession(),且根据JSESSIONID找不到对于的Session时,才会创建新的Session对象,才会被监听到。第二次请求,浏览器会带上JSESSIONID,此时虽然还是request.getSession(),但是会返回上次那个。根据JSESSIONID去Session这个过程是隐式的,我们看到的就是getSession()。
问题三
对于图中的步骤7/11/12,也就是get/setAttribute()时,会触发属性监听。
搞清楚每一种监听器的作用以及触发时机是最重要的,使用其实很简单。去看崔老师视频吧,讲得很清楚了。
大家觉得Listener难在哪?
是监听器种类太多记不清吗?不会吧,不就是“6+2”吗。
是各个监听器的触发时机扑朔迷离吗?也不会啊,上面的图讲得很清楚了。
其实!大家觉得监听器难,是因为根本不知道为啥它能起作用。监听器的底层原理其实涉及到一种设计模式:观察者模式(Observer)。
先来看监听器的定义:
监听器就是一个实现了特定接口的普通Java程序,这个程序专门用于监听另一个Java对象的方法调用或者属性改变。当被监听对象发生上述事件后,监听器某个方法将立即被执行。
三个概念:被监听对象、事件、监听器对象
道理都懂,但是还是没讲到点子上。为啥监听器能监听?
程序毕竟不是人,不像警察盯着小偷,看到他偷东西就动手抓人。在我看来,一个方法能执行,必然是被调用!有两种可能:
监听器肯定不是通过定时任务实现的,毕竟它的方法调用时机是在“被监听对象特定行为发生时”。既然不是定时任务,那么肯定是被监听对象主动告诉监听器的!
是不是觉得很荒唐?被监听对象主动告诉监听对象,那还叫监听器?监听个鬼哦...
但是大家有没有想过,监听器英文名叫Listener,我翻译成“倾听者”有何不可?就是说,Listener对象一直在侧耳倾听,等待被监听对象发号施令。这个翻译骚不骚?
也就是说:被监听对象发生某个行为时,会主动告诉Listener(对象方法调用),让它执行对应的特定操作!
代码结构
被监听对象
// 被监听对象
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) {
// 我这里并没有用到event,实际上可以从event取出事件源
System.out.println("啪啪啪!!!!");
}
};
// 注册监听
thief.registerListener(thiefListener);
// 特定行为,触发监听器:内部调用listener.shot()
thief.steal();
}
}
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
进群,大家一起学习,一起进步,一起对抗互联网寒冬