diff --git a/pom.xml b/pom.xml index 29e5cacfae026346e79ac7b6e058665615f361e4..c90dc3c319dda5ce651faa60b4c5d5e4010287fc 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 af101c55af5a67de97aca352e5b78f31476e2b2b..aeda1db31e2692de12a0b42465365f1fa3adf227 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 0000000000000000000000000000000000000000..67023c01462b2e98cc4ff44465379bbf20bdd9a3 --- /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 0000000000000000000000000000000000000000..511f9dd725fda80049b6e09c4d04be7eb6e13afd --- /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 0000000000000000000000000000000000000000..bc21f85e5a230f9f7b086be98447bf1c1fa618e8 --- /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 0000000000000000000000000000000000000000..6810aa52f5dfd13f53716642e72d8d6494fc2a32 --- /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 0000000000000000000000000000000000000000..2f52fdcb95539e0559dc0c6ced5e415f91a0f0c8 --- /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 0000000000000000000000000000000000000000..6dd2fa3e227b0204d67f3a2905e6896bc00df3d0 --- /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 0000000000000000000000000000000000000000..a4c138623a642d3f2956a50d859bc6d3aa4c7b6c --- /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 0000000000000000000000000000000000000000..c8921d52d80cd8109693a9733c18bec7d37b8b20 --- /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 0000000000000000000000000000000000000000..538715647eddec7ea174969f5ca0c939a0679937 --- /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 0000000000000000000000000000000000000000..82cb91056adfedbcd64eaf56e1a7db5451f9ccb4 --- /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 0000000000000000000000000000000000000000..1acebcd53cdfd0ac8e609f3d8675739d6fff9bde --- /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