近期项目中需要对​​SpringMVC​​​中的​​Controller​​​方法进行拦截做预处理,才接触到​​javaagent​​,仅作记录。

思路:

1.声明​​MyTransformer​​​类,实现​​ClassFileTransformer​​​接口,该接口只有一个方法:​​byte[] transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException;​​​在该方法中获取指定类的指定方法,修改其字节码,达到拦截的目的;如果需要修改方法字节码,则需要引入​​javassist-*.*.*-GA.jar​​的包。

javaagent项目中使用_jarjavaagent项目中使用_java_02

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 }

View Code

2.然后声明​​MyAgent​​类,实现方法:

javaagent项目中使用_jarjavaagent项目中使用_java_02

1      public static void premain(String args, Instrumentation inst){
2 inst.addTransformer(new MyTransformer());
3 }

View Code

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​​​操作,参见

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​​方法。