<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一個(gè)牛逼的 Java 字節(jié)碼類庫!

          共 12884字,需瀏覽 26分鐘

           ·

          2021-06-17 22:37

          點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

          作者:rickiyang
          出處:www.cnblogs.com/rickiyang/p/11336268.html

          Java 字節(jié)碼以二進(jìn)制的形式存儲(chǔ)在 .class 文件中,每一個(gè) .class 文件包含一個(gè) Java 類或接口。

          Javassist 就是一個(gè)用來處理 Java 字節(jié)碼的類庫。它可以在一個(gè)已經(jīng)編譯好的類中添加新的方法,或者是修改已有的方法,并且不需要對(duì)字節(jié)碼方面有深入的了解。同時(shí)也可以去生成一個(gè)新的類對(duì)象,通過完全手動(dòng)的方式。

          1. 使用 Javassist 創(chuàng)建一個(gè) class 文件

          首先需要引入jar包:

          <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
          </dependency>

          編寫創(chuàng)建對(duì)象的類:

          package com.rickiyang.learn.javassist;

          import javassist.*;

          /**
           * @author rickiyang
           * @date 2019-08-06
           * @Desc
           */

          public class CreatePerson {

              /**
               * 創(chuàng)建一個(gè)Person 對(duì)象
               *
               * @throws Exception
               */

              public static void createPseson() throws Exception {
                  ClassPool pool = ClassPool.getDefault();

                  // 1. 創(chuàng)建一個(gè)空類
                  CtClass cc = pool.makeClass("com.rickiyang.learn.javassist.Person");

                  // 2. 新增一個(gè)字段 private String name;
                  // 字段名為name
                  CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
                  // 訪問級(jí)別是 private
                  param.setModifiers(Modifier.PRIVATE);
                  // 初始值是 "xiaoming"
                  cc.addField(param, CtField.Initializer.constant("xiaoming"));

                  // 3. 生成 getter、setter 方法
                  cc.addMethod(CtNewMethod.setter("setName", param));
                  cc.addMethod(CtNewMethod.getter("getName", param));

                  // 4. 添加無參的構(gòu)造函數(shù)
                  CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
                  cons.setBody("{name = \"xiaohong\";}");
                  cc.addConstructor(cons);

                  // 5. 添加有參的構(gòu)造函數(shù)
                  cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
                  // $0=this / $1,$2,$3... 代表方法參數(shù)
                  cons.setBody("{$0.name = $1;}");
                  cc.addConstructor(cons);

                  // 6. 創(chuàng)建一個(gè)名為printName方法,無參數(shù),無返回值,輸出name值
                  CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName"new CtClass[]{}, cc);
                  ctMethod.setModifiers(Modifier.PUBLIC);
                  ctMethod.setBody("{System.out.println(name);}");
                  cc.addMethod(ctMethod);

                  //這里會(huì)將這個(gè)創(chuàng)建的類對(duì)象編譯為.class文件
                  cc.writeFile("/Users/yangyue/workspace/springboot-learn/java-agent/src/main/java/");
              }

              public static void main(String[] args) {
                  try {
                      createPseson();
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }

          執(zhí)行上面的 main 函數(shù)之后,會(huì)在指定的目錄內(nèi)生成 Person.class 文件:

          //
          // Source code recreated from a .class file by IntelliJ IDEA
          // (powered by Fernflower decompiler)
          //

          package com.rickiyang.learn.javassist;

          public class Person {
              private String name = "xiaoming";

              public void setName(String var1) {
                  this.name = var1;
              }

              public String getName() {
                  return this.name;
              }

              public Person() {
                  this.name = "xiaohong";
              }

              public Person(String var1) {
                  this.name = var1;
              }

              public void printName() {
                  System.out.println(this.name);
              }
          }

          跟咱們預(yù)想的一樣。

          在 Javassist 中,類 Javaassit.CtClass 表示 class 文件。一個(gè) GtClass (編譯時(shí)類)對(duì)象可以處理一個(gè) class 文件,ClassPoolCtClass 對(duì)象的容器。它按需讀取類文件來構(gòu)造 CtClass 對(duì)象,并且保存 CtClass 對(duì)象以便以后使用。

          這個(gè)資料分享給你:46 張 PPT 弄懂 JVM

          需要注意的是 ClassPool 會(huì)在內(nèi)存中維護(hù)所有被它創(chuàng)建過的 CtClass,當(dāng) CtClass 數(shù)量過多時(shí),會(huì)占用大量的內(nèi)存,API中給出的解決方案是 有意識(shí)的調(diào)用CtClassdetach()方法以釋放內(nèi)存

          ClassPool需要關(guān)注的方法:

          1. getDefault : 返回默認(rèn)的ClassPool 是單例模式的,一般通過該方法創(chuàng)建我們的ClassPool;
          2. appendClassPath, insertClassPath : 將一個(gè)ClassPath加到類搜索路徑的末尾位置 或 插入到起始位置。通常通過該方法寫入額外的類搜索路徑,以解決多個(gè)類加載器環(huán)境中找不到類的尷尬;
          3. toClass : 將修改后的CtClass加載至當(dāng)前線程的上下文類加載器中,CtClass的toClass方法是通過調(diào)用本方法實(shí)現(xiàn)。需要注意的是一旦調(diào)用該方法,則無法繼續(xù)修改已經(jīng)被加載的class
          4. get , getCtClass : 根據(jù)類路徑名獲取該類的CtClass對(duì)象,用于后續(xù)的編輯。

          CtClass需要關(guān)注的方法:

          1. freeze : 凍結(jié)一個(gè)類,使其不可修改;
          2. isFrozen : 判斷一個(gè)類是否已被凍結(jié);
          3. prune : 刪除類不必要的屬性,以減少內(nèi)存占用。調(diào)用該方法后,許多方法無法將無法正常使用,慎用;
          4. defrost : 解凍一個(gè)類,使其可以被修改。如果事先知道一個(gè)類會(huì)被defrost, 則禁止調(diào)用 prune 方法;
          5. detach : 將該class從ClassPool中刪除;
          6. writeFile : 根據(jù)CtClass生成 .class 文件;
          7. toClass : 通過類加載器加載該CtClass。

          上面我們創(chuàng)建一個(gè)新的方法使用了CtMethod類。CtMthod代表類中的某個(gè)方法,可以通過CtClass提供的API獲取或者CtNewMethod新建,通過CtMethod對(duì)象可以實(shí)現(xiàn)對(duì)方法的修改。

          這個(gè)資料分享給你:Spring Boot 學(xué)習(xí)筆記

          CtMethod中的一些重要方法:

          1. insertBefore : 在方法的起始位置插入代碼;
          2. insterAfter : 在方法的所有 return 語句前插入代碼以確保語句能夠被執(zhí)行,除非遇到exception;
          3. insertAt : 在指定的位置插入代碼;
          4. setBody : 將方法的內(nèi)容設(shè)置為要寫入的代碼,當(dāng)方法被 abstract修飾時(shí),該修飾符被移除;
          5. make : 創(chuàng)建一個(gè)新的方法。

          注意到在上面代碼中的:setBody()的時(shí)候我們使用了一些符號(hào):

          // $0=this / $1,$2,$3... 代表方法參數(shù)
          cons.setBody("{$0.name = $1;}");

          具體還有很多的符號(hào)可以使用,但是不同符號(hào)在不同的場(chǎng)景下會(huì)有不同的含義,所以在這里就不在贅述,可以看javassist 的說明文檔:http://www.javassist.org/tutorial/tutorial2.html。

          Java 核心技術(shù)教程和示例源碼:https://github.com/javastacks/javastack

          2. 調(diào)用生成的類對(duì)象

          1. 通過反射的方式調(diào)用

          上面的案例是創(chuàng)建一個(gè)類對(duì)象然后輸出該對(duì)象編譯完之后的 .class 文件。那如果我們想調(diào)用生成的類對(duì)象中的屬性或者方法應(yīng)該怎么去做呢?javassist也提供了相應(yīng)的api,生成類對(duì)象的代碼還是和第一段一樣,將最后寫入文件的代碼替換為如下:

          // 這里不寫入文件,直接實(shí)例化
          Object person = cc.toClass().newInstance();
          // 設(shè)置值
          Method setName = person.getClass().getMethod("setName", String.class);
          setName.invoke(person, "cunhua");
          // 輸出值
          Method execute = person.getClass().getMethod("printName");
          execute.invoke(person);

          然后執(zhí)行main方法就可以看到調(diào)用了 printName方法。另外,微信搜索Java技術(shù)棧,在后臺(tái)發(fā)送:面試,可以獲取我整理的 Java 系列面試題和答案,非常齊全。

          2. 通過讀取 .class 文件的方式調(diào)用
          ClassPool pool = ClassPool.getDefault();
          // 設(shè)置類路徑
          pool.appendClassPath("/Users/yangyue/workspace/springboot-learn/java-agent/src/main/java/");
          CtClass ctClass = pool.get("com.rickiyang.learn.javassist.Person");
          Object person = ctClass.toClass().newInstance();
          //  ...... 下面和通過反射的方式一樣去使用
          3. 通過接口的方式

          上面兩種其實(shí)都是通過反射的方式去調(diào)用,問題在于我們的工程中其實(shí)并沒有這個(gè)類對(duì)象,所以反射的方式比較麻煩,并且開銷也很大。那么如果你的類對(duì)象可以抽象為一些方法得合集,就可以考慮為該類生成一個(gè)接口類。這樣在newInstance()的時(shí)候我們就可以強(qiáng)轉(zhuǎn)為接口,可以將反射的那一套省略掉了。

          還拿上面的Person類來說,新建一個(gè)PersonI接口類:

          package com.rickiyang.learn.javassist;

          /**
           * @author rickiyang
           * @date 2019-08-07
           * @Desc
           */

          public interface PersonI {

              void setName(String name);

              String getName();

              void printName();

          }

          實(shí)現(xiàn)部分的代碼如下:

          ClassPool pool = ClassPool.getDefault();
          pool.appendClassPath("/Users/yangyue/workspace/springboot-learn/java-agent/src/main/java/");

          // 獲取接口
          CtClass codeClassI = pool.get("com.rickiyang.learn.javassist.PersonI");
          // 獲取上面生成的類
          CtClass ctClass = pool.get("com.rickiyang.learn.javassist.Person");
          // 使代碼生成的類,實(shí)現(xiàn) PersonI 接口
          ctClass.setInterfaces(new CtClass[]{codeClassI});

          // 以下通過接口直接調(diào)用 強(qiáng)轉(zhuǎn)
          PersonI person = (PersonI)ctClass.toClass().newInstance();
          System.out.println(person.getName());
          person.setName("xiaolv");
          person.printName();

          使用起來很輕松。

          2. 修改現(xiàn)有的類對(duì)象

          前面說到新增一個(gè)類對(duì)象。這個(gè)使用場(chǎng)景目前還沒有遇到過,一般會(huì)遇到的使用場(chǎng)景應(yīng)該是修改已有的類。比如常見的日志切面,權(quán)限切面。我們利用javassist來實(shí)現(xiàn)這個(gè)功能。

          有如下類對(duì)象:

          package com.rickiyang.learn.javassist;

          /**
           * @author rickiyang
           * @date 2019-08-07
           * @Desc
           */

          public class PersonService {

              public void getPerson(){
                  System.out.println("get Person");
              }

              public void personFly(){
                  System.out.println("oh my god,I can fly");
              }
          }

          然后對(duì)他進(jìn)行修改:

          package com.rickiyang.learn.javassist;

          import javassist.ClassPool;
          import javassist.CtClass;
          import javassist.CtMethod;
          import javassist.Modifier;

          import java.lang.reflect.Method;

          /**
           * @author rickiyang
           * @date 2019-08-07
           * @Desc
           */

          public class UpdatePerson {

              public static void update() throws Exception {
                  ClassPool pool = ClassPool.getDefault();
                  CtClass cc = pool.get("com.rickiyang.learn.javassist.PersonService");

                  CtMethod personFly = cc.getDeclaredMethod("personFly");
                  personFly.insertBefore("System.out.println(\"起飛之前準(zhǔn)備降落傘\");");
                  personFly.insertAfter("System.out.println(\"成功落地。。。。\");");


                  //新增一個(gè)方法
                  CtMethod ctMethod = new CtMethod(CtClass.voidType, "joinFriend"new CtClass[]{}, cc);
                  ctMethod.setModifiers(Modifier.PUBLIC);
                  ctMethod.setBody("{System.out.println(\"i want to be your friend\");}");
                  cc.addMethod(ctMethod);

                  Object person = cc.toClass().newInstance();
                  // 調(diào)用 personFly 方法
                  Method personFlyMethod = person.getClass().getMethod("personFly");
                  personFlyMethod.invoke(person);
                  //調(diào)用 joinFriend 方法
                  Method execute = person.getClass().getMethod("joinFriend");
                  execute.invoke(person);
              }

              public static void main(String[] args) {
                  try {
                      update();
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }

          personFly方法前后加上了打印日志然后新增了一個(gè)方法joinFriend。執(zhí)行main函數(shù)可以發(fā)現(xiàn)已經(jīng)添加上了。

          另外需要注意的是:上面的insertBefore()setBody()中的語句,如果你是單行語句可以直接用雙引號(hào),但是有多行語句的情況下,你需要將多行語句用{}括起來。javassist只接受單個(gè)語句或用大括號(hào)括起來的語句塊。






          關(guān)注Java技術(shù)棧看更多干貨



          獲取 Spring Boot 實(shí)戰(zhàn)筆記!
          瀏覽 42
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久国产精品视频 | 国产高清视频无码 | 91色情网老熟女 | 精品人妻无码一区二区三级精东 | 十八禁在线免费观看网址 |