事件处理工具组件
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动态修改字节码实现
- 实现ApplicationContextAware 接口,获取ApplicationContext 应用上下文。
- 查找所有包含org.springframework.stereotype.Service注解并且方法上有MySubscribe注解的对象,利用AopUtils工具类获取其class对象,以及相关信息,并封装为EventMetaData对象(目标类对象,包含MySubscribe注解的方法集合等),供下一步使用。
- 根据获取的EventMetaData集合,依次处理,利用AsmUtil工具类,给目标class对象中目标方法添加Subscribe注解,并得到新的class对象。
- 根据EventMetaData中记录的目标对象beanBean,利用GenericBeanDefinition在ApplicationContext 应用上下文中找到该实例对象,重新设置beanClass为新生成的class对象。
- 最后利用DefaultListableBeanFactory 重新注册该对象。
-
定义事件处理器注解(EventHandler):基于插入式注解处理器在编译期直接操作抽象语法树实现
- 继承AbstractProcessor 重写process方法。
- 在process方法方法中,首先找到方法上有EventHandler注解的方法,如果注解属性threadSafe是true则在该方法上添加AllowConcurrentEvents,再在该方法上添加Subscribe注解,并记录该类。
- 判断该类是否已经被Spring容器管理,如果没有则为此类添加org.springframework.stereotype.Component注解。
- 然后为此类添加事件监听注册初始化方法。
- 利用ApplicationListener监听ContextRefreshedEvent事件,在Spring容器初始化完毕后,注册事件监听对象。
-
内部实现guava EventBus
-
扩展接口(EventHelper):
方法名称 参数 说明 post (Object eventObject) 发布同步事件
eventObject:事件对象,可以为任意对象postAsync (Object eventObject) 发布异步事件
eventObject:事件对象,可以为任意对象postDelay (Object eventObject, long delayTime) 发布延迟事件
eventObject:事件对象,可以为任意对象<br />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效果
scrlog.png扩展
基于xkernel 提供的SPI机制,扩展非常方便,大致步骤如下:
-
实现事件帮助类接口:如 com.xxxx.xxxx.MyEventHelper
-
配置事件帮助类接口:
-
在项目resource目录新建包->META-INF->services
-
创建com.javacoo.event.client.api.EventHelper文件,文件内容:实现类的全局限定名,如:
myEventHelper=com.xxxx.xxxx.MyEventHelper
-
修改配置文件,添加如下内容:
#event实现,默认内部实现 event.impl = myEventHelper
-
注意:**@MySubscribe,@EventHandler不能共存,推荐使用@EventHandler注解。
至此组件安装,使用及扩展介绍完毕,接下来将进入预备知识环节。
预备知识
1,ASM
-
简介
-
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 imgASM操作
通过上面的介绍,我们对字节码和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(); }
-
-
实战
利用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
-
简介
-
抽象语法树(Abstract Syntax Tree, AST)是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的结构,树的每个节点ASTNode都表示源码中的一个结构。抽象语法树就像是java文件的dom模型,比如dom4j通过标签解析出xml文件。AST把java中的各种元素比如类、属性、方法、代码块、注解、注释等等定义成相应的对象,在编译器编译代码的过程中,语法分析器首先通过AST将源码分析成一个语法树,然后再转换成二进制文件。
-
Java的编译过程可以分成三个阶段:
javac flow- 所有源文件会被解析成语法树。
- 调用注解处理器。如果注解处理器产生了新的源文件,新文件也要进行编译。
- 最后,语法树会被分析并转化成类文件。
-
例如:下面一段java代的抽象语法树大概长这样:
ast.jpg
-
- 上图即为语法树,左边树的节点对应右边相同颜色覆盖的代码块。
![ast-seq.png](https://img.haomeiwen.com/i23568343/88299a598fe29f01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-
编辑器对代码处理的流程大概是:JavaTXT->词语法分析-> 生成AST ->语义分析 -> 编译字节码
AST Workflow -
通过操作AST,可以达到修改源代码的功能,相比AOP三剑客,他的时机更为提前:
- AST操作推荐类库:Rewrite JavaParser
-
实战
生成@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,编译期注解处理器
-
简介
- Java编译期注解处理器,Annotation Processing Tool,简称APT,是Java提供给开发者的用于在编译期对注解进行处理的一系列API,这类API的使用被广泛的用于各种框架,如dubbo,lombok等。
- Java的注解处理一般分为2种,最常见也是最显式化的就是Spring以及Spring Boot的注解实现了,在运行期容器启动时,根据注解扫描类,并加载到Spring容器中。而另一种就是本文主要介绍的注解处理,即编译期注解处理器,用于在编译期通过JDK提供的API,对Java文件编译前生成的Java语法树进行处理,实现想要的功能。
-
实战
使用基本流程-
定义编译期的注解
@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(抽象语法树)
处理过程
![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
- 将功能注册进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
```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);
}
}
-
事件注册基类
/** * 事件注册基类 * <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;
}
}
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