From b51fd4e0edbfac10cd58c688c7f40c885c70858a Mon Sep 17 00:00:00 2001 From: weixiaoqiang <544483342@qq.com> Date: Sat, 23 Mar 2024 10:13:57 +0800 Subject: [PATCH] perf[event]:Optimize event exception handling and unsubscribed event handling --- .../com/zfoo/event/EventExceptionContext.java | 13 +++++ .../com/zfoo/event/EventExceptionHandler.java | 48 +++++++++++++++++++ .../com/zfoo/event/anno/EventService.java | 19 ++++++++ .../com/zfoo/event/enhance/EnhanceUtils.java | 38 +++++++++++---- .../zfoo/event/enhance/IEventReceiver.java | 6 +++ .../java/com/zfoo/event/manager/EventBus.java | 23 +++++++-- .../com/zfoo/event/model/IPlayerEvent.java | 25 ++++++++++ .../java/com/zfoo/event/ApplicationTest.java | 2 + .../java/com/zfoo/event/MyController1.java | 5 +- .../java/com/zfoo/event/MyController2.java | 4 +- .../java/com/zfoo/event/MyController3.java | 4 +- .../java/com/zfoo/event/UnhandledEvent.java | 6 +++ 12 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 event/src/main/java/com/zfoo/event/EventExceptionContext.java create mode 100644 event/src/main/java/com/zfoo/event/EventExceptionHandler.java create mode 100644 event/src/main/java/com/zfoo/event/anno/EventService.java create mode 100644 event/src/main/java/com/zfoo/event/model/IPlayerEvent.java create mode 100644 event/src/test/java/com/zfoo/event/UnhandledEvent.java diff --git a/event/src/main/java/com/zfoo/event/EventExceptionContext.java b/event/src/main/java/com/zfoo/event/EventExceptionContext.java new file mode 100644 index 000000000..ff1fc0a5d --- /dev/null +++ b/event/src/main/java/com/zfoo/event/EventExceptionContext.java @@ -0,0 +1,13 @@ +package com.zfoo.event; + +import java.lang.reflect.Method; + +/** + * 订阅者抛出异常上下文 + * + * @param event 事件对象 + * @param subscriber 订阅者 + * @param subscriberMethod 订阅方法 + */ +public record EventExceptionContext(Object event, Object subscriber, Method subscriberMethod) { +} diff --git a/event/src/main/java/com/zfoo/event/EventExceptionHandler.java b/event/src/main/java/com/zfoo/event/EventExceptionHandler.java new file mode 100644 index 000000000..c0786bf7d --- /dev/null +++ b/event/src/main/java/com/zfoo/event/EventExceptionHandler.java @@ -0,0 +1,48 @@ +package com.zfoo.event; + +import com.zfoo.event.manager.EventBus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; + +/** + * 处理事件订阅者抛出异常 + * + * @author veione + */ +@FunctionalInterface +public interface EventExceptionHandler { + /** + * 处理订阅者抛出的异常 + * + * @param exception 异常对象 + * @param context 异常上下文 + */ + void handleException(Throwable exception, EventExceptionContext context); + + final class LoggingEventExceptionHandler implements EventExceptionHandler { + public static final LoggingEventExceptionHandler INSTANCE = new LoggingEventExceptionHandler(); + private final Logger logger = LoggerFactory.getLogger(EventBus.class.getName()); + + @Override + public void handleException(Throwable exception, EventExceptionContext context) { + if (logger.isErrorEnabled()) { + logger.error(message(context), exception); + } + } + + private static String message(EventExceptionContext context) { + Method method = context.subscriberMethod(); + return "Exception thrown by subscriber method " + + method.getName() + + '(' + + method.getParameterTypes()[0].getName() + + ')' + + " on subscriber " + + context.subscriber() + + " when dispatching event: " + + context.event(); + } + } +} diff --git a/event/src/main/java/com/zfoo/event/anno/EventService.java b/event/src/main/java/com/zfoo/event/anno/EventService.java new file mode 100644 index 000000000..142309d17 --- /dev/null +++ b/event/src/main/java/com/zfoo/event/anno/EventService.java @@ -0,0 +1,19 @@ +package com.zfoo.event.anno; + +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.stereotype.Service; + +import java.lang.annotation.*; + +/** + * 用于标记处理事件接口 + * + * @author veione + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Reflective +@Service +public @interface EventService { +} diff --git a/event/src/main/java/com/zfoo/event/enhance/EnhanceUtils.java b/event/src/main/java/com/zfoo/event/enhance/EnhanceUtils.java index cc014004a..b31c61c28 100644 --- a/event/src/main/java/com/zfoo/event/enhance/EnhanceUtils.java +++ b/event/src/main/java/com/zfoo/event/enhance/EnhanceUtils.java @@ -57,14 +57,23 @@ public static IEventReceiver createEventReceiver(EventReceiverDefinition definit CtClass enhanceClazz = classPool.makeClass(EnhanceUtils.class.getName() + StringUtils.capitalize(NamespaceHandler.EVENT) + UuidUtils.getLocalIntId()); enhanceClazz.addInterface(classPool.get(IEventReceiver.class.getName())); - // 定义类中的一个成员 - CtField field = new CtField(classPool.get(bean.getClass().getName()), "bean", enhanceClazz); - field.setModifiers(Modifier.PRIVATE); + // 定义类中的一个成员bean + CtClass beanClass = classPool.get(bean.getClass().getName()); + CtField field = new CtField(beanClass, "bean", enhanceClazz); + field.setModifiers(Modifier.PRIVATE + Modifier.FINAL); enhanceClazz.addField(field); + // 定义类中的一个成员method + CtClass methodClass = classPool.get(method.getClass().getName()); + CtField methodField = new CtField(methodClass, "method", enhanceClazz); + methodField.setModifiers(Modifier.PRIVATE + Modifier.FINAL); + enhanceClazz.addField(methodField); + // 定义类的构造器 - CtConstructor constructor = new CtConstructor(classPool.get(new String[]{bean.getClass().getName()}), enhanceClazz); - constructor.setBody("{this.bean=$1;}"); + // 创建构造函数参数数组 + CtClass[] parameterTypes = {beanClass, methodClass}; + CtConstructor constructor = new CtConstructor(parameterTypes, enhanceClazz); + constructor.setBody("{this.bean=$1; this.method=$2;}"); constructor.setModifiers(Modifier.PUBLIC); enhanceClazz.addConstructor(constructor); @@ -82,12 +91,25 @@ public static IEventReceiver createEventReceiver(EventReceiverDefinition definit busMethod.setBody(busMethodBody); enhanceClazz.addMethod(busMethod); + // 定义类实现的接口方法getBean + CtMethod beanMethod = new CtMethod(classPool.get(Object.class.getName()), "getBean", null, enhanceClazz); + beanMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + String beanMethodBody = "{ return this.bean; }"; + beanMethod.setBody(beanMethodBody); + enhanceClazz.addMethod(beanMethod); + + // 定义类实现的接口方法getMethod + CtMethod getMethod = new CtMethod(classPool.get(Method.class.getName()), "getMethod", null, enhanceClazz); + getMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + String getMethodBody = "{ return this.method; }"; + getMethod.setBody(getMethodBody); + enhanceClazz.addMethod(getMethod); + // 释放缓存 enhanceClazz.detach(); Class resultClazz = enhanceClazz.toClass(IEventReceiver.class); - Constructor resultConstructor = resultClazz.getConstructor(bean.getClass()); - IEventReceiver receiver = (IEventReceiver) resultConstructor.newInstance(bean); - return receiver; + Constructor resultConstructor = resultClazz.getConstructor(bean.getClass(), method.getClass()); + return (IEventReceiver) resultConstructor.newInstance(bean, method); } } diff --git a/event/src/main/java/com/zfoo/event/enhance/IEventReceiver.java b/event/src/main/java/com/zfoo/event/enhance/IEventReceiver.java index ef7166f31..a53610d01 100644 --- a/event/src/main/java/com/zfoo/event/enhance/IEventReceiver.java +++ b/event/src/main/java/com/zfoo/event/enhance/IEventReceiver.java @@ -15,6 +15,8 @@ import com.zfoo.event.anno.Bus; import com.zfoo.event.model.IEvent; +import java.lang.reflect.Method; + /** * @author godotg */ @@ -22,4 +24,8 @@ public interface IEventReceiver { Bus bus(); void invoke(IEvent event); + + Object getBean(); + + Method getMethod(); } diff --git a/event/src/main/java/com/zfoo/event/manager/EventBus.java b/event/src/main/java/com/zfoo/event/manager/EventBus.java index b6eaaa873..4071219af 100644 --- a/event/src/main/java/com/zfoo/event/manager/EventBus.java +++ b/event/src/main/java/com/zfoo/event/manager/EventBus.java @@ -12,6 +12,8 @@ */ package com.zfoo.event.manager; +import com.zfoo.event.EventExceptionContext; +import com.zfoo.event.EventExceptionHandler; import com.zfoo.event.enhance.IEventReceiver; import com.zfoo.event.model.IEvent; import com.zfoo.protocol.collection.CollectionUtils; @@ -54,6 +56,10 @@ public abstract class EventBus { * event mapping */ private static final Map, List> receiverMap = new HashMap<>(); + /** + * event exception handler + */ + private static EventExceptionHandler exceptionHandler = EventExceptionHandler.LoggingEventExceptionHandler.INSTANCE; static { for (int i = 0; i < executors.length; i++) { @@ -87,8 +93,19 @@ public Thread newThread(Runnable runnable) { } } + /** + * 设置异常处理器 + * + * @param handler 异常处理器 + */ + public static void setExceptionHandler(EventExceptionHandler handler) { + exceptionHandler = handler; + } + /** * Publish the event + * + * @param event Event object */ public static void post(IEvent event) { if (event == null) { @@ -97,6 +114,7 @@ public static void post(IEvent event) { var clazz = event.getClass(); var receivers = receiverMap.get(clazz); if (CollectionUtils.isEmpty(receivers)) { + logger.warn("This event [" + clazz.getName() + "] has not registered any valid event handler"); return; } for (var receiver : receivers) { @@ -111,14 +129,11 @@ public static void post(IEvent event) { private static void doReceiver(IEventReceiver receiver, IEvent event) { try { receiver.invoke(event); - } catch (Exception e) { - logger.error("eventBus {} [{}] unknown exception", receiver.bus(), event.getClass().getSimpleName(), e); } catch (Throwable t) { - logger.error("eventBus {} [{}] unknown error", receiver.bus(), event.getClass().getSimpleName(), t); + exceptionHandler.handleException(t.getCause(), new EventExceptionContext(event, receiver.getBean(), receiver.getMethod())); } } - public static void asyncExecute(Runnable runnable) { execute(RandomUtils.randomInt(), runnable); } diff --git a/event/src/main/java/com/zfoo/event/model/IPlayerEvent.java b/event/src/main/java/com/zfoo/event/model/IPlayerEvent.java new file mode 100644 index 000000000..a13470085 --- /dev/null +++ b/event/src/main/java/com/zfoo/event/model/IPlayerEvent.java @@ -0,0 +1,25 @@ +package com.zfoo.event.model; + +/** + * 玩家事件接口 + *

+ * 主要用于让同一个玩家ID在同一个线程里面进行事件的业务处理,防止玩家事件在不同的线程中处理造成的玩家数据冲突或者线程竞争导致的性能下降. + *

+ * + * @author veione + */ +public interface IPlayerEvent extends IEvent { + + @Override + default int executorHash() { + return (int) executePlayerId(); + } + + /** + * 执行玩家ID + * + * @return 玩家ID + */ + long executePlayerId(); +} + diff --git a/event/src/test/java/com/zfoo/event/ApplicationTest.java b/event/src/test/java/com/zfoo/event/ApplicationTest.java index 1207fa267..2f81f478d 100644 --- a/event/src/test/java/com/zfoo/event/ApplicationTest.java +++ b/event/src/test/java/com/zfoo/event/ApplicationTest.java @@ -32,6 +32,8 @@ public void startEventTest() { // see receiver method of MyController1 and MyController2 EventBus.post(MyNoticeEvent.valueOf("我的事件")); + EventBus.post(new UnhandledEvent()); + ThreadUtils.sleep(1000); } diff --git a/event/src/test/java/com/zfoo/event/MyController1.java b/event/src/test/java/com/zfoo/event/MyController1.java index 03117df67..6b9137bc7 100644 --- a/event/src/test/java/com/zfoo/event/MyController1.java +++ b/event/src/test/java/com/zfoo/event/MyController1.java @@ -14,14 +14,14 @@ package com.zfoo.event; import com.zfoo.event.anno.EventReceiver; +import com.zfoo.event.anno.EventService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; /** * @author godotg */ -@Component +@EventService public class MyController1 { private static final Logger logger = LoggerFactory.getLogger(MyController1.class); @@ -30,6 +30,7 @@ public class MyController1 { @EventReceiver public void onMyNoticeEvent(MyNoticeEvent event) { logger.info("方法1同步执行事件:" + event.getMessage()); + throw new NullPointerException(); } } diff --git a/event/src/test/java/com/zfoo/event/MyController2.java b/event/src/test/java/com/zfoo/event/MyController2.java index 88af62766..0ddf3afab 100644 --- a/event/src/test/java/com/zfoo/event/MyController2.java +++ b/event/src/test/java/com/zfoo/event/MyController2.java @@ -15,14 +15,14 @@ import com.zfoo.event.anno.Bus; import com.zfoo.event.anno.EventReceiver; +import com.zfoo.event.anno.EventService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; /** * @author godotg */ -@Component +@EventService public class MyController2 { private static final Logger logger = LoggerFactory.getLogger(MyController2.class); diff --git a/event/src/test/java/com/zfoo/event/MyController3.java b/event/src/test/java/com/zfoo/event/MyController3.java index 4390075d2..65b458b85 100644 --- a/event/src/test/java/com/zfoo/event/MyController3.java +++ b/event/src/test/java/com/zfoo/event/MyController3.java @@ -15,14 +15,14 @@ import com.zfoo.event.anno.Bus; import com.zfoo.event.anno.EventReceiver; +import com.zfoo.event.anno.EventService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; /** * @author godotg */ -@Component +@EventService public class MyController3 { private static final Logger logger = LoggerFactory.getLogger(MyController3.class); diff --git a/event/src/test/java/com/zfoo/event/UnhandledEvent.java b/event/src/test/java/com/zfoo/event/UnhandledEvent.java new file mode 100644 index 000000000..e8ed5e389 --- /dev/null +++ b/event/src/test/java/com/zfoo/event/UnhandledEvent.java @@ -0,0 +1,6 @@ +package com.zfoo.event; + +import com.zfoo.event.model.IEvent; + +public class UnhandledEvent implements IEvent { +}