`
ayufox
  • 浏览: 273734 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

面向事件编程

阅读更多

1.前言
    当一个项目启动的时候,我们首先需要面向下面的问题:

  • 实现策略分离
  • 组件之间依赖绑定
  • 生命期控制
  • 需要发布一些事情告诉感兴趣的组件
  • (optional)组件隔离

      第一个问题通过面向接口编程的方式,可以让服务使用者不必关心服务是如何实现的,第二和第三个问题则是一个良好的IOC容器能够解决的,譬如Spring,第四个问题当然是一个事件通知机制,Spring对事件做了支持,第五个问题是OSGI能够处理的问题,一般没有那么严格高要求隔离化,这里不深入探讨。在面向这些问题的时候,我们基本上会选择Spring来解决,事实上Spring也做的足够好了,唯一的遗憾可能就是Spring现在实在是不能称之为一个“轻量级”的容器,即使只使用基础包也足有上M之多。
      在开发BlackStar的时候同样的面对这些问题,而这里,我们使用一种不同的方式来解决这些问题,即所谓的面向事件编程,使用事件机制来解决如上的问题。
2.BlackStar背景介绍
      在开始之前,我们先了解一下BlackStar的问题背景,BlackStar大概地分成如下几个部分:
2.1 基础组件:

  • 调度服务:很多其他组件需要定时执行的时候需要使用到调度服务,譬如JVM监控需要每分钟执行一次,该组件使用Quartz实现
  • Web服务:很多其他组件需要以Web的方式提供服务出去,譬如JMX Proxy需要以Hessian的方式提供JMX接入,UI组件需要展示页面信息,该组件使用Jetty实现
  • 本地JVM JMX管理服务:负责自动识别本地的JVM、并负责与本地JVM的连接,其他组件需要依赖于这个服务,譬如JMX Proxy需要获得所有的本地JVM JMX以进行Proxy、JVM Monitor需要获得本地JVM JMX的变更情况以对本地JVM进行监控,这个服务需要依赖于调度服务以进行周期性地检测本地JVM的变更

2.2插件组件:

  • JMX Proxy:负责将本地的JVM JMX服务Proxy给外部,外部可以使用JConsole接入,需要依赖于本地JVM JMX管理服务
  • JVM Monitor:负责监控本地JVM的数据(CPU、PermSpace、TenuredSpace、Thread等),需要依赖于本地JVM JMX管理服务和调度服务(定期检测)
  • Performance Stat:性能统计服务,需要依赖于调度服务以定时执行
  • UI:将统计数据以Web的方式提供给外部,需要依赖于Web服务和本地JVM JMX管理服务

3.BlackStar内部组成结构
     那么BlackStar是如何解决前言中提出的问题的呢?我们首先看下面的图,所有的组件都会以EventListener的方式存在,并注册到EventMediator上;基础组件部分提供一些服务事件出去,并监听这些事件;而服务使用者当需要使用到服务的时候发布相应的服务事件出去,基础组件收到事件后进行处理;EventMediator充当一个统一的中介者。当然,系统会有统一的生命周期事件(Startup、AfterStartup、BeforeShutdown、Shutdown),由系统统一发布。

4.具体实现
     我们来看看具体是如何实现的
4.1核心的Event部分:
     整个Event机制实现其实非常简单
4.1.1事件监听者EventListener,如果需要接收事件,实现这个接口

public interface EventListener<T extends Event>
{
    /**
     * 需要监听的事件
     * @return
     */
    Class[] events();
   
    /**
     * 事件处理
     * @param event
     * @throws Exception
     */
    void onEvent(T event) throws Exception;
}

4.1.2事件中间者,负责Listener注册和事件分发(EventMediator):

public abstract class EventMediator implements EventDispatcher
{
    private static EventMediator INSTANCE = new DefaultEventMediator();

    public static void setInstance(EventMediator instance)
    {
        if (instance == null)
        {
            throw new IllegalArgumentException("instance can't be null");
        }
        INSTANCE = instance;
    }

    public abstract void dispatch(Event event);

    public abstract void register(EventListener eventListener);

    /**
     * 注册Listener
     * @param eventListener
     */
    public static void registerListener(EventListener eventListener)
    {
        INSTANCE.register(eventListener);
    }

    /**
     * 分发事件被关注的监听者
     * @param event
     */
    public static void dispatchEvent(Event event)
    {
        INSTANCE.dispatch(event);
    }
}

4.1.2事件分发,如果需要事件分发,可以继承自EventDispatcherSupport

public interface EventDispatcher
{
    /**
     * 事件分发
     * @param event
     */
    void dispatch(Event event);
}

public class EventDispatcherSupport implements EventDispatcher
{
    public void dispatch(Event event)
    {
        EventMediator.dispatchEvent(event);
    }
}

4.2系统启动与系统事件
4.2.1
    现在我们的系统准备开始启动了,非常简单,就是从配置中读取所有的Listener,注册,并发送StartupEvent和AfterStartupEvent

   public static void main(String[] args) throws Exception
    {
        String listeners = AgentConfig.getProperty("listeners");
        String[] listenerArray = listeners.split(",");
        for (String listener : listenerArray)
        {
            final EventListener eventListener = (EventListener) Class.forName(
                    listener.trim()).newInstance();
            EventMediator.registerListener(eventListener);
            LOGGER.info("Regist EventListner[" + listener + "]");
        }
        EventMediator.registerListener(new ShutdownHook());

        LOGGER.info("Dispatch StartupEvent");
        EventMediator.dispatchEvent(new StartupEvent());
        LOGGER.info("Dispatch AfterStartupEvent");
        EventMediator.dispatchEvent(new AfterStartupEvent());
        LOGGER.info("Startup Success");
    }
 

4.2.2 从上面我们可以看到一个特殊的Listener——ShutdownHook,其负责向JVM注册ShutdownHook事件,在JVM关闭的时候向发布BeforeShutdownEvent和ShutdownEvent

public class ShutdownHook extends EventDispatcherSupport implements
        EventListener<StartupEvent>
{
    private final static Log LOGGER = LogFactory.getLog(ShutdownHook.class);

    private static boolean isShutdown = false;;

    public Class[] events()
    {
        return new Class[]
        { StartupEvent.class };
    }

    public void onEvent(StartupEvent event) throws Exception
    {
        Runtime.getRuntime().addShutdownHook(new Thread()
        {
            public void run()
            {
                synchronized (ShutdownHook.class)
                {
                    if (!isShutdown)
                    {
                        LOGGER.info("Dispatch BeforeShutdown");
                        dispatch(new BeforeShutdownEvent());
                        LOGGER.info("Dispatch Shutdown");
                        dispatch(new ShutdownEvent());
                        LOGGER.info("Shutdown Success");
                        isShutdown = true;
                    }
                    else
                    {
                        LOGGER.error("Duplicate Shutdown");
                    }
                }
            }
        });
    }
}

4.2.3系统事件
    从上面我们可以看到StartupEvent、AfterStartupEvent、BeforeShutdownEvent、ShutdownEvent,如果我们的Listener需要进行这些声明期的控制,譬如初始化、销毁对象之类的,可以注册这些事件,譬如如上的ShutdownHook,在其他Listener都初始化完成之后才去注册ShutdownHook
4.3如何使用一个服务
     现在,系统已经把我们的Listener注册进去,并发送了启动事件让我们可以从容进行初始化的工作,现在我们的服务需要依赖于其他的服务,该如何做呢?我们以Monitor组件为例,Monitor组件需要定时执行程序。
    首先,调度基础服务定义了调度事件,如下

public class ScheduleEvent extends Event
{
    private ScheduleTask task;
    private String cronExpression;

    public ScheduleEvent(String name, String cronExpression, ScheduleTask task)
    {
        super(name);
        this.cronExpression = cronExpression;
        this.task = task;
    }

    public ScheduleEvent(String cronExpression, ScheduleTask task)
    {
        this(null, cronExpression, task);
    }

    public String getCronExpression()
    {
        return cronExpression;
    }

    public ScheduleTask getTask()
    {
        return task;
    }
}

    我们的服务需要调度,则只需要发布这个事件即可以,调度服务自动会处理这个事件,如下

final Monitor monitor = monitorInfo.getMonitorTask();
monitor.init(proxy);
String jobName = "jvm" + proxy.getId() + "_"
                        + monitor.getName();
//分布定时调度
dispatch(new ScheduleEvent(jobName, monitorInfo
            .getCronExpression(), new ScheduleTask()
            {
                public void schedule()
                {
                    monitor.onTimeout();
                }
        }));

4.4服务如何处理服务事件
    在上面中,服务使用者发布了一个调度事件,则调度服务提供者接受到了这个事件,可以开始处理

public void onEvent(Event event) throws Exception
    {
        ……
        else if (event instanceof ScheduleEvent)
        {
            if (!scheduler.isShutdown())
            {
                ScheduleEvent se = (ScheduleEvent) event;
                if (se.getId() == null)
                {
                    schedule(se.getCronExpression(), se.getTask());
                } else
                {
                    schedule(se.getId(), se.getCronExpression(), se.getTask());
                }
            }
        ……
    }

4.5数据查询如何做
    数据查询是面向事件编程最难处理也处理地最不好的部分,不过我们还是补充上吧。提供数据查询服务的服务提供方需要将自己的接口发布出来,而服务使用者接收到这个发布声明后,将其作为自己的一个属性
    如下,服务发布方

public synchronized void startup()
    {
        ……

        dispatch(new JMXProxyManagerStartupEvent(this));
    }

    服务使用方

public class JMXProxyUtils implements
        EventListener<JMXProxyManagerStartupEvent>
{
    private static JMXProxyManager proxyManager;

    public Class[] events()
    {
        return new Class[]
        { JMXProxyManagerStartupEvent.class };
    }

    public void onEvent(JMXProxyManagerStartupEvent event) throws Exception
    {
        proxyManager = event.getProxyManager();
    }
     ……
}

4.6变更事件通知
    变更事件通知是面向事件的老本行,处理方式与其他一致,譬如当识别到本地的一个JVM时,则需要创建它,并将其发布出去,通知其他组件有一个新的JVM实例产生了

   protected synchronized boolean startupLocalJVM(LocalJVM localJVM)
    {
        if (!localJVM.start())
        {
            return false;
        }
        jvms.put(localJVM.getPid(), localJVM);

        dispatch(new JMXProxyStartupEvent(localJVM));

        return true;
    }

    其他组件接收到这个事件,则可以进行自己的处理

    public void onEvent(Event event) throws Exception
    {
        if (event instanceof JMXProxyStartupEvent)
        {
            startup(((JMXProxyStartupEvent) event).getJMXProxy());
        } else if (event instanceof JMXProxyShutdownEvent)
        {
            shutdown(((JMXProxyShutdownEvent) event).getJMXProxy());
        }
    }

5.结束语
    一般而言,面向接口编程混合事件编程是一种通用的处理问题的方式,但缺陷在于需要有一个良好的生命周期机制和一个依赖管理组件,解决这两个问题一般依赖于一个设计良好的IOC容器。而面向事件编程是一种相对没有那么自然的处理问题的方式,当我们不希望引入一个复杂的IOC容器来解决我们的问题的时候,应该算是一个不错的选择。当然,其固有的缺陷也是相当明显,当项目比较庞杂服务众多的时候,定义事件都会让人不胜其扰,组件之间查询类的依赖比较多的时候,基本上也不是一个好的选择。一般这种纯粹面向事件的方式比较适应于小规模的、核心服务相对比较稳定而且组件之间比较少依赖于查询(没有返回结果,允许异步化处理的类型)的独立应用。

3
3
分享到:
评论

相关推荐

    plc面向对象编程架构与实现

    面向对象编程是计算机语言的一种先进的编程模式,在工业控制系统的PLC程序中也可以采用这种设计思想,虽然我们无法实现面向对象的很多特点如“继承”,甚至于它根本就不具备面向对象编程语言的特点,但面向对象编程...

    面向接口编程。面向接口编程。

    面向接口编程。面向接口编程。面向接口编程。

    java面向对象编程源码

    本书内容由浅入深,紧密结合实际,利用大量典型实例,详细讲解Java面向对象的编程思想、编程语法和设计模式,介绍常见Java类库的用法,总结优化 Java编程的各种宝贵经验,深入阐述Java虚拟机执行Java程序的原理。...

    漫画面向对象编程 Java

    借助于漫画展示的形式,面向对象的简、由类创建一个对象的方法、类的编写与对象的创建、类的构造函数、类的方法、修饰符、... 可以在轻松幽默的氛围中对面向对象编程产生浓厚的兴趣,从而为后续的编程进阶树立信心。

    Python 3面向对象编程

    《Python 3面向对象编程》通过Python 的数据结构、语法、设计模式,从简单到复杂,从初级到高级,一步步通过例子来展示了Python 中面向对象的概念和原则。, 《Python 3面向对象编程》不是Python 的入门书籍,适合...

    面向对象编程与非面向对象编程

    面向对象编程与非面向对象编程

    matlab面向对象编程.pdf

    matlab面向对象编程.pdf

    Spring AOP面向方面编程原理Spring AOP面向方面编程原理

    Spring AOP面向方面编程原理Spring AOP面向方面编程原理Spring AOP面向方面编程原理Spring AOP面向方面编程原理Spring AOP面向方面编程原理Spring AOP面向方面编程原理Spring AOP面向方面编程原理

    面向对象编程(Java).pdf

    包含面向对象编程所有基础知识和实战代码

    面向接口编程详解

    面向接口编程详解

    用C语言实现面向对象编程.pdf

    用C语言实现面向对象编程.pdf

    JavaScript面向对象编程指南

    《JavaScript面向对象编程指南》内容包括:JavaScript作为一门浏览器语言的核心思想;面向对象编程的基础知识及其在JavaScript中的运用;数据类型、操作符以及流程控制语句;函数、闭包、对象和原型等概念,以代码...

    Python+3面向对象编程.

    《Python 3面向对象编程》通过Python 的数据结构、语法、设计模式,从简单到复杂,从初级到高级,一步步通过例子来展示了Python 中面向对象的概念和原则。, 《Python 3面向对象编程》不是Python 的入门书籍,适合...

    面向接口编程而不是面向实现编程

    面向接口编程而不是面向实现编程实例,面向接口编程而不是面向实现编程实例。

    c++面向对象编程实例大全

    在次有大量的关于c++面向对象编程的实例 对于初级的学习者有很大的帮助

    Python3面向对象编程

    《Python 3面向对象编程》通过Python 的数据结构、语法、设计模式,从简单到复杂,从初级到高级,一步步通过例子来展示了Python 中面向对象的概念和原则。 《Python 3面向对象编程》不是Python 的入门书籍,适合具有...

    写给大家看的面向对象编程书(第3版).pdf

    《写给大家看的面向对象编程书(第3版)》是一部独具特色的面向对象技术著作。书中结合代码示例生动透彻地讲述了面向对象思想的精髓,让读者真正学会以对象方式进行思考。此外,《写给大家看的面向对象编程书(第3版)》...

    Labview面向对象编程快速入门.pdf

    Labview面向对象编程快速入门

Global site tag (gtag.js) - Google Analytics