相關代碼參考:http://blog.csdn.net/catoop/article/details/51034778
近期項目中需要對SpringMVC
中的Controller
方法進行攔截做預處理,才接觸到javaagent
,僅作記錄。
思路:
1.聲明MyTransformer
類,實現ClassFileTransformer
接口,該接口只有一個方法:byte[] transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException;
在該方法中獲取指定類的指定方法,修改其字節碼,達到攔截的目的;如果需要修改方法字節碼,則需要引入javassist-*.*.*-GA.jar
的包。

1 import java.lang.instrument.ClassFileTransformer; 2 import java.lang.instrument.IllegalClassFormatException; 3 import java.security.ProtectionDomain; 4 import java.util.ArrayList; 5 import java.util.HashMap; 6 import java.util.List; 7 import java.util.Map; 8 import javassist.ClassPool; 9 import javassist.CtClass; 10 import javassist.CtMethod; 11 import javassist.CtNewMethod; 12
13 public class MyTransformer implements ClassFileTransformer { 14
15 final static String prefix = "\nlong startTime = System.currentTimeMillis();\n"; 16 final static String postfix = "\nlong endTime = System.currentTimeMillis();\n"; 17
18 // 被處理的方法列表
19 final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>(); 20
21 public MyTransformer() { 22 add("com.shanhy.demo.TimeTest.sayHello"); 23 add("com.shanhy.demo.TimeTest.sayHello2"); 24 } 25
26 private void add(String methodString) { 27 String className = methodString.substring(0, methodString.lastIndexOf(".")); 28 String methodName = methodString.substring(methodString.lastIndexOf(".") + 1); 29 List<String> list = methodMap.get(className); 30 if (list == null) { 31 list = new ArrayList<String>(); 32 methodMap.put(className, list); 33 } 34 list.add(methodName); 35 } 36
37 @Override 38 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 39 ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 40 className = className.replace("/", "."); 41 if (methodMap.containsKey(className)) {// 判斷加載的class的包路徑是不是需要監控的類
42 CtClass ctclass = null; 43 try { 44 ctclass = ClassPool.getDefault().get(className);// 使用全稱,用於取得字節碼類<使用javassist>
45 for (String methodName : methodMap.get(className)) { 46 String outputStr = "\nSystem.out.println(\"this method " + methodName 47 + " cost:\" +(endTime - startTime) +\"ms.\");"; 48 CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到這方法實例
49 String newMethodName = methodName + "$old";// 新定義一個方法叫做比如sayHello$old
50 ctmethod.setName(newMethodName);// 將原來的方法名字修改 51 // 創建新的方法,復制原來的方法,名字為原來的名字
52 CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null); 53 // 構建新的方法體
54 StringBuilder bodyStr = new StringBuilder(); 55 bodyStr.append("{"); 56 bodyStr.append(prefix); 57 bodyStr.append(newMethodName + "($$);\n");// 調用原有代碼,類似於method();($$)表示所有的參數
58 bodyStr.append(postfix); 59 bodyStr.append(outputStr); 60 bodyStr.append("}"); 61
62 newMethod.setBody(bodyStr.toString());// 替換新方法
63 ctclass.addMethod(newMethod);// 增加新方法
64 } 65 return ctclass.toBytecode(); 66 } catch (Exception e) { 67 System.out.println(e.getMessage()); 68 e.printStackTrace(); 69 } 70 } 71 return null; 72 } 73 }
2.然后聲明MyAgent
類,實現方法:

1 public static void premain(String args, Instrumentation inst){ 2 inst.addTransformer(new MyTransformer()); 3 }
3.將工程打成jar
包,例如Myagent.jar
,需要修改MANIFEST.MF
內容,添加
1 Premain-Class: com.test.demo.agent.MyAgent
4.使用時只需要java -javaagent:D:/Myagent.jar -jar MyWebApp.jar
就可攔截transform
邏輯中想要的方法。-javaagent:D:/*.jar
可以使用多個,放到-jar *.jar
前面即可。
5.實際項目中,在使用maven
工程編譯jar
包install
后,執行java -javaagent:D:/Myagent.jar -jar MyWebApp.jar
中文字符亂碼,導致啟動異常.
解決辦法
:在啟動命令中添加-Dfile.encoding=utf-8
,如下: java -Dfile.encoding=utf-8 -javaagent:D:/Myagent.jar -jar MyWebApp.jar
程序正常運行。
6.實現transform
方式時,遇到一個問題,在手動修改method
的body
時,例如newMethod.setBody(bodyStr.toString());
,bodyStr
為處理后的方法體,簡單的語句,如:System.out.println("message")
是可以的,但是復雜的邏輯不行,程序運行沒有反映,控制台也沒有異常打印。后來發現需要寫對象的全路徑,比如List
需要寫成java.util.List
等。其中涉及javassist
操作,參見http://blog.csdn.net/u011425751/article/details/51917895
7.遺留問題: SpringMVC
的org.springframework.web.servlet.DispatcherServlet
繼承了抽象類FrameworkServlet
,FrameworkServlet
繼承了抽象類HttpServletBean
,HttpServletBean
繼承抽象類HttpServlet
,HttpServlet
繼承了抽象類GenericServlet
,GenericServlet
實現了Servlet
和ServletConfig
接口。
具體如下:
1 public class DispatcherServlet extends FrameworkServlet 2 public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware 3 public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware 4 public abstract class HttpServlet extends GenericServlet 5 public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable 6 public interface Servlet
項目中攔截DispatcherServlet
的doService
方法。