From 7034cdc2d4e104e85055e721c9e8a89759f48a8d Mon Sep 17 00:00:00 2001 From: icanci Date: Fri, 11 Nov 2022 18:07:29 +0800 Subject: [PATCH] rec-spi event --- pom.xml | 1 + rec-admin/rec-admin-biz/pom.xml | 5 + .../rec/admin/biz/config/EventConfig.java | 38 +++++ rec-spi/pom.xml | 26 +++ .../spi/event/AbstractEventDispatcher.java | 82 +++++++++ .../rec/spi/event/AnonymityEventHandler.java | 25 +++ .../cn/icanci/rec/spi/event/BaseEvent.java | 18 ++ .../rec/spi/event/BaseEventListener.java | 47 ++++++ .../rec/spi/event/DefaultEventDispatcher.java | 159 ++++++++++++++++++ .../icanci/rec/spi/event/EventDispatcher.java | 24 +++ .../rec/spi/event/ListenerComparator.java | 14 ++ .../rec/spi/event/NamedThreadFactory.java | 34 ++++ .../java/cn/icanci/rec/spi/package-info.java | 5 + 13 files changed, 478 insertions(+) create mode 100644 rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/config/EventConfig.java create mode 100644 rec-spi/pom.xml create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/AbstractEventDispatcher.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/AnonymityEventHandler.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEvent.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEventListener.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/DefaultEventDispatcher.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/EventDispatcher.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/ListenerComparator.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/event/NamedThreadFactory.java create mode 100644 rec-spi/src/main/java/cn/icanci/rec/spi/package-info.java diff --git a/pom.xml b/pom.xml index 29e5cac..c90dc3c 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ rec-spring-boot-starter rec-socket rec-common + rec-spi diff --git a/rec-admin/rec-admin-biz/pom.xml b/rec-admin/rec-admin-biz/pom.xml index af101c5..aeda1db 100644 --- a/rec-admin/rec-admin-biz/pom.xml +++ b/rec-admin/rec-admin-biz/pom.xml @@ -22,6 +22,11 @@ rec-admin-dal ${revision} + + cn.icanci.rec + rec-spi + ${revision} + org.mapstruct mapstruct diff --git a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/config/EventConfig.java b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/config/EventConfig.java new file mode 100644 index 0000000..67023c0 --- /dev/null +++ b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/config/EventConfig.java @@ -0,0 +1,38 @@ +package cn.icanci.rec.admin.biz.config; + +import cn.icanci.rec.spi.event.DefaultEventDispatcher; +import cn.icanci.rec.spi.event.EventDispatcher; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/11 18:05 + */ +@Configuration +public class EventConfig implements ApplicationContextAware { + /** + * Spring 上下文 + */ + private ApplicationContext context; + + /** + * 事件分发器 + * + * @return 返回事件分发器 + */ + @Bean("eventDispatcher") + public EventDispatcher eventDispatcher() { + return new DefaultEventDispatcher(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } + +} diff --git a/rec-spi/pom.xml b/rec-spi/pom.xml new file mode 100644 index 0000000..511f9dd --- /dev/null +++ b/rec-spi/pom.xml @@ -0,0 +1,26 @@ + + + + rec-parent + cn.icanci.rec + ${revision} + + 4.0.0 + + rec-spi + + + 8 + 8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + \ No newline at end of file diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/AbstractEventDispatcher.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/AbstractEventDispatcher.java new file mode 100644 index 0000000..bc21f85 --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/AbstractEventDispatcher.java @@ -0,0 +1,82 @@ +package cn.icanci.rec.spi.event; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * 事件分发抽象处理器 + * + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public abstract class AbstractEventDispatcher implements EventDispatcher, ApplicationContextAware { + /** + * Spring 容器 + */ + protected ApplicationContext applicationContext; + + /** + * 事件监听器列表 + */ + protected Map, List> eventListMap = new ConcurrentHashMap<>(); + + /** + * 监听器 + */ + protected List listeners = new LinkedList(); + + /** + * 注册事件类型 + */ + protected Set> eventClasses = new HashSet<>(); + + /** + * 排序器 + */ + protected static final ListenerComparator LISTENER_COMPARATOR = new ListenerComparator(); + + /** + * Asynchronous executor + */ + protected Executor taskExecutor; + + /**锁*/ + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + try { + lock.writeLock().lock(); + register(); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 注册事件和监听器 + */ + protected abstract void register(); + + /** + * 获取事件执行线程池 + * + * @return 事件执行线程池 + */ + protected abstract Executor getTaskExecutor(); + + /** + * 设置任务执行器 + * + * @param taskExecutor 事件执行线程池 + */ + public abstract void setTaskExecutor(Executor taskExecutor); +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/AnonymityEventHandler.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/AnonymityEventHandler.java new file mode 100644 index 0000000..6810aa5 --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/AnonymityEventHandler.java @@ -0,0 +1,25 @@ +package cn.icanci.rec.spi.event; + +/** + * 事件执行 + * + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public class AnonymityEventHandler implements Runnable { + /** 事件 */ + private BaseEvent event; + + /** 事件监听器 */ + private BaseEventListener listener; + + public AnonymityEventHandler(BaseEvent event, BaseEventListener listener) { + this.event = event; + this.listener = listener; + } + + @Override + public void run() { + listener.onEvent(event); + } +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEvent.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEvent.java new file mode 100644 index 0000000..2f52fdc --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEvent.java @@ -0,0 +1,18 @@ +package cn.icanci.rec.spi.event; + +import java.util.EventObject; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public class BaseEvent extends EventObject { + + public BaseEvent(Object source) { + super(source); + } + + public BaseEvent() { + super("BaseEvent"); + } +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEventListener.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEventListener.java new file mode 100644 index 0000000..6dd2fa3 --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/BaseEventListener.java @@ -0,0 +1,47 @@ +package cn.icanci.rec.spi.event; + +import java.util.EventListener; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public abstract class BaseEventListener implements EventListener { + + /** + * 接受Event方法 + * + * @param event 事件 + */ + protected abstract void event(T event); + + /** + * 处理 Event + * + * @param event 事件 + */ + public final void onEvent(T event) { + if (isSupport(event)) { + event(event); + } + } + + /** + * 是否支持 + * + * @param event event + * @return 返回是否支持 + */ + protected boolean isSupport(T event) { + return true; + } + + /** + * 监听器执行的顺序 数值越小,优先级越高 + * + * @return 返回顺序 + */ + protected int order() { + return 0; + } +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/DefaultEventDispatcher.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/DefaultEventDispatcher.java new file mode 100644 index 0000000..a4c1386 --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/DefaultEventDispatcher.java @@ -0,0 +1,159 @@ +package cn.icanci.rec.spi.event; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * 事件分发器默认实现 + * + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public class DefaultEventDispatcher extends AbstractEventDispatcher { + + /** + * 注册事件和监听器 + */ + @Override + protected void register() { + String[] names = applicationContext.getBeanDefinitionNames(); + for (String name : names) { + Object bean = applicationContext.getBean(name); + if (bean instanceof BaseEvent) { + eventClasses.add(bean.getClass()); + } + if (bean instanceof BaseEventListener) { + listeners.add((BaseEventListener) bean); + } + } + + postEventListenerMapper(); + } + + /** + * 映射mapper + */ + private void postEventListenerMapper() { + if (listeners.isEmpty() || eventClasses.isEmpty()) { + return; + } + + for (Class eventClass : eventClasses) { + for (BaseEventListener listener : listeners) { + if (!isSupport(eventClass, listener)) { + continue; + } + List baseEventListeners = eventListMap.get(eventClass); + if (baseEventListeners == null || baseEventListeners.isEmpty()) { + baseEventListeners = new ArrayList<>(); + } + baseEventListeners.add(listener); + eventListMap.put(eventClass, baseEventListeners); + } + } + + // 清空 help gc + listeners = null; + eventClasses = null; + + // 排序 + Collection> listCollection = eventListMap.values(); + for (List baseEventListeners : listCollection) { + baseEventListeners.sort(LISTENER_COMPARATOR); + } + } + + /** + * 是否支持加入到listener + * + * @param eventClass eventClass + * @param listener listener + * @return 是否支持加入到listener + */ + private boolean isSupport(Class eventClass, BaseEventListener listener) { + Type type = listener.getClass().getGenericSuperclass(); + ParameterizedType parameterizedType = (ParameterizedType) type; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Type actualTypeArgument = actualTypeArguments[0]; + return eventClass == actualTypeArgument; + } + + /** + * 获取任务执行器 + * + * @return 任务执行器 + */ + @Override + protected Executor getTaskExecutor() { + if (taskExecutor == null) { + taskExecutor = Executors.newCachedThreadPool(new NamedThreadFactory("beedong-event", true)); + } + return taskExecutor; + } + + /** + * 设置任务执行器 + * + * @param taskExecutor taskExecutor + */ + public void setTaskExecutor(Executor taskExecutor) { + this.taskExecutor = taskExecutor; + } + + /** + * 移除监听器 + * + * @param baseEvent baseEvent 事件 + * @param baseEventListener baseEventListener 监听器 + */ + public void remove(BaseEvent baseEvent, BaseEventListener baseEventListener) { + List listeners = eventListMap.get(baseEvent.getClass()); + if (listeners == null || listeners.isEmpty()) { + return; + } + listeners.remove(baseEventListener); + } + + /** + * 分发事件 + * + * @param baseEvent baseEvent + */ + @Override + public void fire(BaseEvent baseEvent) { + fire(baseEvent, true); + } + + /** + * 分发事件 + * + * @param baseEvent baseEvent + * @param sync 是否同步发送 + */ + @Override + public void fire(BaseEvent baseEvent, boolean sync) { + List listeners = eventListMap.get(baseEvent.getClass()); + if (listeners == null || listeners.isEmpty()) { + return; + } + + // 同步发送 + if (sync) { + for (BaseEventListener listener : listeners) { + listener.onEvent(baseEvent); + } + } else { // 异步发送 + Executor taskExecutor = getTaskExecutor(); + for (BaseEventListener listener : listeners) { + taskExecutor.execute(new AnonymityEventHandler(baseEvent, listener)); + } + } + + } + +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/EventDispatcher.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/EventDispatcher.java new file mode 100644 index 0000000..c8921d5 --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/EventDispatcher.java @@ -0,0 +1,24 @@ +package cn.icanci.rec.spi.event; + +/** + * 事件分发器抽象 + * + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public interface EventDispatcher { + /** + * 分发事件 同步发送 + * + * @param event event + */ + void fire(final BaseEvent event); + + /** + * 分发事件 + * + * @param event event + * @param sync 是否同步发送 + */ + void fire(final BaseEvent event, boolean sync); +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/ListenerComparator.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/ListenerComparator.java new file mode 100644 index 0000000..5387156 --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/ListenerComparator.java @@ -0,0 +1,14 @@ +package cn.icanci.rec.spi.event; + +import java.util.Comparator; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public class ListenerComparator implements Comparator { + @Override + public int compare(BaseEventListener o1, BaseEventListener o2) { + return o1.order() - o2.order(); + } +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/event/NamedThreadFactory.java b/rec-spi/src/main/java/cn/icanci/rec/spi/event/NamedThreadFactory.java new file mode 100644 index 0000000..82cb910 --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/event/NamedThreadFactory.java @@ -0,0 +1,34 @@ +package cn.icanci.rec.spi.event; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +public class NamedThreadFactory implements ThreadFactory { + + private final AtomicInteger threadNum = new AtomicInteger(1); + + private final String prefix; + + private final boolean daemon; + + private final ThreadGroup group; + + public NamedThreadFactory(String prefix, boolean daemon) { + this.prefix = prefix + "-thread-"; + this.daemon = daemon; + SecurityManager s = System.getSecurityManager(); + group = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup(); + } + + @Override + public Thread newThread(Runnable runnable) { + String name = prefix + threadNum.getAndIncrement(); + Thread ret = new Thread(group, runnable, name, 0); + ret.setDaemon(daemon); + return ret; + } +} diff --git a/rec-spi/src/main/java/cn/icanci/rec/spi/package-info.java b/rec-spi/src/main/java/cn/icanci/rec/spi/package-info.java new file mode 100644 index 0000000..1acebcd --- /dev/null +++ b/rec-spi/src/main/java/cn/icanci/rec/spi/package-info.java @@ -0,0 +1,5 @@ +/** + * @author icanci + * @since 1.0 Created in 2022/11/11 18:01 + */ +package cn.icanci.rec.spi; \ No newline at end of file -- Gitee