event-spring-boot-starter是一個基于springboot starter機制,結(jié)合SPI 接口設(shè)計思想實現(xiàn)的事件處理工具組件,旨在提供簡單的事件處理編程模型,讓基于事件的開發(fā)更簡單靈活,內(nèi)部實現(xiàn)基于guava EventBus 實現(xiàn),擴展方便,集成使用簡單口予。
背景介紹
業(yè)務(wù)背景
1、我們在日常開發(fā)過程中經(jīng)常遇到一些特殊業(yè)務(wù)場景(非分布式環(huán)境下,分布式環(huán)境的不在此次討論范圍)涕侈,需要異步處理沪停,常見的處理方式是基于事件機制來完成任務(wù),我們可以基于java的事件處理機制自己實現(xiàn)驾凶,也可以基于Spring事件機制實現(xiàn)牙甫,還可以使用guava EventBus 實現(xiàn),或者基于MQ中間件來實現(xiàn)等等调违,可選方案很多窟哺,大家可以任意發(fā)揮,但同時對于項目來說也帶來了一些問題:技術(shù)不統(tǒng)一技肩,維護成本提高等且轨,所以需要一個統(tǒng)一的事件處理編程模型。
需求分析
1虚婿、提供統(tǒng)一的事件處理編程模型旋奢。
2、盡量少改或者不改造已有功能:少侵入或者0侵入式開發(fā)然痊。
3至朗、擴展方便,集成簡單剧浸,開發(fā)速率高锹引,使用簡單。
設(shè)計思路
事件處理只需要2步:發(fā)布事件唆香,處理事件嫌变。就這么簡單:)
組件整體基于springboot starter機制,結(jié)合SPI 接口設(shè)計思想實現(xiàn)躬它,內(nèi)部集成默認實現(xiàn):guava EventBus腾啥,EventBus使用相對簡單,但是需要我們手動注冊監(jiān)聽者對象有點繁瑣,鑒于此,我想到了兩種方式實現(xiàn)自動注冊監(jiān)聽者對象倘待,1:基于ASM動態(tài)修改字節(jié)碼實現(xiàn)疮跑,2:基于插入式注解處理器在編譯期直接操作抽象語法樹實現(xiàn)。最終選用 第2種方式實現(xiàn)
新建 springboot starter工程:event-spring-boot-starter
基于SPI思想設(shè)計擴展接口(EventHelper):提供事件發(fā)布延柠,監(jiān)聽對象注冊
-
定義事件訂閱注解(MySubscribe):基于ASM動態(tài)修改字節(jié)碼實現(xiàn)
- 實現(xiàn)ApplicationContextAware 接口祸挪,獲取ApplicationContext 應(yīng)用上下文。
- 查找所有包含org.springframework.stereotype.Service注解并且方法上有MySubscribe注解的對象贞间,利用AopUtils工具類獲取其class對象,以及相關(guān)信息雹仿,并封裝為EventMetaData對象(目標類對象增热,包含MySubscribe注解的方法集合等),供下一步使用胧辽。
- 根據(jù)獲取的EventMetaData集合峻仇,依次處理,利用AsmUtil工具類邑商,給目標class對象中目標方法添加Subscribe注解摄咆,并得到新的class對象。
- 根據(jù)EventMetaData中記錄的目標對象beanBean,利用GenericBeanDefinition在ApplicationContext 應(yīng)用上下文中找到該實例對象人断,重新設(shè)置beanClass為新生成的class對象吭从。
- 最后利用DefaultListableBeanFactory 重新注冊該對象。
-
定義事件處理器注解(EventHandler):基于插入式注解處理器在編譯期直接操作抽象語法樹實現(xiàn)
- 繼承AbstractProcessor 重寫process方法恶迈。
- 在process方法方法中涩金,首先找到方法上有EventHandler注解的方法,如果注解屬性threadSafe是true則在該方法上添加AllowConcurrentEvents暇仲,再在該方法上添加Subscribe注解步做,并記錄該類。
- 判斷該類是否已經(jīng)被Spring容器管理奈附,如果沒有則為此類添加org.springframework.stereotype.Component注解全度。
- 然后為此類添加事件監(jiān)聽注冊初始化方法。
- 利用ApplicationListener監(jiān)聽ContextRefreshedEvent事件斥滤,在Spring容器初始化完畢后将鸵,注冊事件監(jiān)聽對象。
內(nèi)部實現(xiàn)guava EventBus
-
擴展接口(EventHelper):
方法名稱 參數(shù) 說明 post (Object eventObject) 發(fā)布同步事件
eventObject:事件對象中跌,可以為任意對象postAsync (Object eventObject) 發(fā)布異步事件
eventObject:事件對象咨堤,可以為任意對象postDelay (Object eventObject, long delayTime) 發(fā)布延遲事件
eventObject:事件對象,可以為任意對象<br />delayTime:延遲時間,單位:毫秒register (Object listenerObject) 注冊監(jiān)聽對象
listenerObject:監(jiān)聽對象漩符,可以為任意對象unRegister (Object listenerObject) 注銷監(jiān)聽對象
listenerObject:監(jiān)聽對象
具體實現(xiàn)見 實施步驟 一節(jié)一喘,由于實施步驟較多,故放在后面章節(jié),我們先來看看如何集成,使用及擴展凸克。
集成议蟆,使用及擴展
源碼
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限流配置
-
發(fā)布事件:任意對象可作為事件對象發(fā)布
@Override public Optional<ExampleDto> getExampleInfo(String id) { AbstractAssert.isNotBlank(id, ErrorCodeConstants.SERVICE_GET_EXAMPLE_INFO_ID); ExampleDto exampleDto = new ExampleDto(); exampleDto.setData("這是測試數(shù)據(jù)"); exampleDto.setId("1"); //發(fā)布延遲事件 EventHolder.getEventHelper().get().postDelay(exampleDto,1000); //發(fā)布同步事件 String str = "我是事件內(nèi)容"; EventHolder.getEventHelper().get().post(str); //發(fā)布異步事件 Integer integer = 1; EventHolder.getEventHelper().get().postAsync(integer); return Optional.ofNullable(exampleDto); }
-
處理事件:
//@MySubscribe public void tesSubscribe(ExampleDto exampleDto){ log.info("事件處理:{}", JSON.toJSONString(exampleDto)); } //@MySubscribe public void tesIntegerSubscribe(Integer integer){ log.info("IntegerSubscribe事件處理:{}", integer); } //@MySubscribe public void tesStringSubscribe(String str){ log.info("StringSubscribe事件處理:{}", str); } @EventHandler public void testEventHandler(ExampleDto exampleDto){ log.info("事件處理:{}", JSON.toJSONString(exampleDto)); } @EventHandler public void testIntegerEventHandler(Integer integer){ log.info("IntegerEventHandler事件處理:{}", integer); } @EventHandler public void testStringEventHandler(String str){ log.info("StringEventHandler事件處理:{}", str); }
事件處理器注解@EventHandler效果
事件訂閱注解@MySubscribe效果
擴展
基于xkernel 提供的SPI機制萎战,擴展非常方便咐容,大致步驟如下:
實現(xiàn)事件幫助類接口:如 com.xxxx.xxxx.MyEventHelper
-
配置事件幫助類接口:
在項目resource目錄新建包->META-INF->services
-
創(chuàng)建com.javacoo.event.client.api.EventHelper文件,文件內(nèi)容:實現(xiàn)類的全局限定名蚂维,如:
myEventHelper=com.xxxx.xxxx.MyEventHelper
修改配置文件戳粒,添加如下內(nèi)容:
#event實現(xiàn),默認內(nèi)部實現(xiàn) event.impl = myEventHelper
注意:**@MySubscribe虫啥,@EventHandler不能共存蔚约,推薦使用@EventHandler注解。
至此組件安裝涂籽,使用及擴展介紹完畢苹祟,接下來將進入預備知識環(huán)節(jié)。
預備知識
1评雌,ASM
-
簡介
ASM 是一個 Java 字節(jié)碼操控框架树枫。它能被用來動態(tài)生成類或者增強既有類的功能。ASM 可以直接產(chǎn)生二進制 class 文件景东,也可以在類被加載入 Java 虛擬機之前動態(tài)改變類行為砂轻。Java class 被存儲在嚴格格式定義的 .class 文件里,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱耐薯、方法舔清、屬性以及 Java 字節(jié)碼(指令)。ASM 從類文件中讀入信息后曲初,能夠改變類行為体谒,分析類信息,甚至能夠根據(jù)用戶要求生成新類臼婆。
-
在ASM的理解和應(yīng)用抒痒,之前需要我們掌握class字節(jié)碼,JVM基于棧的設(shè)計模式,JVM指令
class字節(jié)碼
我們編寫的java文件颁褂,會通過javac命令編譯為class文件故响,JVM最終會執(zhí)行該類型文件來運行程序。下圖所示為class文件結(jié)構(gòu)(圖片來源網(wǎng)絡(luò))颁独。
下面我們通過一個簡單的實例來進行說明彩届。下面是我們編寫的一個簡單的java文件,只是簡單的函數(shù)調(diào)用.
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(); //構(gòu)造函數(shù) 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文件后贮聂,字段名稱,方法名稱寨辩,類型名稱等均在常量池中存在的吓懈。從而做到減小文件的目的。同時方法定義也轉(zhuǎn)變?yōu)榱薺vm指令靡狞。下面我們需要對jvm指令加深一下了解耻警。在了解之前需要我們理解JVM基于棧的設(shè)計模式
JVM基于棧的設(shè)計模式
JVM的指令集是基于棧而不是寄存器,基于椀榕拢可以具備很好的跨平臺性甘穿。在線程中執(zhí)行一個方法時,我們會創(chuàng)建一個棧幀入棧并執(zhí)行梢杭,如果該方法又調(diào)用另一個方法時會再次創(chuàng)建新的棧幀然后入棧扒磁,方法返回之際,原棧幀會返回方法的執(zhí)行結(jié)果給之前的棧幀式曲,隨后虛擬機將會丟棄此棧幀。
局部變量表
局部變量表(Local Variable Table)是一組變量值存儲空間缸榛,用于存放方法參數(shù)和方法內(nèi)定義的局部變量吝羞。虛擬機通過索引定位的方法查找相應(yīng)的局部變量。舉個例子内颗。以上述的代碼為例
public int sub(int a, int b) { return a-b-NUM1; }
這個方法大家可以猜測一下局部變量有哪些? 答案是3個钧排,不應(yīng)該只有a,b嗎?還有this,對應(yīng)實例對象方法編譯器都會追加一個this參數(shù)均澳。如果該方法為靜態(tài)方法則為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
操作數(shù)棧
通過局部變量表我們有了要操作和待更新的數(shù)據(jù),我們?nèi)绻麑植孔兞窟@些數(shù)據(jù)進行操作呢找前?通過操作數(shù)棧糟袁。當一個方法剛剛開始執(zhí)行時,其操作數(shù)棧是空的躺盛,隨著方法執(zhí)行和字節(jié)碼指令的執(zhí)行项戴,會從局部變量表或?qū)ο髮嵗淖侄沃袕椭瞥A炕蜃兞繉懭氲讲僮鲾?shù)棧,再隨著計算的進行將棧中元素出棧到局部變量表或者返回給方法調(diào)用者槽惫,也就是出棧/入棧操作周叮。一個完整的方法執(zhí)行期間往往包含多個這樣出棧/入棧的過程。
JVM指令
- load 命令:用于將局部變量表的指定位置的相應(yīng)類型變量加載到操作數(shù)棧頂界斜;
- store命令:用于將操作數(shù)棧頂?shù)南鄳?yīng)類型數(shù)據(jù)保入局部變量表的指定位置仿耽;
- invokevirtual:調(diào)用實例方法
- ireturn: 當前方法返回int
在舉個例子
a = b + c 的字節(jié)碼執(zhí)行過程中操作數(shù)棧以及局部變量表的變化如下圖所示
ASM操作
通過上面的介紹,我們對字節(jié)碼和JVM指令有了進一步的了解各薇,下面我們看一下ASM是如果編輯class字節(jié)碼的项贺。
ASM API
ASM API基于訪問者模式,為我們提供了ClassVisitor,MethodVisitor敬扛,F(xiàn)ieldVisitor API接口晰洒,每當ASM掃描到類字段是會回調(diào)visitField方法,掃描到類方法是會回調(diào)MethodVisitor啥箭,下面我們看一下API接口
ClassVisitor方法解析
public abstract class ClassVisitor { ...... public void visit(int version, int access, String name, String signature, String superName, String[] interfaces); //訪問類字段時回調(diào) public FieldVisitor visitField(int access, String name, String desc, String signature, Object value); //訪問類方法是回調(diào) 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(); }
-
實戰(zhàn)
利用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("事件注冊數(shù)量:{},對象:{}",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實現(xiàn)事件注冊
@Slf4j @Configuration public class AsmEventRegister extends BaseEventRegister implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { //設(shè)置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("事件元數(shù)據(jù):{}",eventMetaData); ApplicationContextProvider.registerBean(eventMetaData.getBeanName(),newCalss); Object newObject =ApplicationContextProvider.getBean(eventMetaData.getBeanName()); if(newObject != null){ log.info("注冊監(jiān)聽對象:{}",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 { /** * 靜態(tài)初始化器砌滞,由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)是源代碼語法結(jié)構(gòu)的一種抽象表示坏怪。它以樹狀的形式表現(xiàn)編程語言的結(jié)構(gòu)贝润,樹的每個節(jié)點ASTNode都表示源碼中的一個結(jié)構(gòu)。抽象語法樹就像是java文件的dom模型铝宵,比如dom4j通過標簽解析出xml文件栅干。AST把java中的各種元素比如類、屬性媳荒、方法川慌、代碼塊、注解侣夷、注釋等等定義成相應(yīng)的對象横朋,在編譯器編譯代碼的過程中,語法分析器首先通過AST將源碼分析成一個語法樹百拓,然后再轉(zhuǎn)換成二進制文件琴锭。
-
Java的編譯過程可以分成三個階段:
- 所有源文件會被解析成語法樹。
- 調(diào)用注解處理器衙传。如果注解處理器產(chǎn)生了新的源文件决帖,新文件也要進行編譯。
- 最后粪牲,語法樹會被分析并轉(zhuǎn)化成類文件古瓤。
-
例如:下面一段java代的抽象語法樹大概長這樣:
- 上圖即為語法樹,左邊樹的節(jié)點對應(yīng)右邊相同顏色覆蓋的代碼塊腺阳。
![ast-seq.png](https://upload-images.jianshu.io/upload_images/23568343-88299a598fe29f01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-
編輯器對代碼處理的流程大概是:JavaTXT->詞語法分析-> 生成AST ->語義分析 -> 編譯字節(jié)碼
通過操作AST落君,可以達到修改源代碼的功能,相比AOP三劍客亭引,他的時機更為提前:
- AST操作推薦類庫:Rewrite JavaParser
-
實戰(zhàn)
生成@Getter @Setter
首先需要注解類,標明作用的范圍和作用的時期绎速,兩個類分別對應(yīng)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; //構(gòu)建語法樹節(jié)點 protected TreeMaker treeMaker; //創(chuàng)建標識符的對象 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; } /** * 創(chuàng)建方法 * @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<>(); //生成函數(shù)體 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)), //參數(shù)列表 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<>(); //生成函數(shù)體 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())); } }
編譯現(xiàn)有類
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提供給開發(fā)者的用于在編譯期對注解進行處理的一系列API,這類API的使用被廣泛的用于各種框架知残,如dubbo,lombok等靠瞎。
- Java的注解處理一般分為2種,最常見也是最顯式化的就是Spring以及Spring Boot的注解實現(xiàn)了求妹,在運行期容器啟動時乏盐,根據(jù)注解掃描類,并加載到Spring容器中制恍。而另一種就是本文主要介紹的注解處理父能,即編譯期注解處理器,用于在編譯期通過JDK提供的API净神,對Java文件編譯前生成的Java語法樹進行處理何吝,實現(xiàn)想要的功能。
-
實戰(zhàn)
使用基本流程-
定義編譯期的注解
@Target({ElementType.METHOD})//加在方法上的注解 @Retention(RetentionPolicy.SOURCE)//作用于編譯期 public @interface EventHandler { }
-
-
利用JSR269 api(Pluggable Annotation Processing API )創(chuàng)建編譯期的注解處理器
@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://upload-images.jianshu.io/upload_images/23568343-08963e136c18b86a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
處理前
![ast-before.png](https://upload-images.jianshu.io/upload_images/23568343-df513199d8a4001a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
處理后
- 將功能注冊進jar包
4鹃唯,SPI
- SPI全稱Service Provider Interface岔霸,是Java提供的一套用來被第三方實現(xiàn)或者擴展的API,它可以用來啟用框架擴展和替換組件俯渤。
- http://www.reibang.com/p/cc1f08c8aed6
實施步驟
1,新建limit-spring-boot-starter工程
- 工程結(jié)構(gòu)
- 類結(jié)構(gòu)圖
-
項目結(jié)構(gòu)
event-spring-boot-starter └── src ├── main │ ├── java │ │ └── com.javacoo │ │ ├────── event │ │ │ ├──────client │ │ │ │ ├── api │ │ │ │ │ └── EventHelper 事件幫助類接口 │ │ │ │ ├── annotation │ │ │ │ │ └── EventHandler 事件處理器注解 │ │ │ │ │ └── MySubscribe 事件訂閱注解 │ │ │ │ ├── config │ │ │ │ │ └── EventConfig 事件參數(shù)配置 │ │ │ │ ├── context │ │ │ │ │ └── EventContext 事件上下文 │ │ │ │ ├── exception │ │ │ │ │ └── LimitException 限流異常 │ │ │ │ ├── util │ │ │ │ │ └── AsmUtil Asm工具類 │ │ │ │ ├── support │ │ │ │ │ ├── ApplicationContextProvider 抽象限流處理器 │ │ │ │ │ ├── BaseEventRegister 限流注解處理器 │ │ │ │ │ ├── EventMetaData 限流配置處理器 │ │ │ │ └── ├── asm │ │ │ │ │ └── AsmEventRegister ASM實現(xiàn)事件注冊 │ │ │ │ └── ast │ │ │ │ ├── EventHandlerProcessor EventHandler注解處理器 │ │ │ │ └── AstEventRegister AST實現(xiàn)事件注冊 │ │ │ │ └── internal 接口內(nèi)部實現(xiàn) │ │ │ │ └── guava │ │ │ │ └── EventBusEventHelper EventHelper接口實現(xiàn)類 │ │ │ └──────starter │ │ │ ├── EventAutoConfiguration 自動配置類 │ │ │ └── EventHolder 事件處理器對象持有者 │ └── resource │ ├── META-INF │ ├── spring.factories │ └── ext │ └── internal │ └── com.javacoo.event.client.api.EventHelper └── test 測試
2型宝,基于SPI思想設(shè)計擴展接口
-
事件幫助類接口->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 { /** * 發(fā)布同步事件 * <li></li> * @author duanyong@jccfc.com * @date 2021/10/15 11:18 * @param eventObject: 事件對象 * @return: void */ void post(Object eventObject); /** * 發(fā)布異步事件 * <li></li> * @author duanyong@jccfc.com * @date 2021/10/15 11:19 * @param eventObject:事件對象 * @return: void */ void postAsync(Object eventObject); /** * 發(fā)布延遲時間 * <li></li> * @author duanyong@jccfc.com * @date 2021/10/15 11:20 * @param eventObject: 事件對象 * @param time: 延遲時間,單位:毫秒 * @return: void */ void postDelay(Object eventObject, long time); /** * 注冊監(jiān)聽對象 * <p>說明:</p> * <li></li> * @param listenerObject: 監(jiān)聽對象 * @author duanyong@jccfc.com * @date 2021/10/15 18:01 */ void register(Object listenerObject); /** * 注銷監(jiān)聽對象 * <p>說明:</p> * <li></li> * @param listenerObject: 監(jiān)聽對象 * @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 { /** * 當前監(jiān)聽方法是否線程安全 * <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,配置
-
事件參數(shù)配置:com.javacoo.event.client.config.EventConfig
/** * 事件參數(shù)配置 * <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"; /** 默認實現(xiàn):,默認值*/ public static final String DEFAULT_IMPL= "default"; /** event是否可用*/ private String enabled = ENABLED; /**實現(xiàn)*/ 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 { /** * 事件監(jiān)聽對象對象集合 */ private Set<Object> listeningObjects = new HashSet<>(); public Set<Object> getListeningObjects() { return listeningObjects; } public void addListeningObject(Object listeningObject){ listeningObjects.add(listeningObject); } }
6梨树,內(nèi)部實現(xiàn)
-
EventHelper接口實現(xiàn):
/** * EventHelper接口實現(xiàn)類 * <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; /** * 延遲時間執(zhí)行器 */ 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監(jiān)聽
eventBus.register(new Object() {
@Subscribe
public void lister(DeadEvent event) {
log.error("{}發(fā)布的{}事件未找到監(jiān)聽對象", event.getSource().getClass(), event.getEvent());
}
});
asyncEventBus.register(new Object() {
@Subscribe
public void lister(DeadEvent event) {
log.error("{}發(fā)布的{}事件未找到監(jiān)聽對象", event.getSource().getClass(), event.getEvent());
}
});
}
/**
* 發(fā)布同步事件
* <li></li>
*
* @param eventObject : 事件對象
* @author duanyong@jccfc.com
* @date 2021/10/15 11:18
* @return: void
*/
@Override
public void post(Object eventObject) {
log.info("發(fā)布同步事件:{}",eventObject);
eventBus.post(eventObject);
}
/**
* 發(fā)布異步事件
* <li></li>
*
* @param eventObject :事件對象
* @author duanyong@jccfc.com
* @date 2021/10/15 11:19
* @return: void
*/
@Override
public void postAsync(Object eventObject) {
log.info("發(fā)布異步事件:{}",eventObject);
asyncEventBus.post(eventObject);
}
/**
* 發(fā)布延遲時間
* <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("延遲執(zhí)行事件->入延遲隊列:{}",eventObject);
//入延遲隊列
delayQueue.put(new EventBusEventHelper.EventItem(eventObject, time));
//執(zhí)行
delayTaskExecutor.execute(()->execute());
}
/**
* 注冊監(jiān)聽對象
* <p>說明:</p>
* <li></li>
*
* @param listenerObject : 監(jiān)聽對象
* @author duanyong@jccfc.com
* @date 2021/10/15 18:01
*/
@Override
public void register(Object listenerObject) {
log.info("注冊監(jiān)聽對象:{}",listenerObject);
asyncEventBus.register(listenerObject);
eventBus.register(listenerObject);
}
/**
* 注銷監(jiān)聽對象
* <p>說明:</p>
* <li></li>
*
* @param listenerObject : 事件元數(shù)據(jù)
* @author duanyong@jccfc.com
* @date 2021/10/15 18:02
*/
@Override
public void unRegister(Object listenerObject) {
log.info("注銷監(jiān)聽對象:{}",listenerObject.toString());
asyncEventBus.unregister(listenerObject);
eventBus.unregister(listenerObject);
}
/**
* 異步執(zhí)行
* <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("延遲執(zhí)行事件");
}catch (InterruptedException interruptedException){
log.info("延遲執(zhí)行事件異常:",interruptedException);
}
}
/**
* 事件項
* <li></li>
* @author duanyong@jccfc.com
* @date 2021/7/8 9:06
*/
class EventItem<T> implements Delayed {
/** 觸發(fā)時間:單位 毫秒 */
private long time;
/** 事件對象 */
private T eventObject;
public EventItem(T eventObject, long time) {
super();
// 將傳入的時間轉(zhuǎn)換為超時的時刻
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) {
// 剩余時間= 到期時間-當前系統(tǒng)時間抡四,系統(tǒng)一般是納秒級的,所以這里做一次轉(zhuǎn)換
return unit.convert(time- System.nanoTime(), TimeUnit.NANOSECONDS);
}
@Override
public int compareTo(Delayed o) {
// 剩余時間-當前傳入的時間= 實際剩余時間(單位納秒)
long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
// 根據(jù)剩余時間判斷等于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("事件注冊數(shù)量:{},對象:{}",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; } }
-
事件元數(shù)據(jù)
/** * 事件元數(shù)據(jù) * <li></li> * * @author: duanyong@jccfc.com * @since: 2021/10/18 11:42 */ @Data @Builder public class EventMetaData { /** * 目標監(jiān)聽對象名稱 */ private String beanName; /** * 目標監(jiān)聽對象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實現(xiàn)事件注冊
/** * ASM實現(xiàn)事件注冊 * <p>說明:Asm實現(xiàn)</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 { //設(shè)置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("事件元數(shù)據(jù):{}",eventMetaData); ApplicationContextProvider.registerBean(eventMetaData.getBeanName(),newCalss); Object newObject =ApplicationContextProvider.getBean(eventMetaData.getBeanName()); if(newObject != null){ log.info("注冊監(jiān)聽對象:{}",newObject); EventHolder.getEventHelper().get().register(newObject); } } }); } } }
-
AST實現(xiàn)事件注冊
/** * AST實現(xiàn)事件注冊 * <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) { //設(shè)置applicationContext ApplicationContextProvider.setApplicationContext(event.getApplicationContext()); //注冊事件監(jiān)聽對象 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;
/**
* 封裝了創(chuàng)建AST節(jié)點的一些方法
*/
private TreeMaker treeMaker;
/**
* 提供了創(chuàng)建標識符的一些方法
*/
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");
//構(gòu)建方法
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);
//獲取源注解的參數(shù)
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);
//獲取源注解的參數(shù)
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: 參數(shù)
* @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);
}
}
/**
* 構(gòu)建事件監(jiān)聽對象注冊方法
* <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("構(gòu)建事件監(jiān)聽對象注冊方法");
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);
}
/**
* 構(gòu)建注解對象
* <li></li>
* @author duanyong@jccfc.com
* @date 2021/10/21 11:39
* @param annotaionName:注解名稱
* @param args: 注解參數(shù)
* @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;
}
/**
* 構(gòu)建依賴包導入
* <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("構(gòu)建依賴包導入:{}",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("初始化事件處理器,實現(xiàn)類名稱:{}",eventConfig.getImpl()); EventHolder.eventHelper = ExtensionLoader.getExtensionLoader(EventHelper.class).getExtension(eventConfig.getImpl()); log.info("初始化事件處理器成功,實現(xiàn)類:{}",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); } /** * 注冊事件監(jiān)聽對象 * <li>這里只是將監(jiān)聽對象存入上下文,待組件初始化完成才注冊</li> * @author duanyong@jccfc.com * @date 2021/10/21 9:05 * @param listeningObject: * @return: void */ public static void register(Object listeningObject){ log.info("注冊事件監(jiān)聽對象:{}",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方式實現(xiàn)時遇到cglib代理會出現(xiàn)異常情況隶垮。
局限性
不支持事件持久化藻雪。
不支持發(fā)布異步順序事件。
該組件未經(jīng)過生產(chǎn)驗證狸吞,僅供學習參考勉耀,如需用于生產(chǎn)指煎,請謹慎,并多測試便斥。
一些信息
路漫漫其修遠兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ:164863067
作者/微信:javacoo
郵箱:xihuady@126.com