书接上文哦,上文(没什么卵用的监听器设计思路)
远程触发
远程调用的方案其实多种多样,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);
}