没什么卵用的监听器设计思路

890 阅读4分钟

监听器

编写一个监听器模式其实很简单,即向一个god object添加监听回调方法,然后在触发事件的地方调用god object便利现阶段已知的回调方法即可。

想到啥就写点啥,写的不对请指正,菜鸡一只。

关于监听器的思考

  1. 监听器的设计方案依靠于god object。
  2. 监听器适配于单一组件或流程内(类似于SpringApplicationRunListener,整个接口的每一个方法对应一个事件节点),还是说方便于业务流程分组设计(类似于ApplicationListener)。
  3. 如果说要根据业务分组那么触发事件的时候怎么操作才能更简单,更方便使用人调用。
  4. 线程问题,感觉监听场景下不需要过度思考这个问题。
  5. 分布场景下的远程触发问题。事务问题不在监听范围内。

思考

  1. 两种设计思路,一种是编写一个监听管理类,使用非静态方法,缺点就是监听管理类需要自行保持和管理,好处就是监听被实际的隔离开了。另外就是使用全局的静态监听管理类。缺点就是所有的监听最终是被一个类管理的,好处就是使用和注册变得方便了。

image.png

image.png

  1. 问题来到怎么触发一个事件,可以根据事件进行区分,根据触发的事件类型不同来触发监听这个事件的所有回调方法。或者可以根据监听器的不同来区分。归根结蒂是为了把事件扩散到对应的的回调上去。
  2. 那么在分布式的场景下显然采取一个全局静态的监听管理类是合适的,创建监听器的操作是无需同步的,因为监听只是为了听,需要扩散到各各节点的只有事件的内容。

一种设计的思路

这个设计思路隐去了事件规范这个东西,把重心放在了事件的放大上。
设计一个全局的静态上下文(关于add为什么要指定Class,java支持接口多实现,这里不想做多余的限制,只需要有手段实现了listener即可,也就是说这里添加的类是可以继承多个监听器的,写代码去识别子类父类究竟都实现了那些监听器实在是感觉多此一举,不如传入了或者像spring那样使用类上的泛型来实现也可以,但传入是最直观的)

public class ListenerContext {
    private ListenerContext() {
    }

    private static final Map<Class<? extends Listener>, ListenerHolder> LISTENER_HOLDER_CACHE =
            new ConcurrentHashMap<>();

    public static void addListener(Class<? extends Listener> listenerType, Listener listener) {
        createHolderIfAbsent(listenerType).addListener(listener);
    }

    public static void removeListener(Class<? extends Listener> listenerType, Listener listener) {
        ListenerHolder listenerHolder = LISTENER_HOLDER_CACHE.get(listenerType);
        if (listenerHolder != null) {
            listenerHolder.removeListener(listener);
        }
    }
    private static ListenerHolder createHolderIfAbsent(Class<? extends Listener> clazz) {
            return LISTENER_HOLDER_CACHE.computeIfAbsent(clazz, key -> new ListenerHolder(clazz));
    }
}

一个本地的监听器持有集合

public class ListenerHolder {

    private final Class<? extends Listener> clazz;

    private final Set<Listener> listeners = new HashSet<>();

    public <T extends Listener> ListenerHolder(Class<T> clazz) {
        this.clazz = clazz;
    }

    public void addListener(Listener listener) {
        if (clazz.equals(listener.getClass())) {
            listeners.add(listener);
        }
    }

    public void removeListener(Listener listener) {
        if (clazz.equals(listener.getClass())) {
            listeners.remove(listener);
        }
    }

    protected Class<? extends Listener> getHolderClass() {
        return clazz;
    }
}

问题来到事件的触发上,在上面这个逻辑下需要从ListenerContext中根据监听器的类型获取holder对象然后循环触发事件,我考虑有没有更方便更规范的开发的方式来触发事件。
我想到了代理,用cglib代理监听器,重写其中的每一个方法的实现为获取holder对象触发事件,这个代理得来的对象就是触发事件的扳机
要剔除object对象的一些方法,这里还可以添加分布场景下的远程调用方案。

public class ListenerProxyFactory {

    private static final List<String> METHOD_NAME = Arrays.asList("hashCode", "equals", "toString", "clone");

    private static final RpcProgrammeFactory RPC_PROGRAMME_FACTORY=new RpcProgrammeFactory();

    @SuppressWarnings("unchecked")
    public <T extends Listener> T createListenerProxy(ListenerHolder listenerHolder, Class<T> clazz, boolean canRemote) {
        Enhancer enhancer = getEnhancer(listenerHolder, clazz, canRemote);
        return (T) enhancer.create();
    }

    @SuppressWarnings("unchecked")
    public <T extends Listener> T createListenerProxy(ListenerHolder listenerHolder, Class<T> clazz, boolean canRemote, Class<?>[] argumentTypes, Object[] arguments) {
        Enhancer enhancer = getEnhancer(listenerHolder, clazz, canRemote);
        return (T) enhancer.create(argumentTypes, arguments);
    }

    private <T extends Listener> Enhancer getEnhancer(ListenerHolder listenerHolder, Class<T> clazz, boolean canRemote) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(canRemote ? new RemoteEnhancerInterceptor(listenerHolder) : new LocalEnhancerInterceptor(listenerHolder));
        enhancer.setInterceptDuringConstruction(false);
        return enhancer;
    }

    private static class LocalEnhancerInterceptor implements MethodInterceptor {
        private final ListenerHolder listenerHolder;

        private LocalEnhancerInterceptor(ListenerHolder listenerHolder) {
            this.listenerHolder = listenerHolder;
        }

        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) {
            /*
            这里对操作进行放大,把被触发的方法扩散到每一个监听者身上
             */
            if (!METHOD_NAME.contains(method.getName())) {
                listenerHolder.notice(method, args);
            }
            return null;
        }
    }

    private static class RemoteEnhancerInterceptor implements MethodInterceptor {

        private final ListenerHolder listenerHolder;

        private RemoteEnhancerInterceptor(ListenerHolder listenerHolder) {
            this.listenerHolder = listenerHolder;
        }

        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) {
            if (!METHOD_NAME.contains(method.getName())) {
                RPC_PROGRAMME_FACTORY.getRpcProgramme().call(listenerHolder.getHolderClass(), method, args);
            }
            return null;
        }
    }
}

然后在holder中添加调用的方式

public void notice(Method method, Object[] args) {
    listeners.parallelStream().forEach(listener -> {
        try {
            method.invoke(listener, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.error("listener notice error", e);
        }
    });
}

为了调用者使用的简单,还需要把创建代理对象的操作提升到ListenerContext中

public static <T extends Listener> T getTrigger(Class<? extends Listener> clazz) {
    return getTrigger(clazz, false);
}

@SuppressWarnings("unchecked")
public static <T extends Listener> T getTrigger(Class<? extends Listener> clazz, boolean canRemote) {
    return (T) TRIGGER_CACHE.computeIfAbsent(clazz,
            key -> LISTENER_PROXY_FACTORY.createListenerProxy(createHolderIfAbsent(clazz), clazz, canRemote));
}

测试
首先创建一个listener 当然也可以是个类 或者抽象类 无关痛痒

public interface ListenerInterface extends Listener {

    void doSth();

}

然后使用它

@Test
public void test1(){
        ListenerContext.addListener(ListenerInterface.class, (ListenerInterface) () -> System.out.println("aa"));
        ListenerContext.addListener(ListenerInterface.class,new ListenerInterface() {
            @Override
            public void doSth() {
                System.out.println("bb");
            }
        });
        ListenerInterface trigger = ListenerContext.getTrigger(ListenerInterface.class);
        trigger.doSth();
}

接下来是关于远程调用# 没什么卵用的监听器设计思路(远程触发篇)