开发技巧spring 整合使用spring

事件处理工具组件

2021-10-21  本文已影响0人  javacoo

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种方式实现

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

集成,使用及扩展

源码

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>
使用
config.png entlog.png

事件订阅注解@MySubscribe效果

scrlog.png
扩展

基于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代的抽象语法树大概长这样:

      ast.jpg
![ast-seq.png](https://img.haomeiwen.com/i23568343/88299a598fe29f01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
aop.jpg
  1. 实战

    生成@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 {
      }
      
![ast-pro.png](https://img.haomeiwen.com/i23568343/08963e136c18b86a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 处理前

 ![ast-before.png](https://img.haomeiwen.com/i23568343/df513199d8a4001a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 处理后
ast-after.png

4,SPI


实施步骤

1,新建limit-spring-boot-starter工程
pj.png EventAutoConfiguration.png
2,基于SPI思想设计扩展接口
3,注解
4,配置
5,上下文
6,内部实现

//注册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

```java
/**
 * 获取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);
    }

}

 /**
 * 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;
    }
}
  
9,自动配置
8,资源文件

问题及局限性

问题

一些信息

路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo
QQ:164863067
作者/微信:javacoo
邮箱:xihuady@126.com
上一篇下一篇

猜你喜欢

热点阅读