基于Lookup的IOC事件总线框架

本文介绍如何利用Netbeans IDE中的Lookup机制构建一个轻量级的消息系统。该系统采用面向接口的设计,通过定义不同优先级的消息处理者来实现消息的有序处理。文章还提供了具体的代码实现和测试案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Lookup在Netbeans IDE中可以说是整个软件架构系统的灵魂。从本质上讲就是以apdatable模式实现的一个Map,其主要功能体现以下几个方面:

  1. 提供一个Context 可以想象Lookup是一个magic bag可以在运行时(runtime)方便的在bag中添加、删除对象 而这些对象恰好可以组织成系统当前的context get it?
  2. 由于Adaptable的实现方式(核心是泛型),准确的用Class作为参数查找Map中的实例避免了类型转换的问题,把问题留在了编译时。
  3. Map内部的监听机制 可以实现热插拔 动态更新的功能
基于Lookup这样令人兴奋的功能,我实在忍不住想用它来实现一个消息系统。于是我突然面临两个选择 用publisher/subscriber? 还是IOC的? 两者的代码量都不大,但是我更青睐IOC 虽然有悖“开 闭”原则但对于一个小的系统还是足够了 毕竟300行代码 我相信它的成长空间还是巨大的。多说无益直接上代码

/**
 *
 * @author Vijay
 * of course  you can say Callback itself is one of subscribers
 */
public interface Subscriber {

    /**
     * the smaller priority will get done earlier
     *
     * @return
     */
    Integer priority(Message msg);
    
}

/**
 * All callbacks have to extends this ifc thanks for your cooperation
 *
 * @author Vijay
 */
public interface Callback extends Subscriber {

    final static Integer DEFAULT_PRIORITY = 0;
    final static Integer HIGH_PRIORITY = 1;
    final static Integer MEDIUM_PRIORITY = 10;
    final static Integer LOW_PRIORITY = 100;

    /**
     * The <code>call</code> method is called when required, and is given a
     * single argument of type P, with a requirement that an object of type R is
     * returned.
     *
     * @param param The single argument upon which the returned value should be
     * determined.
     * @return An object of type R that may be determined based on the provided
     * parameter value.
     */
    public void call(Message msg);


    /*
     we only broadcast abstract message
     */
    public abstract class Message {

        final private Param param;

        protected Message(Param param) {
            this.param = param;
        }

        public Param getParam() {
            return param;
        }

        public abstract Class<? extends Callback> getRelatedCallback();

        public static Message createLocate(Param param) {
            Message message = new Locate(param);
            return message;
        }

        /**
         * concrete built-in message here and always come with Operation(Callback)
         */
        //locate
        private static class Locate extends Message {

            private Locate(Param param) {
                super(param);
            }

            @Override
            public Class getRelatedCallback() {
                return Moveable.class;
            }

          
        }

        public interface Moveable extends Callback {
        }

        //add
        private static class Add extends Message {

            private Add(Param param) {
                super(param);
            }

            @Override
            public Class getRelatedCallback() {
                return Addable.class;
            }
        }

        public interface Addable extends Callback {

        }

        //delete ....   continue
    }


}

/**
 *
 * @author Vijay
 */
public interface Context {
    Lookup getBusContext();
}

/**
 *
 * @author Vijay
 */
public class DefaultContext implements Context{

    @Override
    public Lookup getBusContext() {
       return  Utilities.actionsGlobalContext();
    }

}
import java.util.Map;

/**
 * Sometimes we needs to define data structure to get parameter
 *
 *
 */
public final class Param {

    final Map<String, Object> attachments;
    final Integer priority;

    private Param(Integer priority, Map<String, Object> attachments) {
        this.priority = priority;
        this.attachments = attachments;
    }

    public static Param empty() {
        return new Param(Callback.DEFAULT_PRIORITY, null);
    }

    public final Param priority(Integer priority) {
        return new Param(priority, this.attachments);
    }

    public final Param attachments(Map<String, Object> attachments) {
        return new Param(this.priority, attachments);
    }

    public Integer getPriority() {
        return priority;
    }

    public Map<String, Object> getAttachments() {
        return attachments;
    }
}

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.openide.util.Lookup;

/**
 * Treat Context as a System Bus
 *
 * @author Vijay
 */
public final class BusEngine {

    private static final BusEngine instance = new BusEngine();
    private final ExecutorService executor;

    static {
    }

    private BusEngine() {
        executor = Executors.newSingleThreadExecutor();
    }

    public void broadcast(final Callback.Message msg) {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                doSyncCallback(msg,new DefaultContext().getBusContext());
            }
        });
    }

    void broadcast(final Callback.Message msg,final Lookup lkp) {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                doSyncCallback(msg,lkp);
            }
        });
    }
    
    public static BusEngine getDefault() {
        return instance;
    }
    
    @Deprecated 
     static void refreshAllInContext(Callback.Message msg,Context cxt) {
        doSyncCallback(msg,cxt.getBusContext());
    }

     static void doSyncCallback(final Callback.Message msg,Lookup lkp) {
        Lookup.Template template = new Lookup.Template(msg.getRelatedCallback());
        Lookup.Result result = lkp.lookup(template);
        Collection c = result.allInstances();
        List<Callback> callbacks = new ArrayList();
        callbacks.addAll(c);
        Collections.sort(callbacks, new Comparator<Callback>() {
            @Override
            public int compare(Callback o1, Callback o2) {
                return o1.priority(msg)-o2.priority(msg);
            }
        });
        for (Callback cb : callbacks) {
              processMsg(cb, msg);
        }
    }

     static void processMsg(Callback cb, final Callback.Message msg) {
         cb.call(msg);
    }
     
}


以上代码需要依赖org.openide.util.jar 和org.openide.util.lookup.jar  由于测试需要有些方法开放为包可见

为了说明其用例 请参考以下测试代码

import java.util.ArrayList;
import java.util.List;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 *
 * @author Vijay
 */
public class BusEngineNGTest {

    /**
     * use list to test priority
     */
    static List<Callback> GLOBAL_TEST_LIST;

    public BusEngineNGTest() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
    }

    @BeforeMethod
    public void setUpMethod() throws Exception {
        GLOBAL_TEST_LIST = new ArrayList();
    }

    @AfterMethod
    public void tearDownMethod() throws Exception {
        GLOBAL_TEST_LIST = null;
    }

    @Test
    public void testBasicFunctionMsg1() throws InterruptedException {
        BusEngine.getDefault().broadcast(new CBMsg1(Param.empty()), new TestContext().getBusContext());
        Thread.sleep(1000);
        Assert.assertEquals(GLOBAL_TEST_LIST.size(), 1);
        Assert.assertEquals(GLOBAL_TEST_LIST.get(0).priority(null),new Integer(1));
        
    }

    @Test
    public void testBasicFunctionMsg2() throws InterruptedException {
        BusEngine.getDefault().broadcast(new CBMsg2(Param.empty()), new TestContext().getBusContext());
        Thread.sleep(1000);
        Assert.assertEquals(GLOBAL_TEST_LIST.size(), 2);
        Assert.assertEquals(GLOBAL_TEST_LIST.get(0).priority(null),new Integer(1));
        Assert.assertEquals(GLOBAL_TEST_LIST.get(1).priority(null),new Integer(10));
    }
}

/**
 *
 * @author Vijay
 */
public interface CB1 extends Callback{
    
}
/**
 *
 * @author Vijay
 */
public interface CB2 extends Callback{
    
}
/**
 *
 * @author Vijay
 */
public class CBMsg1 extends Message{

    public CBMsg1(Param param) {
        super(param);
    }

    @Override
    public  Class<? extends Callback> getRelatedCallback() {
        return CB1.class;
    }    
}
/**
 *
 * @author Vijay
 */
public class CBMsg2 extends Message {

    public CBMsg2(Param param) {
        super(param);
    }

    @Override
    public Class getRelatedCallback() {
        return CB2.class;
    }
}
/**
 *
 * @author Vijay
 */
public class Subscriber1 implements CB1, CB2 {

    @Override
    public void call(Message msg) {
        final String CBName = msg.getRelatedCallback().getSimpleName();
        if ("CB1".equals(CBName)) {
            Assert.assertEquals(CBName, "CB1");
        } else {
            Assert.assertEquals(CBName, "CB2");
        }
        BusEngineNGTest.GLOBAL_TEST_LIST.add(this);
    }

    @Override
    public Integer priority(Message msg) {
        return 1;
    }
}
/**
 *
 * @author Vijay
 */
public class Subscriber2 implements CB2{

    @Override
    public void call(Message msg) {
        final String simpleName = msg.getRelatedCallback().getSimpleName();
        Assert.assertEquals("CB2",simpleName);
        BusEngineNGTest.GLOBAL_TEST_LIST.add(this);
    }

    @Override
    public Integer priority(Message msg) {
        return 10;
    }
    
}
/**
 *
 * @author Vijay
 */
public class TestContext implements Context{
    
    @Override
    public Lookup getBusContext() {
        return Lookups.fixed(new Subscriber1(),new Subscriber2());
    }
}

我所说的有悖“开闭”原则 是指我们必须实现Callback的call方法 这样如果Subscriber实现多个Callback 不得不以Message来区分。目前我还没有想到解决的办法... 望各位看官不吝指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值