我正在使用Java 8事件总线,其中register
方法采用事件类和Java 8函数引用,如: -
class SomeSubscriber {
SomeSubscriber(EventBus eventBus) {
eventBus.register(MyEvent.class, this::onMyEvent);
eventBus.register(SomeOtherEvent.class, this::onSomeOtherEvent);
eventBus.register(YetAnotherEvent.class, this::onYetAnotherEvent);
}
private void onMyEvent(MyEvent e) {
... do something with the MyEvent ...
}
private void onSomeOtherEvent(SomeOtherEvent e) {
... do something with the SomeOtherEvent ...
}
private void onYetAnotherEvent (YetAnotherEvent e) {
... do something with the YetAnotherEvent ...
}
}
发布商可以简单地发布到同一辆公交车: -
class SomePublisher {
SomePublisher(EventBus eventBus) {
eventBus.post(new MyEvent(...));
}
}
我(目前非常简单)EventBus
目前看起来像这样: -
public class EventBus {
private final Map<Class<? extends EventBase>, List<Handler<? extends EventBase>>> subscribers;
public EventBus() {
subscribers = new HashMap<>();
}
public <T extends EventBase> void register(Class<? extends EventBase> eventClass, Handler<T> handler) {
List<Handler<? extends EventBase>> typeSubs =
subscribers.computeIfAbsent(
eventClass,
(e) -> new ArrayList<Handler<? extends EventBase>>());
typeSubs.add(handler);
}
public <T extends EventBase> void post(T event) {
List<Handler<? extends EventBase>> typeSubs = subscribers.get(event.getClass());
for (Handler<? extends EventBase> handler : typeSubs) {
handler.handleEvent((? extends EventBase)event.getClass().asSubclass(event.getClass()));
}
}
}
我的问题在于post
方法 - 它需要以某种方式动态地转换为目标所需的内容,即上例中的MyEvent
。我似乎无法编写post
方法来转换事件或以其他方式满足编译器。即使传入的事件(来自SomePublisher
是MyEvent
个实例,post
方法也无法保留该信息。
我需要做些什么来实现这种动态类型转换。反射? MethodHandles?
另外,我理想情况下不需要EventBase
标记界面。
由于
PS。现有的活动总线图书馆已经足够好了#34;但是使用lambda这会让我感到很兴奋并使我感到紧凑和直接。
答案 0 :(得分:2)
似乎有用的选项是使事件库看起来像这样
public interface EventBase {
default <T extends EventBase> void accept(Handler<T> handler) {
handler.handleEvent((T) this);
}
}
然后让您的事件总线像这样通过它发回。
public <T extends EventBase> void post(T event) {
List<Handler<? extends EventBase>> typeSubs = subscribers.get(event.getClass());
for (Handler<? extends EventBase> handler : typeSubs) {
event.accept(handler);
}
}
虽然事件基础有点乱,所以我不确定我对此感觉如何。但它会根据您的示例代码通过测试。
修改强>: 我稍微简化了EventBase,但我似乎无法摆脱未经检查的演员。
答案 1 :(得分:1)
不幸的是,我认为您尝试做的事情可能无法实现。麻烦在于:你知道你给予处理程序的事件有一个类与处理程序可以接受的类兼容,但只是因为你检索了处理程序这一事实来自您设置的Map
,它按事件类型保存处理程序。但是,编译器并不理解这种逻辑。从它的角度来看,你试图采用一个任意的处理程序,它期望一些特定的事件基础扩展,编译器不知道并给它一个可能或可能的事件不符合预期的类型。也许根据问题进行思考是有帮助的:即使我可以神奇地更改代码,以便在编译器中传递类型为MyEvent
的事件时将其转换为MyEvent
在将它提供给处理程序之前,编译器如何知道处理程序可以接受MyEvent
?所有编译器都知道处理程序接受从EventBase扩展的特定内容。
观察上面的粗体问题,为什么任何反射解决方案都会失败变得更加明确。您可以使用反射将事件转换为适当的类,但编译器不知道该类是否适用于处理程序。
我推荐Iscoughlin在default
中使用EventBase
方法的解决方案来翻转依赖项,以便为事件提供处理程序而不是给处理程序事件。与我即将建议的解决方法相比,这更适合您的模型。但为了完整起见,这是另一个(公认的不太干净)解决方案:
public interface Handler {
public void handleEvent(EventBase event);
}
巴士:
public class EventBus {
private final Map<Class<? extends EventBase>, List<Handler>> subscribers;
public EventBus() {
subscribers = new HashMap<>();
}
public void register(Class<? extends EventBase> eventClass, Handler handler) {
List<Handler> typeSubs =
subscribers.computeIfAbsent(
eventClass,
(e) -> new ArrayList<Handler>());
typeSubs.add(handler);
}
public <T extends EventBase> void post(T event) {
List<Handler> typeSubs = subscribers.get(event.getClass());
for (Handler handler : typeSubs) {
handler.handleEvent(event);
}
}
}
订阅者看起来像这样(这里的解决方案不太理想):
class SomeSubscriber {
SomeSubscriber(EventBus eventBus) {
eventBus.register(MyEvent.class, this::onMyEvent);
eventBus.register(SomeOtherEvent.class, this::onSomeOtherEvent);
eventBus.register(YetAnotherEvent.class, this::onYetAnotherEvent);
}
private void onMyEvent(EventBase e) {
MyEvent event = (MyEvent)e;
//... do something with the MyEvent ...
}
private void onSomeOtherEvent(EventBase e) {
SomeOtherEvent event = (SomeOtherEvent)e;
//... do something with the SomeOtherEvent ...
}
private void onYetAnotherEvent (EventBase e) {
YetAnotherEvent event = (YetAnotherEvent)e;
//... do something with the YetAnotherEvent ...
}
}
答案 2 :(得分:0)
我通过简单地使用反射成功实现了我的原始目标。对于任何人的兴趣,代码如下。我遇到的关键问题(转换事件对象)很好地由Method.invoke(Object obj, Object ...args)
转换为参数的对象 - 不需要转换。
package experiments.eventbus;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventBus {
private static String handlerMethodName;
private final Map<Class<?>, List<HandlerMethod>> handlerMethods;
static {
Class<Handler> c = Handler.class;
handlerMethodName = c.getMethods()[0].getName();
}
public EventBus() {
handlerMethods = new HashMap<>();
}
public <T> void register(Class<T> eventClass, Handler<T> handler) {
List<HandlerMethod> handlers = handlerMethods.computeIfAbsent(eventClass, (e) -> new ArrayList<HandlerMethod>());
Method method = lookupMethod(handler);
handlers.add(new HandlerMethod(handler, method));
}
public <T> void post(T event) {
List<HandlerMethod> handlers = handlerMethods.get(event.getClass());
if (handlers == null) {
return;
}
for (HandlerMethod handler : handlers) {
handler.invoke(event);
}
}
private <T> Method lookupMethod(Handler<T> handler) {
Method[] methods = handler.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(handlerMethodName)) {
return method;
}
}
// This isn't possible, but need to satisfy the compiler
throw new RuntimeException();
}
/**
* Tuple of a Handler<?> (functional interface provided by subscriber) and a {@link Method} to that function (that
* can be invoked with an "Object" event, i.e. Method#invoke takes an Object.
*/
private static class HandlerMethod {
private final Handler<?> handler;
private final Method method;
HandlerMethod(Handler<?> handler, Method method) {
this.handler = handler;
this.method = method;
}
void invoke(Object event) {
try {
method.invoke(handler, event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
更新后的版本有一些额外的东西,比如从Handler实现中查找处理程序方法,以及用于保存处理程序实例和相应Method
的元组。
如果我有更多时间,我会使用MethodHandle
调查差异 - 也许更快。我的解决方案可能比在EventBus中使用defender方法慢。
注意:我的解决方案显然不完整,没有评论,没有测试,没有unregister
方法等等。