java的listener是啥原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java的listener是啥原理相关的知识,希望对你有一定的参考价值。

回调,


Java的事件监听机制是这样的:

事件的观察者向事件的发出者进行注册,当事件发生时,事件的发出者调用之前注册的回调函数,将相关事件信息通知到事件的观察者。这个过程中,当然不用事件的观察者进行轮询。

你可能有个问题:那么事件的发出者如何得知事件发生呢?实际上举个例子来说如窗口类,有个窗口关闭事件可以注册,作为窗口类来说内部有相关关闭窗口的方法,只要这些方法被调用,当然就知道窗口被关闭了,只要这些方法在合适的时机调用回调函数即可。

事件的观察者可以没有,有一个或多个,只要再事件的发出者内部维护一个List<XxxListener>即可,到时候迭代列表依次发出事件即可。(这里一般不设计为多线程,而是单线程依次发送,所以事件处理函数最好不要阻塞太长时间,否则会影响到下一个观察者,甚至事件发出方代码的调用)



Java 事件处理方法是基于授权事件模型
事件源生成事件并将其发送至一个或多个监听器
监听器简单地等待,直到它收到一个事件。一旦事件被接受,监听器将处理这些事件,然后返回。

事件:在授权事件模型中,事件是一个描述事件源状态改变的对象 。  
通过鼠标、键盘与 GUI 界面直接或间接交互都会生成事件。 如:按下一个按钮、通过键盘输入一个字符、选择列表框中的一项、点击一下鼠标等。

事件源:事件源是一个生成事件的对象
一个事件源可能会生成不同类型的事件
事件源提供了一组方法,用于为事件注册一个或多个监听器。
每种事件的类型都有其自己的注册方法。一般形式为:
public void add<EventType>Listener (TypeListener e)

AWT采取的事件控制过程:监听器对象属于一个类的实例,这个类实现了一个特殊的接口,名为“监听者接口”
事件源是一个对象,它可以注册一个或多个监听器对象,并向其发送事件对象。
事件源将在发生事件时向所有注册的监听器发送事件对象。  
监听器对象使用事件对象中的信息来确定它们对事件的响应

事件模型:
1.基于代理(授权)事件模型
事件处理是一个事件源授权到一个或者多个事件监听器。其基本原理是:组件激发事件,事件监听器监听和处理事件,可以调用组件的add<EventType>Listener方法向组件注册监听器。把其加入到组件以后,如果组件激发了相应类型的事件,那么定义在监听器中的事件处理方法会被调用。

2.此模型主要由以三种对象为中心组成
事件源       由它来激发产生事件
   是产生或抛出事件的对象。
事件监听器  由它来处理事件
         实现某个特定EventListener 接口,此接口定义了一种或多种方法,事件源调用它们以响应该接口所处理的每一种特定事件类型 。
事件  具体的事件类型
   事件类型封装在以java.util.EventObject为根的类层次中。当事件发生时,事件记录发生的一切事件,并从事件源传播到监听器对象



参考技术A 观察者模式吧

Java三大器之监听器(Listener)的工作原理和代码演示

现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动
而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些
固定的对象等等。首先来看一下ServletContextListener接口的源代码:

public abstract interface ServletContextListener extends EventListener{
public abstract void contextInitialized(ServletContextEvent paramServletContextEvent);
public abstract void contextDestroyed(ServletContextEvent paramServletContextEvent);
}

下面利用监听器对数据库连接池DataSource的初始化演示它的使用:ListenerTest.java
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.commons.dbcp.BasicDataSource;
/**
* 现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的
* 服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化
* 的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。
*
* 示例代码:使用监听器对数据库连接池DataSource进行初始化
*/
public class ListenerTest implements ServletContextListener{
// 应用监听器的销毁方法
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
// 在整个web应用销毁之前调用,将所有应用空间所设置的内容清空
servletContext.removeAttribute("dataSource");
System.out.println("销毁工作完成...");
}
// 应用监听器的初始化方法
public void contextInitialized(ServletContextEvent servletContextEvent) {
// 通过这个事件可以获取整个应用的空间
// 在整个web应用下面启动的时候做一些初始化的内容添加工作
ServletContext servletContext = servletContextEvent.getServletContext();
// 设置一些基本的内容;比如一些参数或者是一些固定的对象
// 创建DataSource对象,连接池技术 dbcp
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName("com.jdbc.Driver");
basicDataSource.setUrl("jdbc:mysqlocalhost:3306/");
basicDataSource.setUsername("root");
basicDataSource.setPassword("root");
basicDataSource.setMaxActive(10);//最大连接数
basicDataSource.setMaxIdle(5);//最大管理数
//bds.setMaxWait(maxWait); 最大等待时间
// 把 DataSource 放入ServletContext空间中,
// 供整个web应用的使用(获取数据库连接)
servletContext.setAttribute("dataSource", basicDataSource);
System.out.println("应用监听器初始化工作完成...");
System.out.println("已经创建DataSource...");
}
}

web.xml中配置如下,很简单:
<!-- 配置应用监听器 -->
<listener>
<listener-class>com.ycq.ListenerTest</listener-class>
</listener>

这样配置好了之后,以后在web应用中就可以通过ServletContext取得BasicDataSource对象,从而获取与数据库的连接,提高性能,方便使用。
示例代码二:

import java.io.File;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.i2f.fsp.deploy.TransactionDeployer;
/**
* 监听器随着项目的启动而启动
*
*/
public class ListenerTest2 implements ServletContextListener{
// 销毁监听器
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("date20161020095500 :" + servletContextEvent.getServletContext());
}
public void contextInitialized(ServletContextEvent servletContextEvent) {
try{
// 获取项目跟路径
String basePath = servletContextEvent.getServletContext().getRealPath("/");
// D:apache-tomcat-6.0.41webappsi2money 绝对路径
System.out.println("basePath20161020094700 :" + basePath);
if (!(basePath.endsWith(File.separator))){
basePath = basePath + File.separator;
}
basePath = basePath + "WEB-INF" + File.separator + "classes" + File.separator;
new TransactionDeployer(basePath).deploy();
// D:apache-tomcat-6.0.41webappsi2moneyWEB-INFclasses
System.out.println("basePath20161020094701 :" + basePath);
}
catch (Exception e){
e.printStackTrace();
System.exit(-1);
}
}
}

示例代码三:
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
public class UserLogoutListener implements HttpSessionListener{
protected final Log log = LogFactory.getLog(super.getClass());
public void sessionCreated(HttpSessionEvent event){
this.log.error("session created. id = " + event.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent event){
this.log.error("session destroyed.id = " + event.getSession().getId());
HttpSession session = event.getSession();
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(session.getServletContext());
OnlineUserMonitorClient client = (OnlineUserMonitorClient)context.getBean("onlineUserMonitorClient");
client.afterSessionDestroyed(session);
}
}

监听器在实际项目中的应用,监听器在java web中应用的较多,比如:统计当前在线人数、自定义session扫描器。
--------------------- 应用一:统计当前在线人数 ---------------------
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @description HttpSessionListener监听器实现统计网站在线人数的功能
*/
public class SessionListener implements HttpSessionListener{

public static int TOTAL_ONLINE_USERS = 0;
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");
// 如果用户退出,TOTAL_ONLINE_USERS自减1
if(TOTAL_ONLINE_USERS == 0){
servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);
}
else{
TOTAL_ONLINE_USERS--;
servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);
}
}

public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");
// 如果用户登录,TOTAL_ONLINE_USERS自增1
if(TOTAL_ONLINE_USERS == 0){
servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);
}
else{
TOTAL_ONLINE_USERS++;
servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);
}
}
}

--------------------- 应用二:自定义session扫描器 ---------------------
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import jeus.util.concurrent50.Collections;
/**
* @description 当网站用户量增加时,session占用的内存会越来越大,这时session的管理,将会是一项很大的
* 系统开销,为了高效的管理session,我们可以写一个监听器,定期清理掉过期的session
*/
public class SessionScanerListener implements HttpSessionListener,ServletContextListener{
// 创建一个线程安全的集合,用来存储session
@SuppressWarnings("unchecked")
List<HttpSession> sessionList = Collections.synchronizedList(new LinkedList<HttpSession>());
private Object lock = new Object();

public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("session 创建成功...");
HttpSession httpSession = httpSessionEvent.getSession();
synchronized (lock){
sessionList.add(httpSession);
}
}

public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("session 销毁成功...");
}
// web应用关闭时触发contextDestroyed事件
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("web应用关闭...");
}

// web应用启动时触发contextInitialized事件
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("web应用初始化...");
// 创建定时器
Timer timer = new Timer();
// 每隔30秒就定时执行任务
timer.schedule(new MyTask(sessionList,lock), 0, 1000*30);
}
}

import java.util.List;
import java.util.ListIterator;
import java.util.TimerTask;
import javax.servlet.http.HttpSession;
/**
* 定时器,定义定时任务的具体内容
*/
public class MyTask extends TimerTask{
private List<HttpSession> list;
// 存储传递过来的锁
private Object lock;
// 构造方法
MyTask(List<HttpSession> list, Object lock){
this.list = list;
this.lock = lock;
}
@Override
public void run() {
// 考虑到多线程的情况,这里必须要同步
synchronized (lock){
System.out.println("定时器开始执行...");
ListIterator<HttpSession> listIterator = list.listIterator();
while(listIterator.hasNext()){
HttpSession httpSession = listIterator.next();
// httpSession.getLastAccessedTime() = session的最后访问时间
if(System.currentTimeMillis() - httpSession.getLastAccessedTime() > 1000*30){
// 手动销毁session
httpSession.invalidate();
// 从集合中移除已经被销毁的session
listIterator.remove();
}
}
}
}
}


























































































































































































































以上是关于java的listener是啥原理的主要内容,如果未能解决你的问题,请参考以下文章

Java三大器之监听器(Listener)的工作原理和代码演示

可以用 lambda 替换 Listener 是啥意思?

tcp135端口状态为listening是啥意思

了解 listen: false 与 Provider<SomeType>.of(context, listen: false) 一起使用时的工作原理

Android编程之Listener侦听的N种写法及实现原理

java定时器 每天凌晨 固定执行一个方法