由於個人工作原因,近期遇到多起因應用性能導致業務中斷的事情。多次排查分析總結,發現是應用性能問題,當然性能的提現是多維度的,在這里就不贅述了。
主要關注在應用運行中斷之前就發現它(事前處理),是很重要的。
要監控應用的性能,首先列出性能監控點,然后輸出要關注的信息,最終根據信息進行數據分析得出性能瓶頸后進行持續優化改進,在問題爆發前將其扼殺在“子宮”里。
不同應用、不同場景下,監控點不盡相同,要關注的信息如何獲取卻是每個工程師都要思考的問題。
在接觸javassist之前,有過幾個方案,但發布了幾版后發現實現方式太low、成本高、效率低等不足,其中包括:代碼中嵌入日志、使用spring管理應用並使用aop、修改jar包源代碼增加日志。
如果是一個新搭建的工程,以上方案可以在框架搭建過程中便包含進去,作為框架的基礎能力隨同應用一起發布。但是平台上應用很多,明顯不是很適用,並且有些操作所帶來的風險需要更多的工作量去規避。
於是,找到了她------javassist。
javassist的使用要借助於javaagent技術,接下來介紹如何使用javassist
1、獲取javassit-3.20.0-GA.jar
2、創建類AgentTransformer並實現ClassFileTransformer接口,使用javassist API完成對源類字節碼級別的修改
3、創建類AgentDemo,並增加premain實現public static void premain(String args, Instrumentation inst){ inst.addTransformer(new AgentTransformer()); }
4、創建MAINFEST.MF文件,內容如下:
Manifest-Version: 1.0
Premain-Class: AgentDemo
Can-Redefine-Classes: true
Can-Retransform-Classes: true
5、打jar包:main class指定使用MAINFEST.MF文件
6、實際應用--創建demo應用,引入兩個jar包:javassist-3.20.0-GA.jar和javassistdemo.jar(上面打包出來的)
7、修改java啟動參數
8、運行應用程序,觀察結果
System.out.println("This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1");
System.out.println("This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1");
This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1
This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1
System.out.println("This code is inserted before constructor com/hope/javassistapp/app/App");
System.out.println("This code is inserted after constructor com/hope/javassistapp/app/App");
System.out.println("This code is inserted before constructor sun/misc/Cleaner");
System.out.println("This code is inserted after constructor sun/misc/Cleaner");
System.out.println("This code is inserted before constructor java/lang/Enum");
System.out.println("This code is inserted after constructor java/lang/Enum");
This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1
This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1
System.out.println("This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo1");
System.out.println("This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo1");
This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo1
JavassistDemo1:自身構造函數輸出內容
This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo1
This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1
This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1
System.out.println("This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo2");
System.out.println("This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo2");
This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo2
JavassistDemo2:自身構造函數輸出內容
This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo2
System.out.println("This code is inserted before constructor java/lang/Shutdown");
System.out.println("This code is inserted after constructor java/lang/Shutdown");
System.out.println("This code is inserted before constructor java/lang/Shutdown$Lock");
System.out.println("This code is inserted after constructor java/lang/Shutdown$Lock");
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
輸出結果中,黃色高亮部分是動態增加的代碼造成的效果。
AgentDemo和AgentTransformer是agent工程下的,用於動態修改類使用。

package com.hope.agent; import java.lang.instrument.Instrumentation; import com.hope.transform.AgentTransformer; /** * java agent 入口 * @author hp * */ public class AgentDemo { public static void premain(String args, Instrumentation inst) { inst.addTransformer(new AgentTransformer()); } }

package com.hope.transform; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.LoaderClassPath; /** * 對類字節碼轉譯 * @author hp * */ public class AgentTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { /** * 此處使用javassist API對classfileBuffer進行修改 */ ClassPool pool = new ClassPool(true); pool.appendClassPath(new LoaderClassPath(loader)); try { CtClass cls = pool.makeClass(new ByteArrayInputStream(classfileBuffer)); //獲取構造函數數組 CtConstructor[] ccs = cls.getDeclaredConstructors(); //構造函數方法體開始時添加的代碼 String codeStrBefore = "System.out.println(\"This code is inserted before constructor "+className+"\");"; System.out.println(codeStrBefore); //構造函數方法體結束前添加的代碼 String codeStrAfter = "System.out.println(\"This code is inserted after constructor "+className+"\");"; System.out.println(codeStrAfter); for (CtConstructor cc : ccs) { cc.insertBefore(codeStrBefore); cc.insertAfter(codeStrAfter, true); } return cls.toBytecode(); } catch (IOException e) { e.printStackTrace(); } catch (RuntimeException e) { e.printStackTrace(); } catch (CannotCompileException e) { e.printStackTrace(); } return null; } }
App和JavassistDemo1、JavassistDemo2用於demo演示使用

package com.hope.javassistapp.app; import com.hope.javassistapp.construct.JavassistDemo1; import com.hope.javassistapp.construct.JavassistDemo2; public class App { /** * @param args */ public static void main(String[] args) { JavassistDemo1 d1 = new JavassistDemo1(); JavassistDemo2 d2 = new JavassistDemo2(); } }

package com.hope.javassistapp.construct; public class JavassistDemo1 { public JavassistDemo1() { System.out.println("JavassistDemo1:自身構造函數輸出內容"); } }

package com.hope.javassistapp.construct; public class JavassistDemo2 { public JavassistDemo2() { System.out.println("JavassistDemo2:自身構造函數輸出內容"); } }
對於javassist目前探索到這里,日后還會繼續加深對其理解和使用,敬請期待。
歡迎業內人士交流經驗。