没什么卵用的监听器设计思路(远程触发篇)

235 阅读1分钟

书接上文哦,上文(没什么卵用的监听器设计思路

远程触发

远程调用的方案其实多种多样,dobbu,cloud,定时任务,消息队列都可以哒,上文我们预留了一个位置用于编写远程调用的内容。

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())) {
            //todo 
        }
        return null;
    }
}

实现是多种多样的,相比较更符合场景的应该是消息队列,用我手头有的Rabbitmq来实现一个
首先定义一个远程操作的接口

public interface RpcProgramme {

    /**
     * 触发
     *  @param listenerHolder
     * @param rpcEventObject
     */
    void call(ListenerHolder listenerHolder, RpcEventObject rpcEventObject);

    /**
     * 接收
     *
     * @param rpcEventObject
     */
    void receive(RpcEventObject rpcEventObject);

}

远程触发则需要告诉远端哪个类的哪个方法参数是什么,所以RpcEventObject的内容为

@Data
public class RpcEventObject {

    private Class<? extends Listener> clazz;

    private String methodName;

    private Object[] args;

    private Class<?>[] argTypes;

}

在之前的口子上添加代码

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())) {
            RpcEventObject rpcEventObject = new RpcEventObject();
            rpcEventObject.setClazz(listenerHolder.getHolderClass());
            rpcEventObject.setMethodName(method.getName());
            rpcEventObject.setArgs(args);
            Class<?>[] argTypes = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
                argTypes[i] = args[i].getClass();
            }
            rpcEventObject.setArgTypes(argTypes);
            RpcProgrammeFactory.getRpcProgramme().call(listenerHolder, rpcEventObject);
        }
        return null;
    }
}

这里增加了一个RpcProgrammeFactory用于创建远程触发方案。

public class RpcProgrammeFactory {

    private static RpcProgramme rpcProgramme = new LocalRpcProgramme();

    public static RpcProgramme getRpcProgramme() {
        return rpcProgramme;
    }

    public static void switchProgramme(RpcProgramme rpcProgramme) {
        RpcProgrammeFactory.rpcProgramme = rpcProgramme;
    }


}

LocalRpcProgramme是使用本地的触发方案和前面的一样。
然后添加一个Rabbitmq的实现方案,使用rabbitmq的广播订阅发布模型。

public class RabbitRpcProgramme implements RpcProgramme {

    private static final String EXCHANGE = "listener.exchange";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private final Connection connection;
    private Channel producerChannel;
    private Channel consumerChannel;

    public RabbitRpcProgramme(Connection connection) {
        this.connection = connection;
        initProducer();
        initConsumer();
    }

    @SneakyThrows
    private void initProducer() {
        this.producerChannel = this.connection.createChannel();
        //广播模式的交换器
        this.producerChannel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT, false, true, null);
    }

    @SneakyThrows
    public void initConsumer() {
        this.consumerChannel = this.connection.createChannel();
        //临时队列
        String queue = this.consumerChannel.queueDeclare().getQueue();
        //绑定
        this.consumerChannel.queueBind(queue, EXCHANGE, "");
        DefaultConsumer defaultConsumer = new DefaultConsumer(this.consumerChannel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                receive(OBJECT_MAPPER.reader().readValue(body, RpcEventObject.class));
            }
        };
        this.consumerChannel.basicConsume(queue, true, defaultConsumer);
    }

    public void close() throws IOException, TimeoutException {
        producerChannel.close();
        consumerChannel.close();
        connection.close();
    }

    @SneakyThrows
    @Override
    public void call(ListenerHolder listenerHolder, RpcEventObject rpcEventObject) {
        byte[] bytes = OBJECT_MAPPER.writer().writeValueAsBytes(rpcEventObject);
        this.producerChannel.basicPublish(EXCHANGE, "", null, bytes);
    }

    @Override
    public void receive(RpcEventObject rpcEventObject) {
        Class<? extends Listener> clazz = rpcEventObject.getClazz();
        Listener trigger = ListenerContext.getTrigger(clazz);
        String methodName = rpcEventObject.getMethodName();
        try {
            Method method = clazz.getMethod(methodName, rpcEventObject.getArgTypes());
            method.invoke(trigger, rpcEventObject.getArgs());
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        close();
    }
}

然后貌似还有一个问题,在receive方法中获取的扳机依旧会回到触发远程操作这个地方来。
在创建扳机的时候创建两个好了= =简单嘛

public class ListenerContext {

    private ListenerContext() {
    }

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

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

    private static final ListenerProxyFactory LISTENER_PROXY_FACTORY = new ListenerProxyFactory();

    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) {
        T tListener = (T) LOCAL_TRIGGER_CACHE.computeIfAbsent(clazz,
                key -> LISTENER_PROXY_FACTORY.createListenerProxy(createHolderIfAbsent(clazz), clazz, false));
        return canRemote ? (T) REMOTE_TRIGGER_CACHE.computeIfAbsent(clazz,
                key -> LISTENER_PROXY_FACTORY.createListenerProxy(createHolderIfAbsent(clazz), clazz, true)) : tListener;
    }

}

测试

@PostConstruct
public void init(){
    ConnectionFactory connectionFactory = rabbitTemplate.getConnectionFactory();
    Connection delegate = connectionFactory.createConnection().getDelegate();
    RpcProgrammeFactory.switchProgramme(new RabbitRpcProgramme(delegate));
    ListenerContext.addListener(ExampleListener.class, (ExampleListener) System.out::println);
    ListenerContext.addListener(ExampleListener.class, (ExampleListener) System.out::println);
    ListenerContext.addListener(ExampleListener.class, (ExampleListener) System.out::println);
}

@GetMapping("/send")
public void send(String info){
    ExampleListener trigger = ListenerContext.getTrigger(ExampleListener.class,true);
    trigger.print(info);
}