# event-spring-boot-starter
**Repository Path**: javacoo/event-spring-boot-starter
## Basic Information
- **Project Name**: event-spring-boot-starter
- **Description**: event-spring-boot-starter是一个基于springboot starter机制,结合SPI 接口设计思想实现的事件处理工具组件,旨在提供简单的事件处理编程模型,让基于事件的开发更简单灵活,内部实现基于guava EventBus 实现,扩展方便,集成使用简单。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 7
- **Forks**: 3
- **Created**: 2021-10-15
- **Last Updated**: 2025-02-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 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
##### 集成
```xml
com.javacoo
event-spring-boot-starter
1.0.0
```
##### 使用
- 配置文件接入事件处理器(此步骤可省略,默认可用)
主要配置说明,详见 EventConfig限流配置

- 发布事件:任意对象可作为事件对象发布
```java
@Override
public Optional 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);
}
```
- 处理事件:
```java
//@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文件,文件内容:实现类的全局限定名,如:
```properties
myEventHelper=com.xxxx.xxxx.MyEventHelper
```
- 修改配置文件,添加如下内容:
```properties
#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文件结构(图片来源网络)。

下面我们通过一个简单的实例来进行说明。下面是我们编写的一个简单的java文件,只是简单的函数调用.
```text
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文件格式。
```text
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."":()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
#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
#24 = Utf8 SourceFile
#25 = Utf8 Test.java
#26 = NameAndType #10:#11 // "":()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."":()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的指令集是基于栈而不是寄存器,基于栈可以具备很好的跨平台性。在线程中执行一个方法时,我们会创建一个栈帧入栈并执行,如果该方法又调用另一个方法时会再次创建新的栈帧然后入栈,方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧,随后虚拟机将会丢弃此栈帧。

##### 局部变量表
**局部变量表(Local Variable Table)**是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。虚拟机通过索引定位的方法查找相应的局部变量。举个例子。以上述的代码为例
```text
public int sub(int a, int b) {
return a-b-NUM1;
}
```
这个方法大家可以猜测一下局部变量有哪些? 答案是3个,不应该只有a,b吗?还有this,对应实例对象方法编译器都会追加一个this参数。如果该方法为静态方法则为2个了。
```text
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 的字节码执行过程中操作数栈以及局部变量表的变化如下图所示


##### ASM操作
通过上面的介绍,我们对字节码和JVM指令有了进一步的了解,下面我们看一下ASM是如果编辑class字节码的。
##### ASM API
ASM API基于访问者模式,为我们提供了ClassVisitor,MethodVisitor,FieldVisitor API接口,每当ASM扫描到类字段是会回调visitField方法,扫描到类方法是会回调MethodVisitor,下面我们看一下API接口
**ClassVisitor方法解析**
```java
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方法解析
```java
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
```java
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MySubscribe {
}
```
事件注册基类
```java
@Slf4j
public class BaseEventRegister {
/**
* 获取所有注解 类
* 说明:
* 在所有注解 org.springframework.stereotype.Service 类中查找方法包含指定注解
* @author duanyong@jccfc.com
* @param annClass 类注解class
* @param mAnnClass 方法注解class
* @date 2021/10/20 22:02
* @retuen Set
*/
protected Set getEventMetaDatas(Class extends Annotation> annClass,Class extends Annotation> mAnnClass){
//查找Service
Map serviceMap = ApplicationContextProvider.getApplicationContext().getBeansWithAnnotation(annClass);
log.info("事件注册数量:{},对象:{}",serviceMap.size(),serviceMap);
Set eventMetaDatas = new HashSet<>();
for (Map.Entry entry : serviceMap.entrySet()) {
Class entryClass = AopUtils.getTargetClass(entry.getValue());
//获取注解所在方法 public方法
List 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实现事件注册
```java
@Slf4j
@Configuration
public class AsmEventRegister extends BaseEventRegister implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//设置applicationContext
ApplicationContextProvider.setApplicationContext(applicationContext);
//查找Service
Set eventMetaDatas = getEventMetaDatas(Service.class, MySubscribe.class);
if(EventHolder.getEventHelper().isPresent() & !eventMetaDatas.isEmpty()){
eventMetaDatas.forEach(eventMetaData -> {
List 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工具类
```java
/**
* Asm工具类
*
*
* @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();
}
/**
* 添加注解
*
* @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 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 methods;
private String annotation;
public AsmMethodVisitor(int api, ClassVisitor cv,List 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的编译过程可以分成三个阶段:

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

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

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

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

- AST操作推荐类库:[Rewrite](https://github.com/Netflix-Skunkworks/rewrite) [JavaParser](https://github.com/Javaparser/Javaparser)
2. **实战**
生成@Getter @Setter
首先需要注解类,标明作用的范围和作用的时期,两个类分别对应Lombok的Getter、Setter
```java
@Target({ElementType.TYPE}) //加在类上的注解
@Retention(RetentionPolicy.SOURCE) //作用于编译期
public @interface Getter {
}
@Target({ElementType.TYPE}) //加在类上的注解
@Retention(RetentionPolicy.SOURCE) //作用于编译期
public @interface Setter {
}
```
新建抽象注解处理器需要继承AbstractProcessor,这里使用模板方法模式
```java
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 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
```java
@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 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
```java
@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 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()));
}
}
```
编译现有类
```shell
javac -cp $JAVA_HOME/lib/tools.jar *.java -d .
```
新建一个测试类 IDEA中由于找不到get set方法会报错,可以忽略
```java
@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());
}
}
```
接着编译,多个处理器用逗号分隔
```shell
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. **实战**
使用基本流程
- 定义编译期的注解
```java
@Target({ElementType.METHOD})//加在方法上的注解
@Retention(RetentionPolicy.SOURCE)//作用于编译期
public @interface EventHandler {
}
```
- 利用JSR269 api(Pluggable Annotation Processing API )创建编译期的注解处理器
```java
@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工程
- 工程结构

- 类结构图

- 项目结构
```tex
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
```java
/**
* 事件帮助类接口
*
*
* @author: duanyong@jccfc.com
* @since: 2021/10/15 11:16
*/
@Spi(EventConfig.DEFAULT_IMPL)
public interface EventHelper {
/**
* 发布同步事件
*
* @author duanyong@jccfc.com
* @date 2021/10/15 11:18
* @param eventObject: 事件对象
* @return: void
*/
void post(Object eventObject);
/**
* 发布异步事件
*
* @author duanyong@jccfc.com
* @date 2021/10/15 11:19
* @param eventObject:事件对象
* @return: void
*/
void postAsync(Object eventObject);
/**
* 发布延迟时间
*
* @author duanyong@jccfc.com
* @date 2021/10/15 11:20
* @param eventObject: 事件对象
* @param time: 延迟时间,单位:毫秒
* @return: void
*/
void postDelay(Object eventObject, long time);
/**
* 注册监听对象
* 说明:
*
* @param listenerObject: 监听对象
* @author duanyong@jccfc.com
* @date 2021/10/15 18:01
*/
void register(Object listenerObject);
/**
* 注销监听对象
* 说明:
*
* @param listenerObject: 监听对象
* @author duanyong@jccfc.com
* @date 2021/10/15 18:02
*/
void unRegister(Object listenerObject);
}
```
##### 3,注解
- 事件处理器注解:com.javacoo.event.client.annotation.EventHandler
```java
/**
* 事件处理器注解
*
*
* @author: duanyong@jccfc.com
* @since: 2021/10/19 10:40
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface EventHandler {
/**
* 当前监听方法是否线程安全
* 如果是线程安全的,则不会同步订阅者对象,效率更高
* @author duanyong@jccfc.com
* @date 2021/11/1 10:35
* @return: boolean 默认false
*/
boolean threadSafe() default false;
}
```
- 事件订阅注解:com.javacoo.event.client.annotation.MySubscribe
```java
/**
* 事件订阅注解
*
* @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
```java
/**
* 事件参数配置
* 说明:
* @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
```java
/**
* 事件上下文
*
*
* @author: duanyong@jccfc.com
* @since: 2021/10/21 8:58
*/
public class EventContext {
/**
* 事件监听对象对象集合
*/
private Set