事件處理工具組件

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)

    1. 實現(xiàn)ApplicationContextAware 接口祸挪,獲取ApplicationContext 應(yīng)用上下文。
    2. 查找所有包含org.springframework.stereotype.Service注解并且方法上有MySubscribe注解的對象贞间,利用AopUtils工具類獲取其class對象,以及相關(guān)信息雹仿,并封裝為EventMetaData對象(目標類對象增热,包含MySubscribe注解的方法集合等),供下一步使用胧辽。
    3. 根據(jù)獲取的EventMetaData集合峻仇,依次處理,利用AsmUtil工具類邑商,給目標class對象中目標方法添加Subscribe注解摄咆,并得到新的class對象。
    4. 根據(jù)EventMetaData中記錄的目標對象beanBean,利用GenericBeanDefinition在ApplicationContext 應(yīng)用上下文中找到該實例對象人断,重新設(shè)置beanClass為新生成的class對象吭从。
    5. 最后利用DefaultListableBeanFactory 重新注冊該對象。
  • 定義事件處理器注解(EventHandler):基于插入式注解處理器在編譯期直接操作抽象語法樹實現(xiàn)

    1. 繼承AbstractProcessor 重寫process方法恶迈。
    2. 在process方法方法中涩金,首先找到方法上有EventHandler注解的方法,如果注解屬性threadSafe是true則在該方法上添加AllowConcurrentEvents暇仲,再在該方法上添加Subscribe注解步做,并記錄該類。
    3. 判斷該類是否已經(jīng)被Spring容器管理奈附,如果沒有則為此類添加org.springframework.stereotype.Component注解全度。
    4. 然后為此類添加事件監(jiān)聽注冊初始化方法。
    5. 利用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限流配置

config.png
  • 發(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效果

entlog.png

事件訂閱注解@MySubscribe效果

scrlog.png
擴展

基于xkernel 提供的SPI機制萎战,擴展非常方便咐容,大致步驟如下:

  1. 實現(xiàn)事件幫助類接口:如 com.xxxx.xxxx.MyEventHelper

  2. 配置事件幫助類接口:

    • 在項目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

  1. 簡介

    • 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ò))颁独。

      img

      下面我們通過一個簡單的實例來進行說明彩届。下面是我們編寫的一個簡單的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é)果給之前的棧幀式曲,隨后虛擬機將會丟棄此棧幀。

      img
      局部變量表

      局部變量表(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ù)棧以及局部變量表的變化如下圖所示

      img
      img
      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();
      }
      
  2. 實戰(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

  1. 簡介
    • 抽象語法樹(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的編譯過程可以分成三個階段:

      javac flow
      1. 所有源文件會被解析成語法樹。
      2. 調(diào)用注解處理器衙传。如果注解處理器產(chǎn)生了新的源文件决帖,新文件也要進行編譯。
      3. 最后粪牲,語法樹會被分析并轉(zhuǎn)化成類文件古瓤。
    • 例如:下面一段java代的抽象語法樹大概長這樣:

      ast.jpg
  • 上圖即為語法樹,左邊樹的節(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 Workflow
  • 通過操作AST落君,可以達到修改源代碼的功能,相比AOP三劍客亭引,他的時機更為提前:

aop.jpg
  1. 實戰(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洒宝,編譯期注解處理器

  1. 簡介

    • 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)想要的功能。
  2. 實戰(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)


 處理后
ast-after.png
  • 將功能注冊進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)
pj.png
  • 類結(jié)構(gòu)圖
EventAutoConfiguration.png
  • 項目結(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末至壤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子枢纠,更是在濱河造成了極大的恐慌像街,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件京郑,死亡現(xiàn)場離奇詭異宅广,居然都是意外死亡,警方通過查閱死者的電腦和手機些举,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門跟狱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人户魏,你說我怎么就攤上這事驶臊。” “怎么了叼丑?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵关翎,是天一觀的道長。 經(jīng)常有香客問我鸠信,道長纵寝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任星立,我火速辦了婚禮爽茴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绰垂。我一直安慰自己室奏,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布劲装。 她就那樣靜靜地躺著胧沫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪占业。 梳的紋絲不亂的頭發(fā)上绒怨,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音谦疾,去河邊找鬼窖逗。 笑死,一個胖子當著我的面吹牛餐蔬,可吹牛的內(nèi)容都是我干的碎紊。 我是一名探鬼主播佑附,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼仗考!你這毒婦竟也來了音同?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤秃嗜,失蹤者是張志新(化名)和其女友劉穎权均,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锅锨,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡叽赊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了必搞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片必指。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖恕洲,靈堂內(nèi)的尸體忽然破棺而出塔橡,到底是詐尸還是另有隱情,我是刑警寧澤霜第,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布葛家,位于F島的核電站,受9級特大地震影響泌类,放射性物質(zhì)發(fā)生泄漏癞谒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一刃榨、第九天 我趴在偏房一處隱蔽的房頂上張望扯俱。 院中可真熱鬧,春花似錦喇澡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至为流,卻和暖如春呕屎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敬察。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工秀睛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莲祸。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓蹂安,卻偏偏與公主長得像椭迎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子田盈,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容