javassist 動(dòng)態(tài)修改字節(jié)碼

一、gradle Transform 接收一個(gè)輸入input,同時(shí)需要有一組輸出,作為下一個(gè)Transform的輸入。

(1)最簡單的一個(gè)Transform實(shí)現(xiàn)坦康,需要實(shí)現(xiàn)
將輸入數(shù)據(jù)input,原樣不動(dòng)輸出到output
(2)Transform處理的結(jié)果,會(huì)位于工程目錄?/build?/?intermediates?/transform文件夾下雷厂。
如下圖XXX目錄即為自定義的一個(gè)Transfrom。
由圖可知除XXX外叠殷,還經(jīng)過了dexBuilder改鲫、dexMerger、mergeJavaRes林束、mergeJniLibs像棘、StripDebugSymbol等多個(gè)Transform處理

image.png

二、自定義gradle插件實(shí)例

1壶冒、自定義gradle插件的build.gradle

apply plugin: 'groovy'
apply plugin: 'maven'
apply plugin: 'java'
apply plugin: 'maven-publish'

dependencies {
//    implementation fileTree(dir: 'libs', include: ['*.jar'])

    compile gradleApi()//gradle sdk
    compile localGroovy()//groovy sdk


    //build tools
    compile 'com.android.tools.build:gradle:3.1.2'
    //transform
    compile 'com.android.tools.build:transform-api:1.5.0'
    //javassit
    compile 'javassist:javassist:3.12.1.GA'
    //commons-io
    compile 'commons-io:commons-io:2.5'
}
repositories {
    jcenter()
    google()//加在這里
}


SecondPlugin.groovy 自定義插件缕题,內(nèi)部為android注冊了一個(gè)ReClassTransform 接口。

package com.feifei.second

import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.feifei.second.transform.ReClassTransform
import org.gradle.api.Plugin
import org.gradle.api.Project

public class SecondPlugin implements Plugin<Project>{

    void apply(Project project){
        System.out.println("==========")
        System.out.println("feifei  第二個(gè)內(nèi)部用插件")
        System.out.println("==========")

        project.extensions.create("pluginExt",PluginExtension)
        project.pluginExt.extensions.create("nestExt", PluginNestExtension)
        project.task('customTask',type:CustomTask)

        def isApp = project.plugins.getPlugin(AppPlugin)

        if(isApp){
            def android =  project.extensions.getByType(AppExtension)
            android.registerTransform(new ReClassTransform(project))
        }
    }
}

最原始的Transform實(shí)現(xiàn)胖腾。
ReClassTransfrom.groovy

package com.feifei.second.transform
import com.android.build.api.transform.*
import com.android.build.api.transform.Context
import com.android.build.api.transform.DirectoryInput
import com.android.build.api.transform.JarInput
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import com.android.utils.FileUtils
import org.apache.commons.codec.digest.DigestUtils
import org.gradle.api.Project
import org.gradle.internal.impldep.org.apache.ivy.util.FileUtil
import org.gradle.jvm.tasks.Jar
import com.android.build.gradle.internal.pipeline.TransformManager

import javax.xml.crypto.dsig.TransformException

public class ReClassTransform extends Transform{

    private Project mProject;

    public ReClassTransform(Project p){
        this.mProject = p;
    }

    //transform的名稱
    /**
     * 最終運(yùn)行的名字為 transformClassWith+getName()+For+{BuildType}+{ProductFlavor}
     * 如 transformClassWithXXXForDebug
     * @return
     */
    @Override
    String getName() {
        return "XXX"
    }

    /**
     * 需要處理的數(shù)據(jù)類型烟零,有兩種枚舉類型
     * CLASSES和RESOURCES,CLASSES代表處理的java的class文件;RESOURCES代表要處理java的資源.
     * @return
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS;
    }

    /**
     * 指Transform要操作內(nèi)容的范圍咸作,官方文檔Scope有7種類型:
     * EXTERNAL_LIBRARIES   只有外部庫
     * PROJECT              只有項(xiàng)目內(nèi)容
     * PROJECT_LOCAL_DEPS   只有項(xiàng)目的本地依賴(本地jar)
     * PROVIDED_ONLY        只提供本地或遠(yuǎn)程依賴項(xiàng)
     * SUB_PROJECTS         只有子項(xiàng)目
     * SUB_PROJECTS_LOCAL_DEPS 只有子項(xiàng)目的本地依賴項(xiàng)(本地jar)锨阿。
     * TESTED_CODE          由當(dāng)前變量(包括依賴項(xiàng))測試的代碼
     * @return
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT;
    }
//指明當(dāng)前Transform是否支持增量編譯
    @Override
    boolean isIncremental() {
        return false
    }

    /**
     * Transform中的核心方法,
     *
     * @param context 记罚。
     * @param inputs  傳過來的輸入流, 其中有兩種格式墅诡,一種是jar包格式一種是目錄格式
     * @param referencedInputs
     * @param outputProvider  獲取到輸出目錄,最后將修改的文件復(fù)制到輸出目錄桐智,這一步必須做不然編譯會(huì)報(bào)錯(cuò)
     * @param isInCremental
     * @throws IOException
     * @throws TransformException
     */
    @Override
    public void transform(Context context,
                          Collection<TransformInput> inputs,
            Collection<TransformInput> referencedInputs,
            TransformOutputProvider outputProvider,
            boolean isInCremental
    ) throws IOException, TransformException{

        welecome()

        inputs.each { TransformInput input->

            //遍歷目錄
            input.directoryInputs.each { DirectoryInput directoryInput ->

                println "direction = "+directoryInput.file.getAbsolutePath()
                //獲取輸出目錄
                def dest = outputProvider.getContentLocation(directoryInput.name,directoryInput.contentTypes,directoryInput.scopes,Format.DIRECTORY)

                //對于目錄中的class文件原樣輸出
                FileUtils.copyDirectory(directoryInput.file,dest)
            }

            //遍歷jar文件,對jar不操作末早,但是要輸出到out目錄
            input.jarInputs.each { JarInput jarInput->

                // 將jar文件 重命名輸出文件(同目錄copyFile會(huì)沖突)
                def jarName = jarInput.name
                println "jar = "+jarInput.file.getAbsolutePath()

                def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
                if(jarName.endsWith(".jar")){
                    jarName = jarName.substring(0,jarName.length()-4)
                }
                def dest = outputProvider.getContentLocation(jarName+md5Name,jarInput.contentTypes, jarInput.scopes, Format.JAR)
                FileUtils.copyFile(jarInput.file, dest)
            }

        }

        end()

    }


    def welecome(){
        println "----welcome to ReClassTransform"
    }

    def end(){
        println "----ReClassTransform end"
    }
}

執(zhí)行./gradlew :test_gradle_use_plugin:assembleDebug
時(shí)的輸出內(nèi)容。

> Task :test_gradle_use_plugin:transformClassesWithXXXForDebug 
----welcome to ReClassTransform
jar = /Users/feifei/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.0.aar/ad39ea76672d18218cf29f42ea94a4d7/jars/classes.jar
jar = /Users/feifei/.gradle/caches/modules-2/files-2.1/com.android.support.constraint/constraint-layout-solver/1.1.0/931532e953a477f876f2de18c2e7f16eee01078f/constraint-layout-solver-1.1.0.jar
direction = /Users/feifei/Desktop/TM/Github/MyExampleCode/test_gradle_use_plugin/build/intermediates/classes/debug
direction = /Users/feifei/Desktop/TM/Github/MyExampleCode/test_gradle_use_plugin/build/tmp/kotlin-classes/debug
----ReClassTransform end

2说庭、利用向文件中寫入字符串的形式直接生成類文件

Hostconfig.groovy
增加HostConfig的調(diào)用

package com.feifei.second.hostconfig


public class HostConfig {
    static def void createHostConfig(variant,config){

def content = """
package com.sogou.teemo.test_use_gradle_plugin;
public class TheHostConfig{
    public static final String ip = "${config.param1}";
    public static final String port = "5050"; 

}

"""

        File outputDir = variant.getVariantData().getScope().getBuildConfigSourceOutputDir()
        println "feifei createHostConfig outputDir:"+outputDir.getAbsolutePath()
        def javaFile = new File(outputDir, "TheHostConfig.java")
        javaFile.write(content,'UTF-8')

    }
}

SecondPlugin.groovy

package com.feifei.second

import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.api.ApplicationVariant
import com.android.repository.impl.meta.Archive
import com.feifei.second.hostconfig.HostConfig
import com.feifei.second.transform.ReClassTransform
import org.gradle.api.Plugin
import org.gradle.api.Project

public class SecondPlugin implements Plugin<Project>{

    void apply(Project project){
        System.out.println("==========")
        System.out.println("feifei  第二個(gè)內(nèi)部用插件")
        System.out.println("==========")

        project.extensions.create("pluginExt",PluginExtension)
        project.pluginExt.extensions.create("nestExt", PluginNestExtension)
        project.task('customTask',type:CustomTask)

        def isApp = project.plugins.getPlugin(AppPlugin)

        if(isApp){
            def android =  project.extensions.getByType(AppExtension)
            android.registerTransform(new ReClassTransform(project))



            android.applicationVariants.all { variants->

                def variantData =  variants.variantData
                def scope = variantData.scope

                println "feifei current scope:"+scope

                //scope.getTaskName 的作用 就是結(jié)合當(dāng)前scope 拼接人物名
                def taskName = scope.getTaskName("CreateHostConfig")
                def createTask = project.task(taskName)

                println "feifei CreateHostConfigTaskName:"+taskName

                //自定義task 增加action
                createTask.doLast {
                    HostConfig.createHostConfig(variants,project.pluginExt)
                }

                String generateBuildConfigTaskName = scope.getGenerateBuildConfigTask().name
                def generateBuildConfigTask = project.tasks.getByName(generateBuildConfigTaskName)
                println "feifei  generateBuildConfigTaskName:"+generateBuildConfigTaskName

                if(generateBuildConfigTask){
                    createTask.dependsOn generateBuildConfigTask
                    generateBuildConfigTask.finalizedBy(createTask)//執(zhí)行完generateBuildConfigTask之后,執(zhí)行createTask任務(wù)
                }
            }
        }
    }
}

執(zhí)行 ./gradlew clean :test_gradle_use_plugin:assembleDebug
輸出如下:

 Configure project :test_gradle_use_plugin 
==========
feifei  第二個(gè)內(nèi)部用插件
==========
feifei current scope:VariantScopeImpl{debug}
feifei CreateHostConfigTaskName:CreateHostConfigDebug
feifei  generateBuildConfigTaskName:generateDebugBuildConfig
feifei current scope:VariantScopeImpl{release}
feifei CreateHostConfigTaskName:CreateHostConfigRelease
feifei  generateBuildConfigTaskName:generateReleaseBuildConfig

> Task :test_gradle_use_plugin:transformClassesWithXXXForDebug 
----welcome to ReClassTransform
jar = /Users/feifei/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.0.aar/5ae74cdeff58ee396218df991052866b/jars/classes.jar
jar = /Users/feifei/.gradle/caches/modules-2/files-2.1/com.android.support.constraint/constraint-layout-solver/1.1.0/931532e953a477f876f2de18c2e7f16eee01078f/constraint-layout-solver-1.1.0.jar
direction = /Users/feifei/Desktop/TM/Github/MyExampleCode/test_gradle_use_plugin/build/intermediates/classes/debug
direction = /Users/feifei/Desktop/TM/Github/MyExampleCode/test_gradle_use_plugin/build/tmp/kotlin-classes/debug
----ReClassTransform end




生成類文件的位置:

image.png

3然磷、利用javassist 向現(xiàn)有類中動(dòng)態(tài)插入代碼

Javassist是一個(gè)動(dòng)態(tài)類庫,可以用來檢查口渔、”動(dòng)態(tài)”修改以及創(chuàng)建 Java類样屠。其功能與jdk自帶的反射功能類似,但比反射功能更強(qiáng)大
ClassPool:javassist的類池缺脉,使用ClassPool 類可以跟蹤和控制所操作的類,它的工作方式與 JVM 類裝載器非常相似痪欲,
CtClass: CtClass提供了檢查類數(shù)據(jù)(如字段和方法)以及在類中添加新字段、方法和構(gòu)造函數(shù)攻礼、以及改變類业踢、父類和接口的方法。不過礁扮,Javassist 并未提供刪除類中字段知举、方法或者構(gòu)造函數(shù)的任何方法瞬沦。
CtField:用來訪問域
CtMethod :用來訪問方法
CtConstructor:用來訪問構(gòu)造器

新建
CodeInjects.groovy 用于想MainActivity中動(dòng)態(tài)插入代碼

package com.feifei.second.codeinject

import javassist.ClassPool
import javassist.CtClass
import javassist.CtMethod
import org.gradle.api.Project

public class CodeInjects {
    private final static ClassPool pool =  ClassPool.getDefault();

    public static void inject(String path, Project project){

        //當(dāng)前路徑加入類池,不然找不到這個(gè)類
        pool.appendClassPath(path)

        //project.android.bootClasspath 加入android.jar雇锡,不然找不到android相關(guān)的所有類
        pool.appendClassPath(project.android.bootClasspath[0].toString())

        pool.importPackage("android.os.Bundle");
        pool.importPackage(" android.app.Activity")

        File dir = new File(path)
        if(dir.isDirectory()){
            //遍歷目錄
            dir.eachFileRecurse {File file->
                String filePath = file.absolutePath
                println("CodeInjects filePath:"+filePath)
                if(file.getName().equals("MainActivity.class")){

                    //獲取MainActivity.class
                    CtClass ctClass = pool.getCtClass("com.sogou.teemo.test_use_gradle_plugin.MainActivity");
                    println("CodeInjects ctClass = "+ctClass)

                    if(ctClass.isFrozen()){
                        ctClass.defrost()
                    }

                    //獲取到onCreate方法
                    CtMethod ctMethod = ctClass.getDeclaredMethod("onCreate");
                    println("CodeInjects 方法名 = " + ctMethod)

                    String insetBeforeStr = """ android.widget.Toast.makeText(this,"插件中自動(dòng)生成的代碼",android.widget.Toast.LENGTH_SHORT).show();
                                            """

                    ctMethod.insertAfter(insetBeforeStr)

                    ctClass.writeFile(path)

                    ctClass.detach()//釋放

                }
            }
        }


    }


}

ReClassTransform.groovy中逛钻,遍歷class文件時(shí),調(diào)用CodeInjects.inject(directoryInput.file.absolutePath,mProject)锰提。過濾出MainActivity.class并動(dòng)態(tài)修改onCreate()方法

  /**
     * Transform中的核心方法曙痘,
     *
     * @param context 。
     * @param inputs  傳過來的輸入流, 其中有兩種格式立肘,一種是jar包格式一種是目錄格式
     * @param referencedInputs
     * @param outputProvider  獲取到輸出目錄边坤,最后將修改的文件復(fù)制到輸出目錄,這一步必須做不然編譯會(huì)報(bào)錯(cuò)
     * @param isInCremental
     * @throws IOException
     * @throws TransformException
     */
    @Override
    public void transform(Context context,
                          Collection<TransformInput> inputs,
            Collection<TransformInput> referencedInputs,
            TransformOutputProvider outputProvider,
            boolean isInCremental
    ) throws IOException, TransformException{

        welecome()

        inputs.each { TransformInput input->

            //遍歷目錄
            input.directoryInputs.each { DirectoryInput directoryInput ->

                println "direction = "+directoryInput.file.getAbsolutePath()

                CodeInjects.inject(directoryInput.file.absolutePath,mProject)
                //獲取輸出目錄
                def dest = outputProvider.getContentLocation(directoryInput.name,directoryInput.contentTypes,directoryInput.scopes,Format.DIRECTORY)

                //對于目錄中的class文件原樣輸出
                FileUtils.copyDirectory(directoryInput.file,dest)
            }

            //遍歷jar文件,對jar不操作谅年,但是要輸出到out目錄
            input.jarInputs.each { JarInput jarInput->

                // 將jar文件 重命名輸出文件(同目錄copyFile會(huì)沖突)
                def jarName = jarInput.name
                println "jar = "+jarInput.file.getAbsolutePath()

                def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
                if(jarName.endsWith(".jar")){
                    jarName = jarName.substring(0,jarName.length()-4)
                }
                def dest = outputProvider.getContentLocation(jarName+md5Name,jarInput.contentTypes, jarInput.scopes, Format.JAR)
                FileUtils.copyFile(jarInput.file, dest)
            }

        }

        end()

    }

將test_gradle_use_plugin-debug.apk 反編譯后,如下圖所示:


image.png

Github: 查看buildSrc 和test_gradle_use_plugin 兩個(gè)module

四茧痒、相關(guān)知識背景

1、Transfrom API

基于Gradle的Transform API融蹂,在編譯期的構(gòu)建任務(wù)流中旺订,class轉(zhuǎn)為dex之前,插入一個(gè)Transform超燃,并在此Transform流中耸峭,基于Javassist實(shí)現(xiàn)對字節(jié)碼文件的注入。
[圖片上傳失敗...(image-317838-1563938953106)]
http://google.github.io/android-gradle-dsl/javadoc/current/

2淋纲、javassist

Javassist是一個(gè)動(dòng)態(tài)類庫,可以用來檢查院究、”動(dòng)態(tài)”修改以及創(chuàng)建 Java類.其功能與jdk自帶的反射功能類似洽瞬,但比反射功能更強(qiáng)大.

  • ClassPool:javassist的類池,使用ClassPool 類可以跟蹤和控制所操作的類,它的工作方式與 JVM 類裝載器非常相似业汰。
  • CtClass: CtClass提供了檢查類數(shù)據(jù)(如字段和方法)以及在類中添加新字段伙窃、方法和構(gòu)造函數(shù)、以及改變類样漆、父類和接口的方法为障。不過,Javassist 并未提供刪除類中字段放祟、方法或者構(gòu)造函數(shù)的任何方法鳍怨。
  • CtField:用來訪問域
  • CtMethod :用來訪問方法
  • CtConstructor:用來訪問構(gòu)造器
  • insertClassPath:為ClassPool添加搜索路徑,否則ClassPool 無法找打?qū)?yīng)的類
     classPool.insertClassPath(new ClassClassPath(String.class));
            classPool.insertClassPath(new ClassClassPath(Person.class));
            classPool.insertClassPath("/Users/feifei/Desktop/1");
  • classPool.get(className);加載一個(gè)類

  • classPool.makeClass(className);//創(chuàng)建一個(gè)類

  • CtClass.addField();CtClass.addMethod(); 添加方法和屬性

  CtField ageField = new CtField(CtClass.intType,"age",stuClass);
            stuClass.addField(ageField);
    CtMethod setMethod = CtMethod.make("public void setAge(int age) { this.age = age;}",stuClass);

            stuClass.addMethod(getMethod);
  • Class<?>clazz = stuClass.toClass();將CtCLass對象轉(zhuǎn)化為JVM對象

創(chuàng)建一個(gè)類跪妥,并寫入到本地文件

 public static void testCreateClass(){

        System.out.println("testCreateClass");
        //創(chuàng)建ClassPool
        ClassPool classPool = ClassPool.getDefault();

        //添加類路徑
//        classPool.insertClassPath(new ClassClassPath(this.getClass()));
        classPool.insertClassPath(new ClassClassPath(String.class));
        //創(chuàng)建類
        CtClass stuClass = classPool.makeClass("com.feifei.Student");

        //加載類
        //classPool.get(className)
        try {
            //添加屬性
            CtField idField = new CtField(CtClass.longType,"id",stuClass);
            stuClass.addField(idField);

            CtField nameField = new CtField(classPool.get("java.lang.String"),"name",stuClass);
            stuClass.addField(nameField);

            CtField ageField = new CtField(CtClass.intType,"age",stuClass);
            stuClass.addField(ageField);


            //添加方法
            CtMethod getMethod = CtMethod.make("public int getAge(){return this.age;}",stuClass);
            CtMethod setMethod = CtMethod.make("public void setAge(int age) { this.age = age;}",stuClass);

            stuClass.addMethod(getMethod);
            stuClass.addMethod(setMethod);

            //toClass 將CtClass 轉(zhuǎn)換為java.lang.class
            Class<?>clazz = stuClass.toClass();
            System.out.println("testCreateClass clazz:"+clazz);

            System.out.println("testCreateClas ------ 屬性列表 -----");
            Field[] fields = clazz.getDeclaredFields();
            for(Field field:fields){
                System.out.println("testCreateClass"+field.getType()+"\t"+field.getName());
            }

            System.out.println("testCreateClass ------ 方法列表 -----");

            Method[] methods = clazz.getDeclaredMethods();
            for(Method method:methods){
                System.out.println("feifei  "+method.getReturnType()+"\t"+method.getName()+"\t"+ Arrays.toString(method.getParameterTypes()));
            }

            stuClass.writeFile("/Users/feifei/Desktop/1");
        } catch (CannotCompileException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }finally {

            //將stuClass 從ClassPool 移除
            if(stuClass != null){
                stuClass.detach();
            }
        }

    }

修改一個(gè)類的父類


package com.example.myjavassist;

public class Person {
}

public static void testSetSuperClass(){

        System.out.println("testSetSuperClass");
        //創(chuàng)建ClassPool
        ClassPool classPool = ClassPool.getDefault();


        try {
            //添加類路徑
            classPool.insertClassPath(new ClassClassPath(String.class));
            classPool.insertClassPath(new ClassClassPath(Person.class));
            classPool.insertClassPath("/Users/feifei/Desktop/1");

            // 加載類
            //創(chuàng)建類
            CtClass stuClass = classPool.get("com.feifei.Student");
            CtClass personClass = classPool.get("com.example.myjavassist.Person");

            if(stuClass.isFrozen()){
                stuClass.freeze();
            }
            stuClass.setSuperclass(personClass);

            //toClass 將CtClass 轉(zhuǎn)換為java.lang.class
            Class<?>clazz = stuClass.toClass();
            System.out.println("testSetSuperClass ------ 屬性列表 -----");
            Field[] fields = clazz.getDeclaredFields();
            for(Field field:fields){
                System.out.println("testCreateClass"+field.getType()+"\t"+field.getName());
            }

            System.out.println("testSetSuperClass ------ 方法列表 -----");

            Method[] methods = clazz.getDeclaredMethods();
            for(Method method:methods){
                System.out.println("testSetSuperClass  "+method.getReturnType()+"\t"+method.getName()+"\t"+ Arrays.toString(method.getParameterTypes()));
            }

            stuClass.writeFile("/Users/feifei/Desktop/1");
            personClass.writeFile("/Users/feifei/Desktop/1");

        } catch (NotFoundException | CannotCompileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
    }
image.png

方法重命名鞋喇、復(fù)制方法、新建方法眉撵,添加方法體侦香。


package com.example.myjavassist;

public class Calculator {

    public void getSum(long n) {
        long sum = 0;
        for (int i = 0; i < n; i++) {
            sum += i;
        }
        System.out.println("n="+n+",sum="+sum);
    }

}


 public static void testInsertMethod(){

        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = null;
        try {
            ctClass = pool.get("com.example.myjavassist.Calculator");

            //獲取類中現(xiàn)有的方法
            String getSumName = "getSum";
            CtMethod methodOld = ctClass.getDeclaredMethod(getSumName);


            String methodNewName = getSumName+"$impl";
            //修改原有方法的方法名
            methodOld.setName(methodNewName);


            //創(chuàng)建一個(gè)新的方法getSumName,并將舊方法 復(fù)制成新方法中.
            CtMethod newMethod = CtNewMethod.copy(methodOld,getSumName,ctClass,null);

            //設(shè)置新newMethod的方法體
            StringBuffer body = new StringBuffer();
            body.append("{\nlong start = System.currentTimeMillis();\n");
            // 調(diào)用原有代碼落塑,類似于method();($$)表示所有的參數(shù)
            body.append(methodNewName + "($$);\n");
            body.append("System.out.println(\"Call to method " + methodNewName
                    + " took \" +\n (System.currentTimeMillis()-start) + " + "\" ms.\");\n");
            body.append("}");

            newMethod.setBody(body.toString());

            //為類新添加方法
            ctClass.addMethod(newMethod);

            Calculator calculator =(Calculator)ctClass.toClass().newInstance();
            calculator.getSum(10000);

            //將類輸出到文件
            ctClass.writeFile("/Users/feifei/Desktop/1");

        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (CannotCompileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            if(ctClass!=null){
                ctClass.detach();
            }
        }
    }
image.png

Github: 選擇 myjavaassit module

五、參考文章

http://www.reibang.com/p/a6be7cdcfc65

http://www.reibang.com/p/a9b3aaba8e45

https://blog.csdn.net/top_code/article/details/51708043

http://www.javassist.org/tutorial/tutorial2.html

javassit github:
https://github.com/jboss-javassist/javassist

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末罐韩,一起剝皮案震驚了整個(gè)濱河市憾赁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌散吵,老刑警劉巖龙考,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異错蝴,居然都是意外死亡洲愤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門顷锰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柬赐,“玉大人,你說我怎么就攤上這事官紫「厮危” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵束世,是天一觀的道長酝陈。 經(jīng)常有香客問我,道長毁涉,這世上最難降的妖魔是什么沉帮? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮贫堰,結(jié)果婚禮上穆壕,老公的妹妹穿的比我還像新娘。我一直安慰自己其屏,他們只是感情好喇勋,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著偎行,像睡著了一般川背。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛤袒,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天熄云,我揣著相機(jī)與錄音,去河邊找鬼妙真。 笑死皱碘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隐孽。 我是一名探鬼主播癌椿,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼健蕊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了踢俄?” 一聲冷哼從身側(cè)響起缩功,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎都办,沒想到半個(gè)月后嫡锌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琳钉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年势木,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歌懒。...
    茶點(diǎn)故事閱讀 40,861評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啦桌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出及皂,到底是詐尸還是另有隱情甫男,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布验烧,位于F島的核電站板驳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碍拆。R本人自食惡果不足惜若治,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望感混。 院中可真熱鬧直砂,春花似錦、人聲如沸浩习。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谱秽。三九已至,卻和暖如春摹迷,著一層夾襖步出監(jiān)牢的瞬間疟赊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工峡碉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留近哟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓鲫寄,卻偏偏與公主長得像吉执,于是被迫代替她去往敵國和親疯淫。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評論 2 361

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