使用java代理来实现java字节码注入
使用JavaSsist可以对字节码进行修改
使用ASM可以修改字节码

使用Java代理和ASM字节码技术开发java探针工具可以修改字节码

备注:javassist是一个库,实现ClassFileTransformer接口中的transform()方法。ClassFileTransformer 这个接口的目的就是在class被装载到JVM之前将class字节码转换掉,从而达到动态注入代码的目的。

备注:ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。


详细总结

1 使用Java代理和Java字节码注入技术

开发java探针工具来分析复杂的接口方法,无需修改代码,简单部署就可以实时抓取代码的运行轨迹和方法耗时。

2 基于Java代理和Java字节码注入技术的java探针工具技术原理

 
动态代理功能实现说明

我们利用Java代理和ASM字节码技术开发java探针工具,实现原理如下:

jdk1.5以后引入了Java代理技术,Java代理是运行方法之前的拦截器。我们利用Java代理和ASM字节码技术,在JVM加载class二进制文件的时候,利用ASM动态的修改加载的class文件,在监控的方法前后添加计时器功能,用于计算监控方法耗时,同时将方法耗时及内部调用情况放入处理器,处理器利用栈先进后出的特点对方法调用先后顺序做处理,当一个请求处理结束后,将耗时方法轨迹和入参map输出到文件中,然后根据map中相应参数或耗时方法轨迹中的关键代码区分出我们要抓取的耗时业务。最后将相应耗时轨迹文件取下来,转化为xml格式并进行解析,通过浏览器将代码分层结构展示出来,方便耗时分析,如图所示:

 
java探针工具原理图

1:在JVM加载class二进制文件的时候,利用ASM动态的修改加载的class文件,在监控的方法前后添加计时器功能,用于计算监控方法耗时;
2:将监控的相关方法 和 耗时及内部调用情况,按照顺序放入处理器;
3:处理器利用栈先进后出的特点对方法调用先后顺序做处理,当一个请求处理结束后,将耗时方法轨迹入参map输出到文件中;
4:然后区分出耗时的业务,转化为xml格式进行解析和分析。

Java探针工具功能点:

1、支持方法执行耗时范围抓取设置,根据耗时范围抓取系统运行时出现在设置耗时范围的代码运行轨迹。

2、支持抓取特定的代码配置,方便对配置的特定方法进行抓取,过滤出关系的代码执行耗时情况。

3、支持APP层入口方法过滤,配置入口运行前的方法进行监控,相当于监控特有的方法耗时,进行方法专题分析。

4、支持入口方法参数输出功能,方便跟踪耗时高的时候对应的入参数。

5、提供WEB页面展示接口耗时展示、代码调用关系图展示、方法耗时百分比展示、可疑方法凸显功能。


2.1 Java代理小例子

参考:基于 JavaAgent 的应用实例

JavaAgent 是运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法。
那么如何实现一个 Java代理呢?很简单,只需要增加 premain 方法即可。

源码:JavaAgent

 
目录结构

Test1.pre_MyProgram.java

  1. package agent;
  2. import java.lang.instrument.Instrumentation;
  3. public class pre_MyProgram
  4. {
  5. /**
  6. * 该方法在main方法之前运行,与main方法运行在同一个JVM中 并被同一个System ClassLoader装载
  7. * 被统一的安全策略(security policy)和上下文(context)管理
  8. */
  9. public static void premain(String agentOps, Instrumentation inst)
  10. {
  11. System.out.println("====premain 方法执行");
  12. System.out.println(agentOps);
  13. }
  14. /**
  15. * 如果不存在 premain(String agentOps, Instrumentation inst) 则会执行 premain(String
  16. * agentOps)
  17. */
  18. public static void premain(String agentOps)
  19. {
  20. System.out.println("====premain方法执行2====");
  21. System.out.println(agentOps);
  22. }
  23. public static void main(String[] args)
  24. {
  25. }
  26. }

在 src 目录下添加 META-INF/MANIFEST.MF 文件

  1. Manifest-Version: 1.0
  2. Premain-Class: agent.pre_MyProgram
  3. Can-Redefine-Classes: true

打包代码为 pre_MyProgram.jar;注意打包的时候选择我们自己定义的 MANIFEST.MF

Test2.MyProgram.java

  1. package alibaba;
  2. public class MyProgram
  3. {
  4. public static void main(String[] args)
  5. {
  6. System.out.println("====MyProgram====");
  7. }
  8. }
  1. Manifest-Version: 1.0
  2. Main-Class: alibaba.MyProgram

导出main的jar包命名为:MyProgram.jar

 
结果

命令中的Hello1为我们传递给 premain 方法的字符串参数,这就是一个简单的Java代理

2.2 基于 JavaAgent 的应用实例

JDK5中只能通过命令行参数在启动JVM时指定javaagent参数来设置代理类,而JDK6中已经不仅限于在启动JVM时通过配置参数来设置代理类,JDK6中通过 Java Tool API 中的 attach 方式,我们也可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。

Instrumentation 的最大作用,就是类定义动态改变和操作。

最简单的一个例子,计算某个方法执行需要的时间,不修改源代码的方式,使用Instrumentation 代理来实现这个功能,给力的说,这种方式相当于在JVM级别做了AOP支持,这样我们可以在不修改应用程序的基础上就做到了AOP,是不是显得略吊。

创建一个 ClassFileTransformer 接口的实现类 MyTransformer实现 ClassFileTransformer 这个接口的目的就是在class被装载到JVM之前将class字节码转换掉,从而达到动态注入代码的目的。那么首先要了解MonitorTransformer 这个类的目的,就是对想要修改的类做一次转换,这个用到了javassist对字节码进行修改,可以暂时不用关心jaavssist的原理,用ASM同样可以修改字节码,只不过比较麻烦些。

 
项目结构

源码:JavaAgent

MyAgent.MyAgent.java

  1. package agent;
  2. import java.lang.instrument.Instrumentation;
  3. public class MyAgent
  4. {
  5. /**
  6. * 该方法在main方法之前运行,与main方法运行在同一个JVM中 并被同一个System ClassLoader装载
  7. * 被统一的安全策略(security policy)和上下文(context)管理
  8. */
  9. public static void premain(String agentOps, Instrumentation inst)
  10. {
  11. System.out.println("=========premain方法执行========");
  12. System.out.println(agentOps);
  13. // 添加Transformer
  14. inst.addTransformer(new MyTransformer());
  15. }
  16. /**
  17. * 如果不存在 premain(String agentOps, Instrumentation inst) 则会执行 premain(String
  18. * agentOps)
  19. */
  20. public static void premain(String agentOps)
  21. {
  22. System.out.println("====premain方法执行2====");
  23. System.out.println(agentOps);
  24. }
  25. public static void main(String[] args)
  26. {
  27. }
  28. }

MyAgent.MyTransformer

  1. package agent;
  2. import java.lang.instrument.ClassFileTransformer;
  3. import java.lang.instrument.IllegalClassFormatException;
  4. import java.security.ProtectionDomain;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. import javassist.ClassPool;
  10. import javassist.CtClass;
  11. import javassist.CtMethod;
  12. import javassist.CtNewMethod;
  13. /**
  14. * 检测方法的执行时间
  15. */
  16. public class MyTransformer implements ClassFileTransformer
  17. {
  18. final static String prefix = "\nlong startTime = System.currentTimeMillis();\n";
  19. final static String postfix = "\nlong endTime = System.currentTimeMillis();\n";
  20. // 被处理的方法列表
  21. final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>();
  22. public MyTransformer()
  23. {
  24. add("com.shanhy.demo.TimeTest.sayHello");
  25. add("com.shanhy.demo.TimeTest.sayHello2");
  26. }
  27. private void add(String methodString)
  28. {
  29. String className = methodString.substring(0, methodString.lastIndexOf("."));
  30. String methodName = methodString.substring(methodString.lastIndexOf(".") + 1);
  31. List<String> list = methodMap.get(className);
  32. if (list == null)
  33. {
  34. list = new ArrayList<String>();
  35. methodMap.put(className, list);
  36. }
  37. list.add(methodName);
  38. }
  39. // 重写此方法
  40. @Override
  41. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
  42. ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
  43. {
  44. className = className.replace("/", ".");
  45. if (methodMap.containsKey(className))
  46. {
  47. // 判断加载的class的包路径是不是需要监控的类
  48. CtClass ctclass = null;
  49. try
  50. {
  51. ctclass = ClassPool.getDefault().get(className);// 使用全称,用于取得字节码类<使用javassist>
  52. for (String methodName : methodMap.get(className))
  53. {
  54. String outputStr = "\nSystem.out.println(\"this method " + methodName
  55. + " cost:\" +(endTime - startTime) +\"ms.\");";
  56. CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到这方法实例
  57. String newMethodName = methodName + "$old";// 新定义一个方法叫做比如sayHello$old
  58. ctmethod.setName(newMethodName);// 将原来的方法名字修改
  59. // 创建新的方法,复制原来的方法,名字为原来的名字
  60. CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null);
  61. // 构建新的方法体
  62. StringBuilder bodyStr = new StringBuilder();
  63. bodyStr.append("{");
  64. bodyStr.append(prefix);
  65. bodyStr.append(newMethodName + "($$);\n");// 调用原有代码,类似于method();($$)表示所有的参数
  66. bodyStr.append(postfix);
  67. bodyStr.append(outputStr);
  68. bodyStr.append("}");
  69. newMethod.setBody(bodyStr.toString());// 替换新方法
  70. ctclass.addMethod(newMethod);// 增加新方法
  71. System.err.println(outputStr);
  72. }
  73. return ctclass.toBytecode();
  74. }
  75. catch (Exception e)
  76. {
  77. System.out.println(e.getMessage());
  78. e.printStackTrace();
  79. }
  80. }
  81. return null;
  82. }
  83. }

META-INF/MANIFEST.MF

  1. Manifest-Version: 1.0
  2. Premain-Class: agent.MyAgent
  3. Can-Redefine-Classes: true
  4. Boot-Class-Path: javassist-3.12.1-GA.jar

  1. package alibaba;
  2. public class TimeTest
  3. {
  4. public static void main(String[] args)
  5. {
  6. System.err.println("======TimeTest执行========");
  7. sayHello();
  8. sayHello2("hello world222222222");
  9. }
  10. public static void sayHello()
  11. {
  12. try
  13. {
  14. Thread.sleep(2000);
  15. System.out.println("hello world!!");
  16. }
  17. catch (InterruptedException e)
  18. {
  19. e.printStackTrace();
  20. }
  21. }
  22. public static void sayHello2(String hello)
  23. {
  24. try
  25. {
  26. Thread.sleep(1000);
  27. System.out.println(hello);
  28. }
  29. catch (InterruptedException e)
  30. {
  31. e.printStackTrace();
  32. }
  33. }
  34. }

META-INF/MANIFEST.MF

  1. Manifest-Version: 1.0
  2. Main-Class: alibaba.TimeTest

结果:

 
结果

3 使用 spring-loaded 实现 jar 包热部署

在项目开发中我们可以把一些重要但又可能会变更的逻辑封装到某个 logic.jar 中,当我们需要随时更新实现逻辑的时候,可以在不重启服务的情况下让修改后的 logic.jar 被重新加载生效。

spring-loaded是一个开源项目,项目地址:https://github.com/spring-projects/spring-loaded

使用方法:

  1. 在启动主程序之前指定参数
  2. -javaagent:C:/springloaded-1.2.5.RELEASE.jar -noverify
  3. 123

如果你想让 Tomat 下面的应用自动热部署,只需要在 catalina.sh 中添加:

  1. set JAVA_OPTS=-javaagent:springloaded-1.2.5.RELEASE.jar -noverify1

这样就完成了 spring-loaded 的安装,它能够自动检测Tomcat 下部署的webapps ,在不重启Tomcat的情况下,实现应用的热部署。

通过使用 -noverify 参数,关闭 Java 字节码的校验功能。
使用参数 -Dspringloaded=verbose;explain;watchJars=tools.jar 指定监视的jar (verbose;explain; 非必须),多个jar用“冒号”分隔,如 watchJars=tools.jar:utils.jar:commons.jar

当然,它也有一些小缺限:

  1. 目前官方提供的1.2.4 版本在linux上可以很好的运行,但在windows还存在bug,官网已经有人提出:https://github.com/spring-projects/spring-loaded/issues/145
  2. 对于一些第三方框架的注解的修改,不能自动加载,比如:spring mvc的@RequestMapping
  3. log4j的配置文件的修改不能即时生效。

======================================================================================================

20190110根据网络资料整理汇总

======================================================================================================

基于javaAgent和Java字节码注入技术的java探针工具技术原理

图0-0:动态代理功能实现说明

我们利用javaAgent和ASM字节码技术开发java探针工具,实现原理如下:

jdk1.5以后引入了javaAgent技术,javaAgent是运行方法之前的拦截器。我们利用javaAgent和ASM字节码技术,在JVM加载class二进制文件的时候,利用ASM动态的修改加载的class文件,在监控的方法前后添加计时器功能,用于计算监控方法耗时,同时将方法耗时及内部调用情况放入处理器,处理器利用栈先进后出的特点对方法调用先后顺序做处理,当一个请求处理结束后,将耗时方法轨迹和入参map输出到文件中,然后根据map中相应参数或耗时方法轨迹中的关键代码区分出我们要抓取的耗时业务。最后将相应耗时轨迹文件取下来,转化为xml格式并进行解析,通过浏览器将代码分层结构展示出来,方便耗时分析,如图0-1所示。

图0-1:java探针工具原理图

Java探针工具功能点:

1、支持方法执行耗时范围抓取设置,根据耗时范围抓取系统运行时出现在设置耗时范围的代码运行轨迹。

2、支持抓取特定的代码配置,方便对配置的特定方法进行抓取,过滤出关系的代码执行耗时情况。

3、支持APP层入口方法过滤,配置入口运行前的方法进行监控,相当于监控特有的方法耗时,进行方法专题分析。

4、支持入口方法参数输出功能,方便跟踪耗时高的时候对应的入参数。

5、提供WEB页面展示接口耗时展示、代码调用关系图展示、方法耗时百分比展示、可疑方法凸显功能。

下面看个例子:

第一篇:

JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。

JavaAgent 是运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法。

那么如何实现一个 JavaAgent 呢?很简单,只需要增加 premain 方法即可。

看下面的代码和代码中的注释说明:

  1. 先写一个premain方法:
  1. package agent;
  2. import java.lang.instrument.Instrumentation;
  3.  
  4. public class pre_MyProgram {
  5. /**
  6. * 该方法在main方法之前运行,与main方法运行在同一个JVM中
  7. * 并被同一个System ClassLoader装载
  8. * 被统一的安全策略(security policy)和上下文(context)管理
  9. *
  10. * @param agentOps
  11. * @param inst
  12. * @author SHANHY
  13. * @create 2016年3月30日
  14. */
  15. public static void premain(String agentOps,Instrumentation inst){
  16.  
  17. System.out.println("====premain 方法执行");
  18. System.out.println(agentOps);
  19. }
  20.  
  21. /**
  22. * 如果不存在 premain(String agentOps, Instrumentation inst)
  23. * 则会执行 premain(String agentOps)
  24. *
  25. * @param agentOps
  26. * @author SHANHY
  27. * @create 2016年3月30日
  28. */
  29. public static void premain(String agentOps){
  30.  
  31. System.out.println("====premain方法执行2====");
  32. System.out.println(agentOps);
  33. }
  34. public static void main(String[] args) {
  35. // TODO Auto-generated method stub
  36.  
  37. }
  38.  
  39. }

写完这个类后,我们还需要做一步配置工作。

在 src 目录下添加 META-INF/MANIFEST.MF 文件,内容按如下定义:

  1.  

Manifest-Version: 1.0
Premain-Class: agent.pre_MyProgram
Can-Redefine-Classes: true

  1.  
  1.  

要特别注意,一共是四行,第四行是空行,还有就是冒号后面的一个空格,如下截图: 

然后我们打包代码为 pre_MyProgram.jar

注意打包的时候选择我们自己定义的 MANIFEST.MF ,这是导出步骤:

(1)

(2) 注意选择pre的MF文件


接着我们在创建一个带有main方法的主程序工程,截图如下: 

这时候别忘了:

main函数也有MF文件:别写错了,不然导出报错:No main manifest attribute(说明MF文件写错了)

  1. Manifest-Version: 1.0
  2. Main-Class: alibaba.MyProgram

按同样的方法导出main的jar包命名为:MyProgram.jar

如下:

选择它的MF文件:


如何执行 MyProgram.jar ?我们通过 -javaagent 参数来指定我们的Java代理包,值得一说的是 -javaagent 这个参数的个数是不限的,如果指定了多个,则会按指定的先后执行,执行完各个 agent 后,才会执行主程序的 main 方法。

命令如下:

C:\WINDOWS\system32>java -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre
_MyProgram.jar=Hello1 -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre_My
Program.jar=Hello2 -jar C:\Users\z003fe9c\Desktop\tessdata\agent\MyProgram.jar

  1.  

输出结果:

  1. ====premain 方法执行
  2. Hello1
  3. ====premain 方法执行
  4. Hello2
  5. =========main方法执行====
  1.  

特别提醒:

(1)如果你把 -javaagent 放在 -jar 后面,则不会生效。也就是说,放在主程序后面的 agent 是无效的。

比如执行:

  1.  
  1. java -javaagent:G:\myagent.jar=Hello1 -javaagent:G:\myagent.jar=Hello2 -jar myapp.jar -javaagent:G:\myagent.jar=Hello3
  1. (2)如果main函数忘了选择MF文件或是MF文件选择的不对,就会报错:
  1.  

只会有前个生效,第三个是无效的。

  1.  

命令中的Hello1为我们传递给 premain 方法的字符串参数。

至此,我们会使用 javaagent 了,但是单单看这样运行的效果,好像没有什么实际意义嘛。

我们可以用 javaagent 做什么呢?下篇文章我们来介绍如何在项目中应用 javaagent。


最后说一下,还有一种,在main方法执行后再执行代理的方法,因为不常用,而且主程序需要配置 Agent-Class,所以不常用,如果需要自行了解下 agentmain(String agentArgs, Instrumentation inst) 方法。

第二篇:

从此处开始,到最后,是我直接复制了其他人员的,因为我自己的一直没有调试出来,不过思路清楚了:

第二篇可以直接看别人的 JavaAgent 应用(spring-loaded 热部署),以下的可以忽略掉:

上一篇文章简单介绍了 javaagent ,想了解的可以移步 “JavaAgent

本文重点说一下,JavaAgent 能给我们带来什么?

  1. 自己实现一个 JavaAgent xxxxxx
  2. 基于 JavaAgent 的 spring-loaded 实现 jar 包的热更新,也就是在不重启服务器的情况下,使我们某个更新的 jar 被重新加载。

一、基于 JavaAgent 的应用实例

JDK5中只能通过命令行参数在启动JVM时指定javaagent参数来设置代理类,而JDK6中已经不仅限于在启动JVM时通过配置参数来设置代理类,JDK6中通过 Java Tool API 中的 attach 方式,我们也可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。 
Instrumentation 的最大作用,就是类定义动态改变和操作。

最简单的一个例子,计算某个方法执行需要的时间,不修改源代码的方式,使用Instrumentation 代理来实现这个功能,给力的说,这种方式相当于在JVM级别做了AOP支持,这样我们可以在不修改应用程序的基础上就做到了AOP,是不是显得略吊。

  1. 创建一个 ClassFileTransformer 接口的实现类 MyTransformer 
    实现 ClassFileTransformer 这个接口的目的就是在class被装载到JVM之前将class字节码转换掉,从而达到动态注入代码的目的。那么首先要了解MonitorTransformer 这个类的目的,就是对想要修改的类做一次转换,这个用到了javassist对字节码进行修改,可以暂时不用关心jaavssist的原理,用ASM同样可以修改字节码,只不过比较麻烦些。

接着上一篇文章的2个工程,分别添加下面的类。 
MyTransformer.java 添加到 MyAgent 工程中。

  1.  
  1. package com.shanhy.demo.agent;
  2.  
  3. import java.lang.instrument.ClassFileTransformer;
  4. import java.lang.instrument.IllegalClassFormatException;
  5. import java.security.ProtectionDomain;
  6. import java.util.ArrayList;
  7. import java.util.HashMap;
  8. import java.util.List;
  9. import java.util.Map;
  10.  
  11. import javassist.ClassPool;
  12. import javassist.CtClass;
  13. import javassist.CtMethod;
  14. import javassist.CtNewMethod;
  15.  
  16. /**
  17. * 检测方法的执行时间
  18. *
  19. * @author 单红宇(365384722)
  20. * @myblog http://blog.csdn.net/catoop/
  21. * @create 2016年3月30日
  22. */
  23. public class MyTransformer implements ClassFileTransformer {
  24.  
  25. final static String prefix = "\nlong startTime = System.currentTimeMillis();\n";
  26. final static String postfix = "\nlong endTime = System.currentTimeMillis();\n";
  27.  
  28. // 被处理的方法列表
  29. final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>();
  30.  
  31. public MyTransformer() {
  32. add("com.shanhy.demo.TimeTest.sayHello");
  33. add("com.shanhy.demo.TimeTest.sayHello2");
  34. }
  35.  
  36. private void add(String methodString) {
  37. String className = methodString.substring(0, methodString.lastIndexOf("."));
  38. String methodName = methodString.substring(methodString.lastIndexOf(".") + 1);
  39. List<String> list = methodMap.get(className);
  40. if (list == null) {
  41. list = new ArrayList<String>();
  42. methodMap.put(className, list);
  43. }
  44. list.add(methodName);
  45. }
  46.  
  47. @Override
  48. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
  49. ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  50. className = className.replace("/", ".");
  51. if (methodMap.containsKey(className)) {// 判断加载的class的包路径是不是需要监控的类
  52. CtClass ctclass = null;
  53. try {
  54. ctclass = ClassPool.getDefault().get(className);// 使用全称,用于取得字节码类<使用javassist>
  55. for (String methodName : methodMap.get(className)) {
  56. String outputStr = "\nSystem.out.println(\"this method " + methodName
  57. + " cost:\" +(endTime - startTime) +\"ms.\");";
  58.  
  59. CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到这方法实例
  60. String newMethodName = methodName + "$old";// 新定义一个方法叫做比如sayHello$old
  61. ctmethod.setName(newMethodName);// 将原来的方法名字修改
  62.  
  63. // 创建新的方法,复制原来的方法,名字为原来的名字
  64. CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null);
  65.  
  66. // 构建新的方法体
  67. StringBuilder bodyStr = new StringBuilder();
  68. bodyStr.append("{");
  69. bodyStr.append(prefix);
  70. bodyStr.append(newMethodName + "($$);\n");// 调用原有代码,类似于method();($$)表示所有的参数
  71. bodyStr.append(postfix);
  72. bodyStr.append(outputStr);
  73. bodyStr.append("}");
  74.  
  75. newMethod.setBody(bodyStr.toString());// 替换新方法
  76. ctclass.addMethod(newMethod);// 增加新方法
  77. }
  78. return ctclass.toBytecode();
  79. } catch (Exception e) {
  80. System.out.println(e.getMessage());
  81. e.printStackTrace();
  82. }
  83. }
  84. return null;
  85. }
  86. }
  1.  
  1.  

TimeTest.java 添加到 MyProgram 工程中。

  1.  
  1. package com.shanhy.demo;
  2.  
  3. /**
  4. * 被测试类
  5. *
  6. * @author 单红宇(365384722)
  7. * @myblog http://blog.csdn.net/catoop/
  8. * @create 2016年3月30日
  9. */
  10. public class TimeTest {
  11.  
  12. public static void main(String[] args) {
  13. sayHello();
  14. sayHello2("hello world222222222");
  15. }
  16.  
  17. public static void sayHello() {
  18. try {
  19. Thread.sleep(2000);
  20. System.out.println("hello world!!");
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25.  
  26. public static void sayHello2(String hello) {
  27. try {
  28. Thread.sleep(1000);
  29. System.out.println(hello);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  1.  
  1.  

修改MyAgent.java 的 permain 方法,如下:

  1. public static void premain(String agentOps, Instrumentation inst) {
  2. System.out.println("=========premain方法执行========");
  3. System.out.println(agentOps);
  4. // 添加Transformer
  5. inst.addTransformer(new MyTransformer());
  6. }

修改MANIFEST.MF内容,增加 Boot-Class-Path 如下:

  1.  
  1. Manifest-Version: 1.0
  2. Premain-Class: com.shanhy.demo.agent.MyAgent
  3. Can-Redefine-Classes: true
  4. Boot-Class-Path: javassist-3.18.1-GA.jar
  1.  
  1.  

对2个工程分别打包为 myagent.jar 和 myapp.jar 然后将 javassist-3.18.1-GA.jar 和 myagent.jar 放在一起。

最后执行命令测试,结果如下:

  1.  
  1. G:\>java -javaagent:G:\myagent.jar=Hello1 -jar myapp.jar
  2. =========premain方法执行========
  3. Hello1
  4. hello world!!
  5. this method sayHello cost:2000ms.
  6. hello world222222222
  7. this method sayHello2 cost:1000ms.
  1.  
  1.  

二、使用 spring-loaded 实现 jar 包热部署

在项目开发中我们可以把一些重要但又可能会变更的逻辑封装到某个 logic.jar 中,当我们需要随时更新实现逻辑的时候,可以在不重启服务的情况下让修改后的 logic.jar 被重新加载生效。

spring-loaded是一个开源项目,项目地址:https://github.com/spring-projects/spring-loaded

使用方法:

  1. 在启动主程序之前指定参数
  1. 在启动主程序之前指定参数
  2. -javaagent:C:/springloaded-1.2.5.RELEASE.jar -noverify

如果你想让 Tomat 下面的应用自动热部署,只需要在 catalina.sh 中添加:

  1.  
  1. set JAVA_OPTS=-javaagent:springloaded-1.2.5.RELEASE.jar -noverify
  1.  
  1.  

这样就完成了 spring-loaded 的安装,它能够自动检测Tomcat 下部署的webapps ,在不重启Tomcat的情况下,实现应用的热部署。

通过使用 -noverify 参数,关闭 Java 字节码的校验功能。 
使用参数 -Dspringloaded=verbose;explain;watchJars=tools.jar 指定监视的jar (verbose;explain; 非必须),多个jar用“冒号”分隔,如 watchJars=tools.jar:utils.jar:commons.jar

当然,它也有一些小缺限: 
1. 目前官方提供的1.2.4 版本在linux上可以很好的运行,但在windows还存在bug,官网已经有人提出:https://github.com/spring-projects/spring-loaded/issues/145 
2. 对于一些第三方框架的注解的修改,不能自动加载,比如:spring mvc的@RequestMapping 
3. log4j的配置文件的修改不能即时生效。

Java探针的更多相关文章

  1. 深入浅出Java探针技术1--基于java agent的字节码增强案例

    Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一.什么是java agent? Java agent是在JDK1.5引入的,是一种可以动态修改 ...

  2. Java探针-Java Agent技术-阿里面试题

    Java探针参考:Java探针技术在应用安全领域的新突破 最近面试阿里,面试官先是问我类加载的流程,然后问了个问题,能否在加载类的时候,对字节码进行修改 我懵逼了,答曰不知道,面试官说可以的,使用Ja ...

  3. 深入浅出Java探针技术2---java字节码生成框架ASM、Javassist和byte buddy的使用

    目前Java字节码生成框架大致有ASM.Javassist和byte buddy三种 ASM框架介绍及使用 1.ASM介绍 ASM是一种Java字节码操控框架,能够以二进制形式修改已有的类或是生成类, ...

  4. Java探针技术-retransformclasses的介绍

    retransformclasses void retransformclasses(class... classes) throws unmodifiableclassexception 重转换提供 ...

  5. 🏆【Java技术专区】「探针Agent专题」Java Agent探针的技术介绍(1)

    前提概要 Java调式.热部署.JVM背后的支持者Java Agent: 各个 Java IDE 的调试功能,例如 eclipse.IntelliJ : 热部署功能,例如 JRebel.XRebel. ...

  6. 干货 | 云智慧透视宝Java代码性能监控实现原理

    这篇图文并茂,高端大气上档次,思维缜密的文章,一看就和我平时的风格不同.对了.这不是我写的,是我家高大英俊,写一手好代码,做一手好菜的男神老公的大作,曾发表于技术公号,经本人授权转载~~ 一.Java ...

  7. Java 类加载机制(阿里面试题)-何时初始化类

    (1)阿里的面试官问我,可以不可以自己写个String类 答案:不可以,因为 根据类加载的双亲委派机制,会去加载父类,父类发现冲突了String就不再加载了; (2)能否在加载类的时候,对类的字节码进 ...

  8. 多语言(Java、.NET、Node.js)混合架构下开源调用链追踪APM项目初步选型

    1. 背景 我们的技术栈包括了Java..NET.Node.js等,并且采用了分布式的技术架构,系统性能管理.问题排查成本越来越高. 2. 基本诉求 针对我们的情况,这里列出了选型的主要条件,作为最终 ...

  9. Java 调式、热部署、JVM 背后的支持者 Java Agent

    我们平时写 Java Agent 的机会确实不多,也可以说几乎用不着.但其实我们一直在用它,而且接触的机会非常多.下面这些技术都使用了 Java Agent 技术,看一下你就知道为什么了. -各个 J ...

随机推荐

  1. 有效的括号(Java实现)

    题目: 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足: 左括号必须用相同类型的右括号闭合. 左括号必须以正确的顺序闭合. 注意空字符 ...

  2. Cocos Creator学习三:生命周期回调函数

    1.目的:学习生命周期回调函数以及回调顺序,更有利于我们逻辑的处理把控. 2.生命周期回调函数: 节点:指cc.Node:组件:指cc.Component. ①onLoad:脚本组件绑定的节点所在场景 ...

  3. 如何查看linux服务器内存使用情况

    1. free命令 free 命令显示系统使用和空闲的内存情况,包括物理内存.交互区内存(swap)和内核缓冲区内存. 直接输入free命令,显示如下   free命令默认是显示单位kb,可以采用fr ...

  4. scrapy框架使用笔记

    目前网上有很多关于scrapy的文章,这里我主要介绍一下我在开发中遇到问题及一些技巧: 1,以登录状态去爬取(带cookie) -安装内容: brew install phantomjs (MAC上) ...

  5. java之JVM(一)

    内存模型: Java内存模型建立在自动内存管理的概念之上.当一个对象不再被一个应用所引用,垃圾回收器就会回收它,从而释放相应的内存. JVM从底层操作系统中分配内存,并将它们分为以下几个区域: 方法区 ...

  6. getter unddfined

    今天用vue.js写代码 报错: getter unddfined 错误原因:没有在main.js中注册store

  7. live 2d js demo

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  8. 脚本语言 ES

    C# 编写,解释执行,语法类似 JS,动态类型,支持闭包,支持热更新,效率比较低,目前暂时没有发现 BUG,实际游戏运行稳定,没有发现内存泄漏 Github:https://github.com/ea ...

  9. iOS调试踩过的坑 以及instruments使用指南

    1. 低版本的XCode工程中包含的lib,在高版本的XCode中会编译错误,提示找不到库,故需要恢复libC++库到原位置上,参考 https://github.com/devdawei/libst ...

  10. hadoop hdfs 数据迁移到其他集群

    # hadoop fs -cat /srclist Warning: $HADOOP_HOME is deprecated. hdfs://sht-sgmhadoopcm-01:9011/jdk-6u ...