面向事件编程

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容器来解决我们的问题的时候,应该算是一个不错的选择。当然,其固有的缺陷也是相当明显,当项目比较庞杂服务众多的时候,定义事件都会让人不胜其扰,组件之间查询类的依赖比较多的时候,基本上也不是一个好的选择。一般这种纯粹面向事件的方式比较适应于小规模的、核心服务相对比较稳定而且组件之间比较少依赖于查询(没有返回结果,允许异步化处理的类型)的独立应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值