1 Star 7 Fork 3

javacoo/event-spring-boot-starter

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

event-spring-boot-starter

event-spring-boot-starter是一个基于springboot starter机制,结合SPI 接口设计思想实现的事件处理工具组件,旨在提供简单的事件处理编程模型,让基于事件的开发更简单灵活,内部实现基于guava EventBus 实现,扩展方便,集成使用简单。

背景介绍

业务背景

1、我们在日常开发过程中经常遇到一些特殊业务场景(非分布式环境下,分布式环境的不在此次讨论范围),需要异步处理,常见的处理方式是基于事件机制来完成任务,我们可以基于java的事件处理机制自己实现,也可以基于Spring事件机制实现,还可以使用guava EventBus 实现,或者基于MQ中间件来实现等等,可选方案很多,大家可以任意发挥,但同时对于项目来说也带来了一些问题:技术不统一,维护成本提高等,所以需要一个统一的事件处理编程模型。

需求分析

1、提供统一的事件处理编程模型。

2、尽量少改或者不改造已有功能:少侵入或者0侵入式开发。

3、扩展方便,集成简单,开发速率高,使用简单。

设计思路

事件处理只需要2步:发布事件,处理事件。就这么简单:)

组件整体基于springboot starter机制,结合SPI 接口设计思想实现,内部集成默认实现:guava EventBus,EventBus使用相对简单,但是需要我们手动注册监听者对象有点繁琐,鉴于此,我想到了两种方式实现自动注册监听者对象,1:基于ASM动态修改字节码实现,2:基于插入式注解处理器在编译期直接操作抽象语法树实现。最终选用 第2种方式实现

  • 新建 springboot starter工程:event-spring-boot-starter

  • 基于SPI思想设计扩展接口(EventHelper):提供事件发布,监听对象注册

  • 定义事件订阅注解(MySubscribe):基于ASM动态修改字节码实现

    1. 实现ApplicationContextAware 接口,获取ApplicationContext 应用上下文。
    2. 查找所有包含org.springframework.stereotype.Service注解并且方法上有MySubscribe注解的对象,利用AopUtils工具类获取其class对象,以及相关信息,并封装为EventMetaData对象(目标类对象,包含MySubscribe注解的方法集合等),供下一步使用。
    3. 根据获取的EventMetaData集合,依次处理,利用AsmUtil工具类,给目标class对象中目标方法添加Subscribe注解,并得到新的class对象。
    4. 根据EventMetaData中记录的目标对象beanBean,利用GenericBeanDefinition在ApplicationContext 应用上下文中找到该实例对象,重新设置beanClass为新生成的class对象。
    5. 最后利用DefaultListableBeanFactory 重新注册该对象。
  • 定义事件处理器注解(EventHandler):基于插入式注解处理器在编译期直接操作抽象语法树实现

    1. 继承AbstractProcessor 重写process方法。
    2. 在process方法方法中,首先找到方法上有EventHandler注解的方法,如果注解属性threadSafe是true则在该方法上添加AllowConcurrentEvents,再在该方法上添加Subscribe注解,并记录该类。
    3. 判断该类是否已经被Spring容器管理,如果没有则为此类添加org.springframework.stereotype.Component注解。
    4. 然后为此类添加事件监听注册初始化方法。
    5. 利用ApplicationListener监听ContextRefreshedEvent事件,在Spring容器初始化完毕后,注册事件监听对象。
  • 内部实现guava EventBus

  • 扩展接口(EventHelper):

    方法名称 参数 说明
    post (Object eventObject) 发布同步事件
    eventObject:事件对象,可以为任意对象
    postAsync (Object eventObject) 发布异步事件
    eventObject:事件对象,可以为任意对象
    postDelay (Object eventObject, long delayTime) 发布延迟事件
    eventObject:事件对象,可以为任意对象
    delayTime:延迟时间,单位:毫秒
    register (Object listenerObject) 注册监听对象
    listenerObject:监听对象,可以为任意对象
    unRegister (Object listenerObject) 注销监听对象
    listenerObject:监听对象

具体实现见 实施步骤 一节,由于实施步骤较多,故放在后面章节,我们先来看看如何集成,使用及扩展。

集成,使用及扩展

源码

https://gitee.com/javacoo/event-spring-boot-starter

集成
<dependency>
   <groupId>com.javacoo</groupId>
   <artifactId>event-spring-boot-starter</artifactId>
   <version>1.0.0</version>
</dependency>
使用
  • 配置文件接入事件处理器(此步骤可省略,默认可用)

    主要配置说明,详见 EventConfig限流配置

输入图片说明

  • 发布事件:任意对象可作为事件对象发布

        @Override
    	public Optional<ExampleDto> getExampleInfo(String id) {
    		AbstractAssert.isNotBlank(id, ErrorCodeConstants.SERVICE_GET_EXAMPLE_INFO_ID);
            ExampleDto exampleDto = new ExampleDto();
            exampleDto.setData("这是测试数据");
            exampleDto.setId("1");
            //发布延迟事件
            EventHolder.getEventHelper().get().postDelay(exampleDto,1000);
            //发布同步事件
            String str = "我是事件内容";
            EventHolder.getEventHelper().get().post(str);
            //发布异步事件
            Integer integer = 1;
            EventHolder.getEventHelper().get().postAsync(integer);
            return Optional.ofNullable(exampleDto);
    	}
    
  • 处理事件:

        //@MySubscribe
    	public void tesSubscribe(ExampleDto exampleDto){
    	    log.info("事件处理:{}", JSON.toJSONString(exampleDto));
        }
        //@MySubscribe
        public void tesIntegerSubscribe(Integer integer){
            log.info("IntegerSubscribe事件处理:{}", integer);
        }
        //@MySubscribe
        public void tesStringSubscribe(String str){
            log.info("StringSubscribe事件处理:{}", str);
        }
        @EventHandler
        public void testEventHandler(ExampleDto exampleDto){
            log.info("事件处理:{}", JSON.toJSONString(exampleDto));
        }
        @EventHandler
      public void testIntegerEventHandler(Integer integer){
            log.info("IntegerEventHandler事件处理:{}", integer);
      }
        @EventHandler
        public void testStringEventHandler(String str){
            log.info("StringEventHandler事件处理:{}", str);
        }
    

    事件处理器注解@EventHandler效果

输入图片说明

事件订阅注解@MySubscribe效果

输入图片说明

扩展

基于xkernel 提供的SPI机制,扩展非常方便,大致步骤如下:

  1. 实现事件帮助类接口:如 com.xxxx.xxxx.MyEventHelper

  2. 配置事件帮助类接口:

    • 在项目resource目录新建包->META-INF->services

    • 创建com.javacoo.event.client.api.EventHelper文件,文件内容:实现类的全局限定名,如:

      myEventHelper=com.xxxx.xxxx.MyEventHelper
      
    • 修改配置文件,添加如下内容:

    #event实现,默认内部实现
    event.impl = myEventHelper
    

注意:**@MySubscribe,@EventHandler不能共存,推荐使用@EventHandler注解。

至此组件安装,使用及扩展介绍完毕,接下来将进入预备知识环节。


预备知识

1,ASM

  1. 简介

    • ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

    • 在ASM的理解和应用,之前需要我们掌握class字节码JVM基于栈的设计模式,JVM指令

      class字节码

      我们编写的java文件,会通过javac命令编译为class文件,JVM最终会执行该类型文件来运行程序。下图所示为class文件结构(图片来源网络)。

      img

      下面我们通过一个简单的实例来进行说明。下面是我们编写的一个简单的java文件,只是简单的函数调用.

      public class Test {
          private int num1 = 1;
          public static int NUM1 = 100;
          public int func(int a,int b){
              return add(a,b);
          }
          public int add(int a,int b) {
              return a+b+num1;
          }
          public int sub(int a, int b) {
              return a-b-NUM1;
          }
      }
      

      使用javac -g Test.java编译为class文件,然后通过 javap -verbose Test.class 命令查看class文件格式。

      public class com.wuba.asmdemo.Test
        minor version: 0
        major version: 52
        flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:
         #1 = Methodref          #6.#26         // java/lang/Object."<init>":()V
         #2 = Fieldref           #5.#27         // com/wuba/asmdemo/Test.num1:I
         #3 = Methodref          #5.#28         // com/wuba/asmdemo/Test.add:(II)I
         #4 = Fieldref           #5.#29         // com/wuba/asmdemo/Test.NUM1:I
         #5 = Class              #30            // com/wuba/asmdemo/Test
         #6 = Class              #31            // java/lang/Object
         #7 = Utf8               num1
         #8 = Utf8               I
         #9 = Utf8               NUM1
        #10 = Utf8               <init>
        #11 = Utf8               ()V
        #12 = Utf8               Code
        #13 = Utf8               LineNumberTable
        #14 = Utf8               LocalVariableTable
        #15 = Utf8               this
        #16 = Utf8               Lcom/wuba/asmdemo/Test;
        #17 = Utf8               func
        #18 = Utf8               (II)I
        #19 = Utf8               a
        #20 = Utf8               b
        #21 = Utf8               add
        #22 = Utf8               sub
        #23 = Utf8               <clinit>
        #24 = Utf8               SourceFile
        #25 = Utf8               Test.java
        #26 = NameAndType        #10:#11        // "<init>":()V
        #27 = NameAndType        #7:#8          // num1:I
        #28 = NameAndType        #21:#18        // add:(II)I
        #29 = NameAndType        #9:#8          // NUM1:I
        #30 = Utf8               com/wuba/asmdemo/Test
        #31 = Utf8               java/lang/Object
      {
        public static int NUM1;
          descriptor: I
          flags: ACC_PUBLIC, ACC_STATIC
      
        public com.wuba.asmdemo.Test();     //构造函数
          descriptor: ()V
          flags: ACC_PUBLIC
          Code:
            stack=2, locals=1, args_size=1
               0: aload_0
               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
               4: aload_0
               5: iconst_1
               6: putfield      #2                  // Field num1:I
               9: return
            LineNumberTable:
              line 3: 0
              line 5: 4
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0      10     0  this   Lcom/wuba/asmdemo/Test;
      
        public int func(int, int);
          descriptor: (II)I
          flags: ACC_PUBLIC
          Code:
            stack=3, locals=3, args_size=3
               0: aload_0
               1: iload_1
               2: iload_2
               3: invokevirtual #3                  // Method add:(II)I
               6: ireturn
            LineNumberTable:
              line 10: 0
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0       7     0  this   Lcom/wuba/asmdemo/Test;
                  0       7     1     a   I
                  0       7     2     b   I
      
        public int add(int, int);
          descriptor: (II)I
          flags: ACC_PUBLIC
          Code:
            stack=2, locals=3, args_size=3
               0: iload_1
               1: iload_2
               2: iadd
               3: aload_0
               4: getfield      #2                  // Field num1:I
               7: iadd
               8: ireturn
            LineNumberTable:
              line 14: 0
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0       9     0  this   Lcom/wuba/asmdemo/Test;
                  0       9     1     a   I
                  0       9     2     b   I
      
        public int sub(int, int);
          descriptor: (II)I
          flags: ACC_PUBLIC
          Code:
            stack=2, locals=3, args_size=3
               0: iload_1
               1: iload_2
               2: isub
               3: getstatic     #4                  // Field NUM1:I
               6: isub
               7: ireturn
            LineNumberTable:
              line 18: 0
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0       8     0  this   Lcom/wuba/asmdemo/Test;
                  0       8     1     a   I
                  0       8     2     b   I
      
        static {};
          descriptor: ()V
          flags: ACC_STATIC
          Code:
            stack=1, locals=0, args_size=0
               0: bipush        100
               2: putstatic     #4                  // Field NUM1:I
               5: return
            LineNumberTable:
              line 7: 0
      }
      SourceFile: "Test.java"
      

      可以看出在编译为class文件后,字段名称,方法名称,类型名称等均在常量池中存在的。从而做到减小文件的目的。同时方法定义也转变为了jvm指令。下面我们需要对jvm指令加深一下了解。在了解之前需要我们理解JVM基于栈的设计模式

      JVM基于栈的设计模式

      JVM的指令集是基于栈而不是寄存器,基于栈可以具备很好的跨平台性。在线程中执行一个方法时,我们会创建一个栈帧入栈并执行,如果该方法又调用另一个方法时会再次创建新的栈帧然后入栈,方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧,随后虚拟机将会丢弃此栈帧。

      img

      局部变量表

      **局部变量表(Local Variable Table)**是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。虚拟机通过索引定位的方法查找相应的局部变量。举个例子。以上述的代码为例

       public int sub(int a, int b) {
              return a-b-NUM1;
          }
      

      这个方法大家可以猜测一下局部变量有哪些? 答案是3个,不应该只有a,b吗?还有this,对应实例对象方法编译器都会追加一个this参数。如果该方法为静态方法则为2个了。

      public int sub(int, int);
          descriptor: (II)I
          flags: ACC_PUBLIC
          Code:
            stack=2, locals=3, args_size=3
               0: iload_1
               1: iload_2
               2: isub
               3: getstatic     #4                  // Field NUM1:I
               6: isub
               7: ireturn
            LineNumberTable:
              line 18: 0
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0       8     0  this   Lcom/wuba/asmdemo/Test;
                  0       8     1     a   I
                  0       8     2     b   I
      

      所以局部变量表第0个元素为this, 第一个为a,第二个为b

      操作数栈

      通过局部变量表我们有了要操作和待更新的数据,我们如果对局部变量这些数据进行操作呢?通过操作数栈。当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。

      JVM指令
      • load 命令:用于将局部变量表的指定位置的相应类型变量加载到操作数栈顶;
      • store命令:用于将操作数栈顶的相应类型数据保入局部变量表的指定位置;
      • invokevirtual:调用实例方法
      • ireturn: 当前方法返回int

      在举个例子

      a = b + c 的字节码执行过程中操作数栈以及局部变量表的变化如下图所示

      img

      img

      ASM操作

      通过上面的介绍,我们对字节码和JVM指令有了进一步的了解,下面我们看一下ASM是如果编辑class字节码的。

      ASM API

      ASM API基于访问者模式,为我们提供了ClassVisitor,MethodVisitor,FieldVisitor API接口,每当ASM扫描到类字段是会回调visitField方法,扫描到类方法是会回调MethodVisitor,下面我们看一下API接口

      ClassVisitor方法解析

      public abstract class ClassVisitor {
              ......
          public void visit(int version, int access, String name, String signature, String superName, String[] interfaces);
          //访问类字段时回调
          public FieldVisitor visitField(int access, String name, String desc, String signature, Object value);
          //访问类方法是回调
          public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);
          public void visitEnd();
      }
      
      MethodVisitor方法解析
      public abstract class MethodVisitor {
              ......
          public void visitParameter(String name, int access);
          //访问本地变量类型指令 操作码可以是LOAD,STORE,RET中一种;
          public void visitIntInsn(int opcode, int operand);
          //域操作指令,用来加载或者存储对象的Field
          public void visitFieldInsn(int opcode, String owner, String name, String descriptor);
          //访问方法操作指令
          public void visitMethodInsn(int opcode, String owner, String name, String descriptor);
          public void visitEnd();
      }
      
  2. 实战

    利用ASM替换自定义注解MySubscribe,代码如下

    自定义注解MySubscribe

    @Documented
    @Inherited
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MySubscribe {
    }
    

    事件注册基类

    @Slf4j
    public class BaseEventRegister {
        /**
         * 获取所有注解 类
         * <p>说明:</p>
         * <li>在所有注解 org.springframework.stereotype.Service 类中查找方法包含指定注解</li>
         * @author duanyong@jccfc.com
         * @param annClass 类注解class
         * @param mAnnClass 方法注解class
         * @date 2021/10/20 22:02
         * @retuen Set<EventMetaData>
         */
        protected Set<EventMetaData> getEventMetaDatas(Class<? extends Annotation> annClass,Class<? extends Annotation> mAnnClass){
            //查找Service
            Map<String, Object> serviceMap = ApplicationContextProvider.getApplicationContext().getBeansWithAnnotation(annClass);
            log.info("事件注册数量:{},对象:{}",serviceMap.size(),serviceMap);
            Set<EventMetaData> eventMetaDatas = new HashSet<>();
            for (Map.Entry<String, Object> entry : serviceMap.entrySet()) {
                Class entryClass = AopUtils.getTargetClass(entry.getValue());
                //获取注解所在方法 public方法
                List<Method> methods = Arrays.stream(entryClass.getDeclaredMethods())
                        .filter(method -> Modifier.isPublic(method.getModifiers()))//获取本类 public方法
                        .filter(method->method.isAnnotationPresent(mAnnClass))//找到注解所在方法
                        .collect(Collectors.toList());
                if(methods.isEmpty()){
                    continue;
                }
                eventMetaDatas.add(EventMetaData.builder().beanName(entry.getKey()).targetMethods(methods).targetClass(entryClass).build());
            }
            return eventMetaDatas;
        }
    }
    

    ASM实现事件注册

    @Slf4j
    @Configuration
    public class AsmEventRegister extends BaseEventRegister implements ApplicationContextAware {
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            //设置applicationContext
            ApplicationContextProvider.setApplicationContext(applicationContext);
            //查找Service
            Set<EventMetaData> eventMetaDatas = getEventMetaDatas(Service.class, MySubscribe.class);
    
            if(EventHolder.getEventHelper().isPresent() & !eventMetaDatas.isEmpty()){
                eventMetaDatas.forEach(eventMetaData -> {
                    List<String> methodNames = eventMetaData.getTargetMethods().stream().map(method -> method.getName()).collect(
                        Collectors.toList());
                    Class newCalss = AsmUtil.getInstance().addAnntation(methodNames, "Lcom/google/common/eventbus/Subscribe;", eventMetaData.getTargetClass());
                    if(newCalss != null){
                        log.info("事件元数据:{}",eventMetaData);
                        ApplicationContextProvider.registerBean(eventMetaData.getBeanName(),newCalss);
                        Object newObject =ApplicationContextProvider.getBean(eventMetaData.getBeanName());
                        if(newObject != null){
                            log.info("注册监听对象:{}",newObject);
                            EventHolder.getEventHelper().get().register(newObject);
                        }
                    }
                });
            }
        }
    }
    

    ASM工具类

    /**
     * Asm工具类
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     * @since: 2021/10/18 11:23
     */
    @Slf4j
    public class AsmUtil extends ClassLoader{
        public static AsmUtil getInstance() {
            return AsmUtilHolder.instance;
        }
        private static class AsmUtilHolder {
            /**
             * 静态初始化器,由JVM来保证线程安全
             */
            private static AsmUtil
                instance = new AsmUtil();
        }
        /**
         * 添加注解
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/10/18 11:34
         * @param methods:目标方法
         * @param annotation:注解名称
         * @param clazz:当前类
         * @return: java.lang.Class<?> 添加注解后的class
         */
        public Class<?> addAnntation(List<String> methods,String annotation,Class<?> clazz){
            try {
                String className = clazz.getName();
                log.info("添加注解className:{}",className);
                ClassReader cr = new ClassReader(className);
                ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                log.info("添加注解annotation:{}",annotation);
                AsmMethodVisitor mv = new AsmMethodVisitor(Opcodes.ASM4,cw,methods,annotation);
                log.info("添加注解mv:{}",mv);
                cr.accept(mv, 0);
                byte[] code = cw.toByteArray();
                return defineClass(null, code, 0, code.length);
            } catch (Exception e) {
                e.printStackTrace();
                log.info("类:{} 的方法:{},添加注解:{}失败,msg:{}",clazz,methods,annotation,e.getMessage());
            }
            return null;
        }
    
        class AsmMethodVisitor extends ClassVisitor implements Opcodes {
    
            private List<String> methods;
            private String annotation;
    
            public AsmMethodVisitor(int api, ClassVisitor cv,List<String> methods,String annotation) {
                super(api, cv);
                this.methods = methods;
                this.annotation = annotation;
            }
    
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                if(methods.contains(name)){
                    AnnotationVisitor av1 = mv.visitAnnotation(annotation, true);
                    av1.visitEnd();
                }
                return mv;
            }
        }
    }
    

2,AST

  1. 简介
    • 抽象语法树(Abstract Syntax Tree, AST)是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的结构,树的每个节点ASTNode都表示源码中的一个结构。抽象语法树就像是java文件的dom模型,比如dom4j通过标签解析出xml文件。AST把java中的各种元素比如类、属性、方法、代码块、注解、注释等等定义成相应的对象,在编译器编译代码的过程中,语法分析器首先通过AST将源码分析成一个语法树,然后再转换成二进制文件。

    • Java的编译过程可以分成三个阶段:

      javac flow

      1. 所有源文件会被解析成语法树。
      2. 调用注解处理器。如果注解处理器产生了新的源文件,新文件也要进行编译。
      3. 最后,语法树会被分析并转化成类文件。
    • 例如:下面一段java代的抽象语法树大概长这样:

      <img alt="">

    • 上图即为语法树,左边树的节点对应右边相同颜色覆盖的代码块。

      <img alt="">

    • 编辑器对代码处理的流程大概是:JavaTXT->词语法分析-> 生成AST ->语义分析 -> 编译字节码

      AST Workflow

    • 通过操作AST,可以达到修改源代码的功能,相比AOP三剑客,他的时机更为提前:

      <img alt="">

    • AST操作推荐类库:Rewrite JavaParser

  2. 实战

    生成@Getter @Setter

    首先需要注解类,标明作用的范围和作用的时期,两个类分别对应Lombok的Getter、Setter

    @Target({ElementType.TYPE}) //加在类上的注解
    @Retention(RetentionPolicy.SOURCE) //作用于编译期
    public @interface Getter {
    }
    @Target({ElementType.TYPE}) //加在类上的注解
    @Retention(RetentionPolicy.SOURCE) //作用于编译期
    public @interface Setter {
    }
    

    新建抽象注解处理器需要继承AbstractProcessor,这里使用模板方法模式

    public abstract class MyAbstractProcessor extends AbstractProcessor {
        //语法树
        protected JavacTrees trees;
     
        //构建语法树节点
        protected TreeMaker treeMaker;
     
        //创建标识符的对象
        protected Names names;
     
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            this.trees = JavacTrees.instance(processingEnv);
            Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
            this.treeMaker = TreeMaker.instance(context);
            this.names = Names.instance(context);
        }
     
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            //获取注解标识的类
            Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(getAnnotation());
            //拿到语法树
            set.stream().map(element -> trees.getTree(element)).forEach(jcTree -> jcTree.accept(new TreeTranslator() {
                //拿到类定义
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    //拿到所有成员变量
                    for (JCTree tree : jcClassDecl.defs) {
                        if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }
     
                    jcVariableDeclList.forEach(jcVariableDecl -> {
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeMethodDecl(jcVariableDecl));
                    });
                    super.visitClassDef(jcClassDecl);
                }
            }));
            return true;
        }
     
        /**
         * 创建方法
         * @param jcVariableDecl
         * @return
         */
        public abstract JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl);
     
        /**
         * 获取何种注解
         * @return
         */
        public abstract Class<? extends Annotation> getAnnotation();
    }
    

    用于处理Setter注解 继承MyAbstractProcessor

    @SupportedAnnotationTypes("com.javacoo.processor.Setter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes
    @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion
    public class SetterProcessor extends MyAbstractProcessor {
     
        @Override
        public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
            ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
            //生成函数体 this.name = name;
            statements.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),treeMaker.Ident(jcVariableDecl.getName()))));
            JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
            //生成方法
            return treeMaker.MethodDef(
                    treeMaker.Modifiers(Flags.PUBLIC), //访问标志
                    getNewMethodName(jcVariableDecl.getName()), //名字
                    treeMaker.TypeIdent(TypeTag.VOID), //返回类型
                    List.nil(), //泛型形参列表
                    List.of(getParameters(jcVariableDecl)), //参数列表
                    List.nil(), //异常列表
                    body, //方法体
                    null //默认方法(可能是interface中的那个default)
            );
        }
     
        @Override
        public Class<? extends Annotation> getAnnotation() {
            return Setter.class;
        }
     
        private Name getNewMethodName(Name name) {
            String fieldName = name.toString();
            return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
        }
     
        private JCTree.JCVariableDecl getParameters(JCTree.JCVariableDecl prototypeJCVariable) {
            return treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.PARAMETER), //访问标志
                    prototypeJCVariable.name, //名字
                    prototypeJCVariable.vartype, //类型
                    null //初始化语句
            );
        }
    }
    

    用于处理Getter注解 继承MyAbstractProcessor

    @SupportedAnnotationTypes("com.javacoo.processor.Getter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes
    @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion
    public class GetterProcessor extends MyAbstractProcessor {
     
        @Override
        public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
            ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
            //生成函数体 return this.字段名
            statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
            JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
            //生成方法
            return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
        }
     
        @Override
        public Class<? extends Annotation> getAnnotation() {
            return Getter.class;
        }
     
        private Name getNewMethodName(Name name) {
            String fieldName = name.toString();
            return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
        }
    }
    

    编译现有类

    javac -cp $JAVA_HOME/lib/tools.jar *.java -d .
    

    新建一个测试类 IDEA中由于找不到get set方法会报错,可以忽略

    @Getter
    @Setter
    public class UserInfo {
        private String userId;
     
        private String userName;
     
        public static void main(String[] args) {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserId("001");
            userInfo.setUserName("得物");
            System.out.println("id = "+userInfo.getUserId()+" name = "+userInfo.getUserName());
        }
    }
    

    接着编译,多个处理器用逗号分隔

    javac -processor com.ingxx.processor.GetterProcessor,com.ingxx.processor.SetterProcessor UserInfo.java -d .
    

3,编译期注解处理器

  1. 简介

    • Java编译期注解处理器,Annotation Processing Tool,简称APT,是Java提供给开发者的用于在编译期对注解进行处理的一系列API,这类API的使用被广泛的用于各种框架,如dubbo,lombok等。
    • Java的注解处理一般分为2种,最常见也是最显式化的就是Spring以及Spring Boot的注解实现了,在运行期容器启动时,根据注解扫描类,并加载到Spring容器中。而另一种就是本文主要介绍的注解处理,即编译期注解处理器,用于在编译期通过JDK提供的API,对Java文件编译前生成的Java语法树进行处理,实现想要的功能。
  2. 实战 使用基本流程

    • 定义编译期的注解

      @Target({ElementType.METHOD})//加在方法上的注解
      @Retention(RetentionPolicy.SOURCE)//作用于编译期
      public @interface EventHandler {
      }
      
    • 利用JSR269 api(Pluggable Annotation Processing API )创建编译期的注解处理器

      @AutoService(Processor.class)//利用google AutoService自动在META-INF/services/生成接口的类名
      @SupportedSourceVersion(SourceVersion.RELEASE_8)//可以处理什么版本 也可以重写getSupportedSourceVersion
      @SupportedAnnotationTypes("com.javacoo.event.client.annotation.EventHandler")//注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes
      public class EventHandlerProcessor extends AbstractProcessor {
          .....
      }
      
    • 利用tools.jar的javac api处理AST(抽象语法树)

      处理过程

      输入图片说明

      处理前

      输入图片说明

      处理后

    输入图片说明

    • 将功能注册进jar包

4,SPI

  • SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
  • https://www.jianshu.com/p/cc1f08c8aed6

实施步骤

1,新建limit-spring-boot-starter工程
  • 工程结构

输入图片说明

  • 类结构图

输入图片说明

  • 项目结构

    event-spring-boot-starter
     └── src
        ├── main  
        │	├── java  
        │	│   └── com.javacoo
        │	│	├────── event
        │	│	│         ├──────client
        │   │   │         │         ├── api
        │   │   │         │         │    └── EventHelper 事件帮助类接口
        │   │   │         │         ├── annotation
        │   │   │         │         │    └── EventHandler 事件处理器注解
        │   │   │         │         │    └── MySubscribe 事件订阅注解
        │   │   │         │         ├── config
        │   │   │         │         │    └── EventConfig 事件参数配置
        │   │   │         │         ├── context
        │   │   │         │         │    └── EventContext 事件上下文
        │   │   │         │         ├── exception
        │   │   │         │         │    └── LimitException 限流异常
        │   │   │         │         ├── util
        │   │   │         │         │    └── AsmUtil Asm工具类
        │   │   │         │         ├── support
        │   │   │         │         │    ├── ApplicationContextProvider 抽象限流处理器
        │   │   │         │         │    ├── BaseEventRegister 限流注解处理器
        │   │   │         │         │    ├── EventMetaData 限流配置处理器
        │   │   │         │         └──  ├── asm
        │   │   │         │              │    └── AsmEventRegister ASM实现事件注册
        │   │   │         │              └── ast
        │   │   │         │                   ├── EventHandlerProcessor EventHandler注解处理器
        │   │   │         │                   └── AstEventRegister AST实现事件注册
        │   │   │         │         └── internal 接口内部实现
        │   │   │         │              └── guava
        │   │   │         │                   └── EventBusEventHelper EventHelper接口实现类
        │	│	│         └──────starter
        │   │   │                   ├── EventAutoConfiguration 自动配置类
        │   │   │                   └── EventHolder 事件处理器对象持有者
        │	└── resource  
        │	    ├── META-INF
        │             ├── spring.factories
        │             └── ext
        │	               └── internal
        │	                       └── com.javacoo.event.client.api.EventHelper
        └── test  测试
    
2,基于SPI思想设计扩展接口
  • 事件帮助类接口->com.javacoo.event.client.api.EventHelper

    /**
     * 事件帮助类接口
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     * @since: 2021/10/15 11:16
     */
    @Spi(EventConfig.DEFAULT_IMPL)
    public interface EventHelper {
        /**
         * 发布同步事件
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/10/15 11:18
         * @param eventObject: 事件对象
         * @return: void
         */
        void post(Object eventObject);
        /**
         * 发布异步事件
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/10/15 11:19
         * @param eventObject:事件对象
         * @return: void
         */
        void postAsync(Object eventObject);
        /**
         * 发布延迟时间
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/10/15 11:20
         * @param eventObject: 事件对象
         * @param time: 延迟时间,单位:毫秒
         * @return: void
         */
        void postDelay(Object eventObject, long time);
    
        /**
         * 注册监听对象
         * <p>说明:</p>
         * <li></li>
         * @param listenerObject: 监听对象
         * @author duanyong@jccfc.com
         * @date 2021/10/15 18:01
         */
        void register(Object listenerObject);
        /**
         * 注销监听对象
         * <p>说明:</p>
         * <li></li>
         * @param listenerObject: 监听对象
         * @author duanyong@jccfc.com
         * @date 2021/10/15 18:02
         */
        void unRegister(Object listenerObject);
    }
    
3,注解
  • 事件处理器注解:com.javacoo.event.client.annotation.EventHandler

    /**
     * 事件处理器注解
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     * @since: 2021/10/19 10:40
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface EventHandler {
        /**
         * 当前监听方法是否线程安全
         * <li>如果是线程安全的,则不会同步订阅者对象,效率更高</li>
         * @author duanyong@jccfc.com
         * @date 2021/11/1 10:35
         * @return: boolean 默认false
         */
        boolean threadSafe() default false;
    }
    
  • 事件订阅注解:com.javacoo.event.client.annotation.MySubscribe

    /**
     * 事件订阅注解
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/15 22:39
     */
    @Documented
    @Inherited
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MySubscribe {
    }
    
4,配置
  • 事件参数配置:com.javacoo.event.client.config.EventConfig

    /**
     * 事件参数配置
     * <p>说明:</p>
     * @author duanyong
     * @date 2021/3/4 15:15
     */
    @ConfigurationProperties(prefix = EventConfig.PREFIX)
    public class EventConfig {
        /** 前缀 */
        public static final String PREFIX = "event";
        /** lock是否可用,默认值*/
        public static final String ENABLED = "enabled";
        /** 默认实现:,默认值*/
        public static final String DEFAULT_IMPL= "default";
        /** event是否可用*/
        private String enabled = ENABLED;
        /**实现*/
        private String impl = DEFAULT_IMPL;
    
        public String getEnabled() {
            return enabled;
        }
    
        public void setEnabled(String enabled) {
            this.enabled = enabled;
        }
    
        public String getImpl() {
            return impl;
        }
    
        public void setImpl(String impl) {
            this.impl = impl;
        }
    }
    
5,上下文
  • 事件上下文:com.javacoo.event.client.context.EventContext

    /**
     * 事件上下文
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     * @since: 2021/10/21 8:58
     */
    public class EventContext {
        /**
         * 事件监听对象对象集合
         */
        private Set<Object> listeningObjects = new HashSet<>();
    
        public Set<Object> getListeningObjects() {
            return listeningObjects;
        }
    
        public void addListeningObject(Object listeningObject){
            listeningObjects.add(listeningObject);
        }
    
    }
    
6,内部实现
  • EventHelper接口实现:

    /**
     * EventHelper接口实现类
     * <p>说明:</p>
     * <li>基于google eventbus</li>
     * @author duanyong@jccfc.com
     * @date 2021/10/15 17:38
     */
    @Slf4j
    public class EventBusEventHelper implements EventHelper {
        /**
         * EventBus
         */
        private static EventBus eventBus = null;
        /**
         * AsyncEventBus
         */
        private static AsyncEventBus asyncEventBus = null;
        /**
         * DelayQueue
         */
        private static DelayQueue<EventItem> delayQueue = null;
        /**
         * 延迟时间执行器
         */
        private static TaskExecutor delayTaskExecutor = null;
    
        public EventBusEventHelper() {
            delayTaskExecutor = new TaskExecutor() {
                ExecutorService executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(),
                        0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<>(), Executors.defaultThreadFactory());
                @Override
                public void execute(Runnable task) {
                    executorService.execute(task);
                }
            };
            eventBus = new EventBus();
            asyncEventBus = new AsyncEventBus(delayTaskExecutor);
            delayQueue = new DelayQueue<>();
            //注册DeadEvent监听
            eventBus.register(new Object() {
                @Subscribe
                public void lister(DeadEvent event) {
                    log.error("{}发布的{}事件未找到监听对象", event.getSource().getClass(), event.getEvent());
                }
            });
            asyncEventBus.register(new Object() {
                @Subscribe
                public void lister(DeadEvent event) {
                    log.error("{}发布的{}事件未找到监听对象", event.getSource().getClass(), event.getEvent());
                }
            });
        }
        /**
         * 发布同步事件
         * <li></li>
         *
         * @param eventObject : 事件对象
         * @author duanyong@jccfc.com
         * @date 2021/10/15 11:18
         * @return: void
         */
        @Override
        public void post(Object eventObject) {
            log.info("发布同步事件:{}",eventObject);
            eventBus.post(eventObject);
        }
    
        /**
         * 发布异步事件
         * <li></li>
         *
         * @param eventObject :事件对象
         * @author duanyong@jccfc.com
         * @date 2021/10/15 11:19
         * @return: void
         */
        @Override
        public void postAsync(Object eventObject) {
            log.info("发布异步事件:{}",eventObject);
            asyncEventBus.post(eventObject);
        }
    
        /**
         * 发布延迟时间
         * <li></li>
         *
         * @param eventObject : 事件对象
         * @param time        : 延迟时间,单位:毫秒
         * @author duanyong@jccfc.com
         * @date 2021/10/15 11:20
         * @return: void
         */
        @Override
        public void postDelay(Object eventObject, long time) {
            log.info("延迟执行事件->入延迟队列:{}",eventObject);
            //入延迟队列
            delayQueue.put(new EventBusEventHelper.EventItem(eventObject, time));
            //执行
            delayTaskExecutor.execute(()->execute());
        }
    
        /**
         * 注册监听对象
         * <p>说明:</p>
         * <li></li>
         *
         * @param listenerObject : 监听对象
         * @author duanyong@jccfc.com
         * @date 2021/10/15 18:01
         */
        @Override
        public void register(Object listenerObject) {
            log.info("注册监听对象:{}",listenerObject);
            asyncEventBus.register(listenerObject);
            eventBus.register(listenerObject);
        }
    
        /**
         * 注销监听对象
         * <p>说明:</p>
         * <li></li>
         *
         * @param listenerObject : 事件元数据
         * @author duanyong@jccfc.com
         * @date 2021/10/15 18:02
         */
        @Override
        public void unRegister(Object listenerObject) {
            log.info("注销监听对象:{}",listenerObject.toString());
            asyncEventBus.unregister(listenerObject);
            eventBus.unregister(listenerObject);
        }
    
        /**
         * 异步执行
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/7/8 10:08
         * @return: void
         */
        private void execute(){
            try {
                // 使用DelayQueue的take方法获取当前队列里的元素(take方法是阻塞方法,如果队列里有值则取出,否则一直阻塞)
                asyncEventBus.post(delayQueue.take().getEventObject());
                log.info("延迟执行事件");
            }catch (InterruptedException interruptedException){
                log.info("延迟执行事件异常:",interruptedException);
            }
        }
    
        /**
         * 事件项
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/7/8 9:06
         */
        class EventItem<T> implements Delayed {
            /** 触发时间:单位 毫秒 */
            private long time;
            /** 事件对象 */
            private T eventObject;
    
            public EventItem(T eventObject, long time) {
                super();
                // 将传入的时间转换为超时的时刻
                this.time = TimeUnit.NANOSECONDS.convert(time, TimeUnit.MILLISECONDS)
                        + System.nanoTime();
                this.eventObject = eventObject;
            }
    
            public long getTime() {
                return time;
            }
    
            public T getEventObject() {
                return eventObject;
            }
    
            @Override
            public long getDelay(TimeUnit unit) {
                // 剩余时间= 到期时间-当前系统时间,系统一般是纳秒级的,所以这里做一次转换
                return unit.convert(time- System.nanoTime(), TimeUnit.NANOSECONDS);
            }
    
            @Override
            public int compareTo(Delayed o) {
                // 剩余时间-当前传入的时间= 实际剩余时间(单位纳秒)
                long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
                // 根据剩余时间判断等于0 返回1 不等于0
                // 有可能大于0 有可能小于0  大于0返回1  小于返回-1
                return (d == 0) ? 0 : ((d > 0) ? 1 : -1);
            }
    
            @Override
            public String toString() {
                return "EventItem{" +
                        "time=" + time +
                        ", eventObject='" + eventObject + '\'' +
                        '}';
            }
        }
    }
    
7,支持类
  • ApplicationContextProvider

    /**
     * 获取applicationContext
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/16 9:28
     */
    @Slf4j
    public class ApplicationContextProvider {
        private static ApplicationContext ctx = null;  
        public static void setApplicationContext(ApplicationContext applicationContext)
                throws BeansException {
            log.info("获取applicationContext:{}",applicationContext);
            ctx = applicationContext;
        }
    
        public static ApplicationContext getApplicationContext() {
            return ctx;
        }
    
        public static <T> T getBean(Class<T> type) {
            try {
                return Optional.ofNullable(ctx).orElseThrow(() -> new IllegalStateException("non in spring application context.")).getBean(type);
            } catch (Exception e) {
                return null;
            }
        }
        public static Object getBean(String beanName) {
            try {
                return Optional.ofNullable(ctx).orElseThrow(() -> new IllegalStateException("non in spring application context.")).getBean(beanName);
            } catch (Exception e) {
                return null;
            }
        }
        public static void registerBean(String beanName,Class beanClass){
            ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) ctx;
            GenericBeanDefinition beanDefinition = (GenericBeanDefinition)configurableApplicationContext.getBeanFactory().getBeanDefinition(beanName);
            log.info("注册bean,getBeanClassName:{}",beanDefinition.getBeanClassName());
            beanDefinition.setBeanClass(beanClass);
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
            beanFactory.registerBeanDefinition(beanName,beanDefinition);
            log.info("注册bean:{}",beanName);
        }
        public  static void removeBean(String beanName){
            ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) ctx;
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
            beanFactory.removeBeanDefinition(beanName);
            log.info("删除bean:{}",beanName);
        }
    
    }
    
    
  • 事件注册基类

    /**
     * 事件注册基类
     * <p>说明:</p>
     * <li></li>
     *
     * @author duanyong@jccfc.com
     * @date 2021/10/20 22:02
     */
    @Slf4j
    public class BaseEventRegister {
        /**
         * 获取所有注解 类
         * <p>说明:</p>
         * <li>在所有注解 org.springframework.stereotype.Service 类中查找方法包含指定注解</li>
         * @author duanyong@jccfc.com
         * @param annClass 类注解class
         * @param mAnnClass 方法注解class
         * @date 2021/10/20 22:02
         * @retuen Set<EventMetaData>
         */
        protected Set<EventMetaData> getEventMetaDatas(Class<? extends Annotation> annClass,Class<? extends Annotation> mAnnClass){
            //查找Service
            Map<String, Object> serviceMap = ApplicationContextProvider.getApplicationContext().getBeansWithAnnotation(annClass);
            log.info("事件注册数量:{},对象:{}",serviceMap.size(),serviceMap);
            Set<EventMetaData> eventMetaDatas = new HashSet<>();
            for (Map.Entry<String, Object> entry : serviceMap.entrySet()) {
                Class entryClass = AopUtils.getTargetClass(entry.getValue());
                //获取注解所在方法 public方法
                List<Method> methods = Arrays.stream(entryClass.getDeclaredMethods())
                        .filter(method -> Modifier.isPublic(method.getModifiers()))//获取本类 public方法
                        .filter(method->method.isAnnotationPresent(mAnnClass))//找到注解所在方法
                        .collect(Collectors.toList());
                if(methods.isEmpty()){
                    continue;
                }
                eventMetaDatas.add(EventMetaData.builder().beanName(entry.getKey()).targetMethods(methods).targetClass(entryClass).build());
            }
            return eventMetaDatas;
        }
    }
    
    
  • 事件元数据

    /**
     * 事件元数据
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     * @since: 2021/10/18 11:42
     */
    @Data
    @Builder
    public class EventMetaData {
        /**
         * 目标监听对象名称
         */
        private String beanName;
        /**
         * 目标监听对象class
         */
        private Class targetClass;
        /**
         * 目标方法
         */
        private List<Method> targetMethods;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            EventMetaData that = (EventMetaData) o;
            return Objects.equals(beanName, that.beanName);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(beanName);
        }
    }
    
  • ASM实现事件注册

    /**
     * ASM实现事件注册
     * <p>说明:Asm实现</p>
     * <li></li>
     *
     * @author duanyong@jccfc.com
     * @date 2021/10/16 7:50
     */
    @Slf4j
    @Configuration
    public class AsmEventRegister extends BaseEventRegister implements ApplicationContextAware {
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            //设置applicationContext
            ApplicationContextProvider.setApplicationContext(applicationContext);
            //查找Service
            Set<EventMetaData> eventMetaDatas = getEventMetaDatas(Service.class, MySubscribe.class);
    
            if(EventHolder.getEventHelper().isPresent() & !eventMetaDatas.isEmpty()){
                eventMetaDatas.forEach(eventMetaData -> {
                    List<String> methodNames = eventMetaData.getTargetMethods().stream().map(method -> method.getName()).collect(
                        Collectors.toList());
                    Class newCalss = AsmUtil.getInstance().addAnntation(methodNames, "Lcom/google/common/eventbus/Subscribe;", eventMetaData.getTargetClass());
                    if(newCalss != null){
                        log.info("事件元数据:{}",eventMetaData);
                        ApplicationContextProvider.registerBean(eventMetaData.getBeanName(),newCalss);
                        Object newObject =ApplicationContextProvider.getBean(eventMetaData.getBeanName());
                        if(newObject != null){
                            log.info("注册监听对象:{}",newObject);
                            EventHolder.getEventHelper().get().register(newObject);
                        }
                    }
                });
            }
        }
    }
    
  • AST实现事件注册

    /**
     * AST实现事件注册
     * <p>说明:AST</p>
     * <li></li>
     *
     * @author duanyong@jccfc.com
     * @date 2021/10/16 7:50
     */
    @Slf4j
    @Component
    public class AstEventRegister extends BaseEventRegister implements ApplicationListener<ContextRefreshedEvent> {
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            if (event.getApplicationContext().getParent() == null) {
                //设置applicationContext
                ApplicationContextProvider.setApplicationContext(event.getApplicationContext());
                EventHolder.getEventContext().getListeningObjects().forEach(o -> EventHolder.getEventHelper().get().register(o));
            }
        }
    }
    
    
  • EventHandler注解处理器

  /**
 * EventHandler注解处理器
 * <li></li>
 *
 * @author: duanyong@jccfc.com
 * @since: 2021/10/19 10:42
 */
@Slf4j
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.javacoo.event.client.annotation.EventHandler")
public class EventHandlerProcessor extends AbstractProcessor {
    /**
     * spring注解包
     */
    private static final String SPRING_ANNONATION_PACKAGE = "org.springframework.stereotype";
    /**
     * 打印log
     */
    private Messager messager;
    /**
     * 抽象语法树
     */
    private JavacTrees trees;
    /**
     * 封装了创建AST节点的一些方法
     */
    private TreeMaker treeMaker;
    /**
     * 提供了创建标识符的一些方法
     */
    private Names names;
    private JavacElements elementUtils;
    // 初始化方法
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
        this.elementUtils = (JavacElements) processingEnv.getElementUtils();
    }

    // 真正处理注解的方法
    @Override
    public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        printLog("获取所有注解:{}",annotations.toString());
        //包含注解的类集合
        Set<Element> classElements = new HashSet<>();
        // 获取所有包含EventHandler注解的Element
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(EventHandler.class);
        //处理所有包含EventHandler注解的Element,添加注解并导入包
        set.forEach(element -> {
            JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) elementUtils.getTree(element);
            //获取注解
            EventHandler eventHandler = element.getAnnotation(EventHandler.class);
            //如果是线程安全的 则添加AllowConcurrentEvents注解
            if(eventHandler != null && eventHandler.threadSafe()){
                //添加AllowConcurrentEvents注解
                addMethodAnnotation(jcMethodDecl,"com.google.common.eventbus.AllowConcurrentEvents");
                //导入包
                addConsumerImport(element,"com.google.common.eventbus","AllowConcurrentEvents");
            }
            //添加Subscribe注解
            addMethodAnnotation(jcMethodDecl,"com.google.common.eventbus.Subscribe");
            //导入包
            addConsumerImport(element,"com.google.common.eventbus", "Subscribe");
            //记录注解所在类
            classElements.add(element.getEnclosingElement());
            printLog("包含注解所在类:{}",element.getEnclosingElement().getSimpleName());
        });
        //处理注解所在类添加初始化方法
        classElements.forEach(element -> {
            addClassAnnotation(element,"org.springframework.stereotype.Component");
            //导入包
            addConsumerImport(element,"com.javacoo.event.client.starter","EventHolder");
            //构建方法
            JCTree.JCMethodDecl jcMethodDecl = makeEventListeningRegisterMethodDecl();
            //添加注解
            addMethodAnnotation(jcMethodDecl,"javax.annotation.PostConstruct");
            //添加类方法
            addClassMethod(element,jcMethodDecl);
        });
        return true;
    }
    /**
     * 添加类方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:36
     * @param element: 类element对象
     * @param jcMethodDecl: 方法
     * @return: void
     */
    private void addClassMethod(Element element,JCTree.JCMethodDecl jcMethodDecl) {
        printLog("添加类方法:{}",jcMethodDecl.name);
        JCTree jcTree = trees.getTree(element);
        //获取源注解的参数
        jcTree.accept(new TreeTranslator(){
            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                jcClassDecl.defs=jcClassDecl.defs.append(jcMethodDecl);
                super.visitClassDef(jcClassDecl);
            }
        });
    }
    /**
     * 添加方法注解
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:38
     * @param jcMethodDecl: 方法
     * @param annotaionName: 注解名称
     * @return: void
     */
    private void addMethodAnnotation(JCTree.JCMethodDecl jcMethodDecl,String annotaionName){
        printLog("添加方法注解:{}",annotaionName);
        JCTree.JCAnnotation jcAnnotation = makeAnnotation(annotaionName, List.nil());
        jcMethodDecl.mods.annotations = jcMethodDecl.mods.annotations.append(jcAnnotation);
    }
    /**
     * 添加类注解
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/27 22:05
     */
    private void addClassAnnotation(Element element,String annotaionName) {
        JCTree jcTree = trees.getTree(element);
        //获取源注解的参数
        jcTree.accept(new TreeTranslator(){
            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                //是否包含spring注解
                boolean incAnno = jcClassDecl.mods.annotations.stream().anyMatch(annotation ->annotation.type.toString().contains(SPRING_ANNONATION_PACKAGE));
                //boolean incAnno = jcClassDecl.mods.annotations.stream().anyMatch(annotation ->annotation.type.toString().equals(jcAnnotation.annotationType.toString()));
                printLog("是否包含待添加注解:{}",incAnno);
                if(incAnno){
                   return;
                }
                JCTree.JCAnnotation jcAnnotation = makeAnnotation(annotaionName, List.nil());
                printLog("类添加注解:{}",jcAnnotation.toString());
                jcClassDecl.mods.annotations = jcClassDecl.mods.annotations.append(jcAnnotation);
                jcClassDecl.mods.annotations.forEach(e -> {
                    printLog("当前类包含注解:{}",e.toString());
                });
                super.visitClassDef(jcClassDecl);
            }
        });
    }

    /**
     * 日志打印
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:38
     * @param ss: 信息
     * @param args: 参数
     * @return: void
     */
    private void printLog(String ss,Object... args){
        if(args.length>0) {
            ss = ss.replace("{}", "%s");
            String logs = String.format(ss, args);
            messager.printMessage(Diagnostic.Kind.NOTE, logs);
        }else{
            messager.printMessage(Diagnostic.Kind.NOTE, ss);
        }
    }
    /**
     * 构建事件监听对象注册方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 9:19
     * @return: com.sun.tools.javac.tree.JCTree.JCMethodDecl
     */
    private JCTree.JCMethodDecl makeEventListeningRegisterMethodDecl() {
        printLog("构建事件监听对象注册方法");
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        statements.append(treeMaker.Exec(
                    treeMaker.Apply(
                        List.<JCTree.JCExpression>nil(),
                        treeMaker.Select(
                            treeMaker.Ident(
                                elementUtils.getName("EventHolder")
                            ),
                            elementUtils.getName("register")
                        ),
                        List.<JCTree.JCExpression>of(
                            treeMaker.Ident(names.fromString("this"))
                        )
                    )
                ));
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), names.fromString("eventListeningRegister"), treeMaker.Type(new Type.JCVoidType()), List.nil(), List.nil(), List.nil(), body, null);
    }
    /**
     * 构建注解对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:39
     * @param annotaionName:注解名称
     * @param args: 注解参数
     * @return: com.sun.tools.javac.tree.JCTree.JCAnnotation
     */
    private JCTree.JCAnnotation makeAnnotation(String annotaionName, List<JCTree.JCExpression> args) {
        JCTree.JCExpression expression = chainDots(annotaionName.split("\\."));
        JCTree.JCAnnotation jcAnnotation=treeMaker.Annotation(expression, args);
        return jcAnnotation;
    }
    /**
     * 构建依赖包导入
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:40
     * @param packageName:
     * @param className:
     * @return: com.sun.tools.javac.tree.JCTree.JCImport
     */
    private JCTree.JCImport buildImport(String packageName, String className) {
        JCTree.JCIdent ident = treeMaker.Ident(names.fromString(packageName));
        JCTree.JCImport jcImport = treeMaker.Import(treeMaker.Select(ident, names.fromString(className)), false);
        printLog("构建依赖包导入:{}",jcImport.toString());
        return jcImport;
    }
    /**
     * 导入依赖包
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:40
     * @param element: 类element对象
     * @return: void
     */
    private void addConsumerImport(Element element,String packageName, String className) {
        TreePath treePath = trees.getPath(element);
        JCTree.JCCompilationUnit jccu = (JCTree.JCCompilationUnit) treePath.getCompilationUnit();
        java.util.List<JCTree> trees = new ArrayList<>();
        trees.addAll(jccu.defs);
        JCTree.JCImport custImport = buildImport(packageName, className);
        if (!trees.contains(custImport)) {
            trees.add(0,custImport);
        }
        jccu.defs=List.from(trees);
    }
    private JCTree.JCExpression chainDots(String... elems) {
        assert elems != null;
        JCTree.JCExpression e = null;
        for (int i = 0 ; i < elems.length ; i++) {
            e = e == null ? treeMaker.Ident(names.fromString(elems[i])) : treeMaker.Select(e, names.fromString(elems[i]));
        }
        assert e != null;
        return e;
    }
}
8,工具类
  • Asm工具类

    /**
     * Asm工具类
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     * @since: 2021/10/18 11:23
     */
    @Slf4j
    public class AsmUtil extends ClassLoader{
        public static AsmUtil getInstance() {
            return AsmUtilHolder.instance;
        }
        private static class AsmUtilHolder {
            /**
             * 静态初始化器,由JVM来保证线程安全
             */
            private static AsmUtil
                instance = new AsmUtil();
        }
        /**
         * 添加注解
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/10/18 11:34
         * @param methods:目标方法
         * @param annotation:注解名称
         * @param clazz:当前类
         * @return: java.lang.Class<?> 添加注解后的class
         */
        public Class<?> addAnntation(List<String> methods,String annotation,Class<?> clazz){
            try {
                String className = clazz.getName();
                //Class<?> clazz = this.findLoadedClass(className);
                log.info("添加注解className:{}",className);
                ClassReader cr = new ClassReader(className);
                ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                log.info("添加注解annotation:{}",annotation);
                AsmMethodVisitor mv = new AsmMethodVisitor(Opcodes.ASM4,cw,methods,annotation);
                log.info("添加注解mv:{}",mv);
                cr.accept(mv, 0);
                byte[] code = cw.toByteArray();
                return defineClass(null, code, 0, code.length);
            } catch (Exception e) {
                e.printStackTrace();
                log.info("类:{} 的方法:{},添加注解:{}失败,msg:{}",clazz,methods,annotation,e.getMessage());
            }
            return null;
        }
    
        class AsmMethodVisitor extends ClassVisitor implements Opcodes {
    
            private List<String> methods;
            private String annotation;
    
            public AsmMethodVisitor(int api, ClassVisitor cv,List<String> methods,String annotation) {
                super(api, cv);
                this.methods = methods;
                this.annotation = annotation;
            }
    
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                if(methods.contains(name)){
                    AnnotationVisitor av1 = mv.visitAnnotation(annotation, true);
                    av1.visitEnd();
                }
                return mv;
            }
        }
    }
    
    
9,自动配置
  • 自动配置类

    /**
     * 自动配置类
     * <li></li>
     * @author duanyong
     * @date 2021/10/15 22:50
     */
    @Slf4j
    @Configuration                                                                                                                              
    @EnableConfigurationProperties(value = EventConfig.class)
    @ConditionalOnClass(EventConfig.class)
    @ConditionalOnProperty(prefix = EventConfig.PREFIX, value = EventConfig.ENABLED, matchIfMissing = true)
    public class EventAutoConfiguration {
    	@Autowired
        private EventConfig eventConfig;
    	@Bean
    	@ConditionalOnMissingBean(EventHelper.class)
    	public EventHelper createEventHelper() {
            log.info("初始化事件处理器,实现类名称:{}",eventConfig.getImpl());
            EventHolder.eventHelper = ExtensionLoader.getExtensionLoader(EventHelper.class).getExtension(eventConfig.getImpl());
            log.info("初始化事件处理器成功,实现类:{}",EventHolder.eventHelper);
           return EventHolder.eventHelper;
    	}
    }
    
    
  • 事件对象持有者

    /**
     * 事件对象持有者
     * <li></li>
     *
     * @author: duanyong
     * @since: 2021/10/15 22:56
     */
    @Slf4j
    public class EventHolder {
        /** 事件上下文*/
        static EventContext eventContext = new EventContext();
        /** 事件帮助类接口对象*/
        static EventHelper eventHelper;
        /**
         * 获取EventHelper
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/10/21 9:03
         * @return: java.util.Optional<com.javacoo.event.client.api.EventHelper>
         */
        public static Optional<EventHelper> getEventHelper() {
            return Optional.ofNullable(eventHelper);
        }
        /**
         * 注册事件监听对象
         * <li>这里只是将监听对象存入上下文,待组件初始化完成才注册</li>
         * @author duanyong@jccfc.com
         * @date 2021/10/21 9:05
         * @param listeningObject:
         * @return: void
         */
        public static void register(Object listeningObject){
            log.info("注册事件监听对象:{}",listeningObject);
            eventContext.addListeningObject(listeningObject);
        }
        /**
         * 获取EventContext
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/10/21 11:31
         * @return: com.javacoo.event.client.context.EventContext
         */
        public static EventContext getEventContext(){
            return eventContext;
        }
    
    }
    
8,资源文件
  • spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.javacoo.event.client.support.asm.AsmEventRegister,\
      com.javacoo.event.client.support.ast.AstEventRegister,\
      com.javacoo.event.client.starter.EventAutoConfiguration
    
    
  • com.javacoo.event.client.api.EventHelper

    default=com.javacoo.event.client.internal.guava.EventBusEventHelper
    

问题及局限性

问题

  • ASM方式实现时遇到cglib代理会出现异常情况。

    局限性

  • 不支持事件持久化。

  • 不支持发布异步顺序事件。

  • 未经过生产验证,仅供学习参考,如需用于生产,请谨慎,并多测试。

一些信息

路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo
QQ:164863067
作者/微信:javacoo
邮箱:xihuady@126.com
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

event-spring-boot-starter是一个基于springboot starter机制,结合SPI 接口设计思想实现的事件处理工具组件,旨在提供简单的事件处理编程模型,让基于事件的开发更简单灵活,内部实现基于guava EventBus 实现,扩展方便,集成使用简单。 展开 收起
README
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

语言

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/javacoo/event-spring-boot-starter.git
git@gitee.com:javacoo/event-spring-boot-starter.git
javacoo
event-spring-boot-starter
event-spring-boot-starter
master

搜索帮助