(二)Java1.8核心包rt.jar——java.lang.instrument

1、ClassFileTransformer

java 复制代码
/**
* 参数:
* loader - 正在加载类的类加载器
* className - 正在加载的类的名称
* classBeingRedefined -重定义的类
* protectionDomain-要定义或重定义的类的保护域
* classfileBuffer-类文件格式的输入字节缓冲区(不得修改)
*/
1 public interface ClassFileTransformer
2 {
3     
4     byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
5         ProtectionDomain protectionDomain, byte[] classfileBuffer)
6         throws IllegalClassFormatException;
7 }

2、Instrumentation

java 复制代码
 1  public interface Instrumentation {
 2  
 3     //添加ClassFileTransformer
 4     void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
 5     
 6     //添加ClassFileTransformer
 7     void addTransformer(ClassFileTransformer transformer);
 8     
 9     //移除ClassFileTransformer
10     boolean removeTransformer(ClassFileTransformer transformer);
11     
12     //是否可以被重新定义
13     boolean isRetransformClassesSupported();
14     
15     //重新定义Class文件
16     void redefineClasses(ClassDefinition... definitions)
17         throws ClassNotFoundException, UnmodifiableClassException;
18     
19     //是否可以修改Class文件
20     boolean isModifiableClass(Class<?> theClass);
21     
22     //获取所有加载的Class
23     @SuppressWarnings("rawtypes")
24     Class[] getAllLoadedClasses();
25     
26     //获取指定类加载器已经初始化的类
27     @SuppressWarnings("rawtypes")
28     Class[] getInitiatedClasses(ClassLoader loader);
29     
30     //获取某个对象的大小
31     long getObjectSize(Object objectToSize);
32     
33     //添加指定jar包到启动类加载器检索路径
34     void appendToBootstrapClassLoaderSearch(JarFile jarfile);
35     
36     //添加指定jar包到系统类加载检索路径
37     void appendToSystemClassLoaderSearch(JarFile jarfile);
38     
39     //本地方法是否支持前缀
40     boolean isNativeMethodPrefixSupported();
41     
42     //设置本地方法前缀,一般用于按前缀做匹配操作
43     void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);
44 }

3、 java.lang.instrument简介

  • Java5之后,增加了一个包java.lang.instrument,这个包的东西很少,两个接口,ClassFileTransformer和Instrumentation,一个类ClassDefinition,还有两个Exception:IllegalClassFormatException和UnmodifiableClassException;

  • Instrument的最大作用,就是类定义的动态改变和操作。在 Java SE 5 及其后续版本当中,开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 -javaagent参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。

  • 一个代理就是部署一个Jar文件。Jar文件的manifest文件(每个jar包根目录下有个META-INF文件夹,此文件夹下的MANIFEST.MF文件就是这里的manifest文件)需要指定这个代理的类,指定之后,这个类将被加载以启动代理。因为支持命令行接口,所以一个代理可以通过在命令行中指定一个选项来移动。具体的实现也可能支持在虚拟机启动之后的某个时间开启代理。例如,一个具体的实现可能提供一个机制,使一个工具附加到一个运行的应用中,然后,初始化这个工具的代理,加载到正在运行的应用中。关于类的加载如何被初始化的细节,依赖于具体的实现。

  • 在用命令行接口的实现中,一个代理可以通过增加下面的Option到命令行中启动:

java 复制代码
-javaagent:jarpath[=options]

 jarpath是代理jar文件的路径。option是代理的选项。上边的命令可以在同一个命令行中使用多次,因此,可以创建多个代理。多个代理可以使用同一jarpath。一个代理Jar文件必须符合Jar文件规范。
  • 一个代理jar文件的manifest必须包含Premain-Class属性。这个属性的值是代理类的名字。代理类必须实现一个public static的premain 方法,与应用程序中的main方法作为程序入口的原理类似。JVM初始化之后,每一个premain方法将被以代理被指定时的顺序调用,然后应用程序真正的main方法将被调用。每一个premain方法必须以启动的顺序返回结果。(premain=pre-main)
java 复制代码
JVM首先尝试调用代理类中下面的方法:

public static void premain(String agentArgs, Instrumentation inst);

如果代理类没有实现这个方法,jvm将尝试调用

public static void premain(String agentArgs);
  • 一个代理具体的实现可能提供一个在JVM启动之后启动的机制。关于代理被初始化的细节是具体实现特定的,但是通常应用程序已经启动,应用程序的main方法已经被调用。在代理的实现支持在JVM启动之后启动的情况下,需要遵循下面的规则:
    代理的Jar文件中的manifest文件必须包含Agent-Class属性。这个属性的值是代理类的名字。
    代理类必须实现一个public static agentmain方法
    系统类加载器( ClassLoader.getSystemClassLoader)必须支持增加一个代理Jar文件到系统类路径。

代理的Jar文件被追加到系统类路径中。这个类加载器是通常用来加载包含应用程序main方法的类的类加载器。代理类被加载之后,JVM尝试调用agentmain方法。

java 复制代码
JVM首先尝试调用代理类中的下面这个方法:

public static void agentmain(String agentArgs, Instrumentation inst);

如果代理类中没有实现这个方法,然后JVM尝试调用:

public static void agentmain(String agentArgs);

这个代理类可能也有一个premain方法,当代理在命令行选项中启动时使用;当这个代理类在JVM启动之后启动时,premain方法不会被调用。

  • manifest属性在一个代理的jar文件中被定义:
yaml 复制代码
Premain-Class

当一个代理在JVM启动时被指定时,这个属性指定代理使用的类。也就是,这个类包含premain方法。当一个代理在JVM启动时被指定时,这个属性是必须的。如果没有配置这个属性,JVM将会abort。注意:这是一个类的名字,不是一个文件名活路径。

Agent-Class

如果一个代理的实现支持在JVM启动之后的某个时间启动,这个属性指定一个代理类。也就是说,这个类要包含agentmain方法。这个属性是必须的,如果不配置的话,代理将不会被启动。注意:这是一个类的名字,不是一个文件名字,也不是文件路径。

Boot-Class-Path

这个代理的实现中用到的第三方jar包都放到这里边。

Can-Redefine-Classes

Boolean(true或false)。Agent是否可以重定义类。可选的,默认是false。

Can-Retransform-Classes

Boolean(true或false)。Agent是否可以将类变回原形。可选的,默认是false。

Can-Set-Native-Method-Prefix

Boolean(true或false)。Agent是否可以设置本地方法的前缀。可选的,默认是false。
  • 一个代理Jar文件中的manifest文件可能同时包含Premain-Class和Agent-Class属性。当代理在命令行中用-javaagent选项被启动,使用Premain-Class属性指定的代理类的名字,然后Agent-Class属性的配置被忽略。相似地,如果代理在JVM启动之后的某个时间启动,使用Agent-Class属性指定的类的名字,Premain-Class属性被忽略。

4、 示例

(1) 启动时加载

Agent类

java 复制代码
 public class MyAgent {
     public static void premain(String args,Instrumentation inst)
     {
         System.out.println("premain方法会在main方法之前执行......");
         inst.addTransformer(new MyTransformClass());
     }
 }
 

ClassFileTransformer类

java 复制代码
 public class MyTransformClass implements ClassFileTransformer{
     
     @Override
     public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
         ProtectionDomain protectionDomain, byte[] classfileBuffer)
         throws IllegalClassFormatException{
		 
         // 编译后的字符流数组
         byte[] newClassFileBuffer = new byte[classfileBuffer.length];
		 // 重定义指定类
         String transClassName = "com.App";
		 
         if (className.equals(transClassName)){
		 
             System.out.println("监控到目标类");
             
             // 对byte[]重新编译可以使用第三方工具如javassist

             newClassFileBuffer = classfileBuffer;
         }
         return newClassFileBuffer;
     }
     
 }

MANIFEST.MF文件

yaml 复制代码
Manifest-Version: 1.0
Premain-Class:com.test.MyAgent 
Boot-Class-Path: javassist.jar

打jar包

yaml 复制代码
jar cvfm MyAgent.jar MANIFEST.MF  javassist.jar  com

将MANIFEST.MF文件,javassist.jar,com文件夹下的类打成jar包

编写测试类

java 复制代码
public class App {
   public static void main(String[] args) {
     System.out.println("HelloWorld!!");
   }
}

执行测试

yaml 复制代码
java   -javaagent:MyAgent.jar   App

(2)运行时加载

Agent类

java 复制代码
 public class MyAgent {
     public static void agentmain(String args, Instrumentation inst) {
        Class[] classes = inst.getAllLoadedClasses();
        for(Class cls :classes){
            System.out.println(cls.getName());
        }
     }
 }
 

MANIFEST.MF文件

yaml 复制代码
Manifest-Version: 1.0
Agent-Class::com.test.MyAgent 
Boot-Class-Path: javassist.jar

打jar包

yaml 复制代码
jar cvfm MyAgent.jar MANIFEST.MF  javassist.jar  com

将MANIFEST.MF文件,javassist.jar,com文件夹下的类打成jar包

编写测试类,用jps命令可以方便的查看进程的ID,名字是应用程序main方法所在的类的名字。

java 复制代码
public class TargetVM {
   public static void main(String[] args) throws InterruptedException {
      while (true) {
         Thread.sleep(1000);
      }
   }
}

执行测试

java 复制代码
public class Test {
    public static void main(String[] args) throws AttachNotSupportedException,
            IOException, AgentLoadException, AgentInitializationException {
        VirtualMachine vm = VirtualMachine.attach(args[0]);
        vm.loadAgent("MyAgent.jar");
    }
}
相关推荐
小魏冬琅9 分钟前
探索面向对象的高级特性与设计模式(2/5)
java·开发语言
lihao lihao11 分钟前
C++stack和queue的模拟实现
开发语言·c++
TT哇23 分钟前
【Java】数组的定义与使用
java·开发语言·笔记
天天进步201528 分钟前
Lodash:现代 JavaScript 开发的瑞士军刀
开发语言·javascript·ecmascript
假装我不帅37 分钟前
js实现类似与jquery的find方法
开发语言·javascript·jquery
look_outs41 分钟前
JavaSE笔记2】面向对象
java·开发语言
萧鼎41 分钟前
【Python】高效数据处理:使用Dask处理大规模数据
开发语言·python
武子康1 小时前
大数据-191 Elasticsearch - ES 集群模式 配置启动 规划调优
java·大数据·elk·elasticsearch·搜索引擎·全文检索
A_aspectJ1 小时前
‌Spring MVC的主要组件有哪些?
java·spring·mvc
塔塔开!.1 小时前
Maven的依赖
java·maven