The listener

Writing a listener pattern is as simple as adding listener callback methods to a God Object and then calling the God Object convenience known callback methods where the event is triggered.

Think of what to write what, write wrong please correct, a dish chicken.

Thoughts on listeners

  1. The listener design relies on the God Object.
  2. Listener adapter to a single component or process (similar to SpringApplicationRunListener, the interface of each method corresponding to an event node), or is convenient in the design of the business process group (similar to the ApplicationListener).
  3. If you want to group by business, then how to operate when triggering events is easier and more convenient for users to call.
  4. Threading, feel like you don’t have to overthink this in listening scenarios.
  5. Remote triggering problems in distributed scenarios. Transaction issues are not monitored.

thinking

  1. Two design ideas, one is to write a listener management class, using a non-static method, the disadvantage is that the listener management class needs to maintain and manage itself, the advantage is that the listener is actually isolated. The other is to use a global static listener management class. The downside is that all listeners are ultimately managed by a class, and the upside is that it’s easy to use and register.

  1. The question is how to trigger an event, which can be distinguished by the type of event that triggers all the callback methods that listen for that event. Or depending on the listener. The bottom line is to diffuse the event to the corresponding callback.
  2. Therefore, it is obviously appropriate to adopt a global static listener management class in a distributed scenario. The operation of creating listeners does not need to be synchronized, because listeners are only listening and only the content of events need to be diffused to each node.

A design idea

This design approach hides the event specification and focuses on the amplification of events. Java supports multiple implementations of interfaces. The listener can be added to a Class that inherits multiple listeners. Writing code to identify which listeners are implemented by the subclass’s parent class is a waste of time. Passing in or using generics on a class like Spring does work, but passing in is the most intuitive.)

public class ListenerContext {
    private ListenerContext(a) {}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 -> newListenerHolder(clazz)); }}Copy the code

A local listener holds the collection

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() {
        returnclazz; }}Copy the code

In the above logic, we need to get the holder object from the ListenerContext according to the type of listener and then loop to trigger the event. I consider whether there is a more convenient and standardized development way to trigger the event. I came up with the idea of proxies, using cglib proxy listeners, rewriting the implementation of each of these methods in order to get the holder object trigger event, and this proxy object is the trigger event to get rid of some of the object methods, and you can also add remote call schemes for distributed scenarios.

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, booleancanRemote, 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) {
            /* This amplifies the action to spread out the triggered method to each listener */
            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; }}}Copy the code

Then add the invocation method to the 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); }}); }Copy the code

For ease of use by the caller, the creation of the proxy object also needs to be promoted to the 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));
}
Copy the code

The test starts by creating a listener, which can also be a class or abstract class

public interface ListenerInterface extends Listener {

    void doSth(a);

}
Copy the code

And then use it

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

Next is about remote call # no useless listener design ideas (remote trigger article)