一、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處理
二、自定義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
生成類文件的位置:
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 反編譯后,如下圖所示:
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 {
}
}
方法重命名鞋喇、復(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();
}
}
}
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