Premain-Class: com.aruforce.myAop.jvmagent.Agent
java -jar app.jar -javaagent:pathto/agent.jar
pom.xml:java
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.aruforce.jvm-agent</groupId> <artifactId>jvm-agent</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <encoding>UTF-8</encoding> </properties> <build> <finalName>${project.artifactId}-${project.version}</finalName> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestEntries> <Premain-Class>com.aruforce.myAop.jvmagent.Agent</Premain-Class> </manifestEntries> </archive> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
Agent:git
package com.aruforce.myAop.jvmagent; import java.lang.instrument.Instrumentation; /** * @Author * JVM 提供了一种JVM启动后(在main方法以前)执行agentJar内premain方法的机制 * 启动一个pre-Main-Agent的方式是使用command-line 参数指定Jar的path:- javaagent: pathToAgentJar[agentArgs]; * 注意能够有多个-javaagent参数sample: * java -jar HelloWorld.jar -javaagent:pathToAgentA[agentArgs] -javaagent:pathToAgentB[agentArgs] */ public class Agent { public static Instrumentation instrumentation = null; public static String agentArgs = null; /** * JVM初始化完成后,会按照命令行的指定Agent的顺序依次调用每一个agentJar包内的Premain-class 值指定类的premain方法,所有执行完成后后再执行main方法; * JVM会优先尝试执行{@link #premain(String agentArgs,Instrumentation instrument)},若是成功则执行下一个Agent的premain,不然尝试{@link #premain(String agentArgs)} * @param agentArgs 命令行参数 * @param instrument JVM自动注入的一个工具类,提供了一套API 用于类文件字节码的修改buf等等,虚拟机级别的AOP 支持 ; */ public static void premain(String agentArgs,Instrumentation instrument){ System.out.println("now invoking method 'premain(String agentArgs,Instrumentation instrument)'"); Agent.agentArgs = agentArgs; Agent.instrumentation = instrument; } /** * * @param agentArgs */ public static void premain(String agentArgs){ System.out.println("now invoking method 'premain(String agentArgs)'"); Agent.agentArgs = agentArgs; } }
Mainshell
package com.aruforce.myAop.app; import com.aruforce.myAop.jvmagent.Agent; public class Main{ public static void main(String [] args){ System.out.println("Agent.instrumention != null >>"+(Agent.instrumention!=null); } }
CommandLine:apache
java com.aruforce.myAop.app.Main -javaagent:pathto/jvm-agent-0.0.1-SNAPSHOT.jar
log:api
now invoking method 'premain(String agentArgs,Instrumentation instrument)' Agent.instrumention != null >>true
当JVM已经处于running mode时候再启用agent数组
Agent-Class: com.aruforce.myAop.jvmagent.Agent
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.aruforce.myAop</groupId> <artifactId>myAop</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>jvm-agent</module> <module>app</module> </modules> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <encoding>UTF-8</encoding> </properties> <dependencyManagement> <dependencies> <!--about log,代码只容许使用slf4j-api--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--log start--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> </dependencies> </project>
pom:app
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.aruforce.myAop</groupId> <artifactId>myAop</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>com.aruforce.myAop</groupId> <artifactId>jvm-agent</artifactId> <version>${parent.version}</version> <packaging>jar</packaging> <build> <finalName>${project.artifactId}-${project.version}</finalName> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestEntries> <Agent-Class>com.aruforce.myAop.jvmagent.Agent</Agent-Class> </manifestEntries> </archive> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
Agent:jvm
package com.aruforce.myAop.jvmagent; import java.lang.instrument.Instrumentation; public class Agent { public static Instrumentation instrumentation = null; public static String agentArgs = null; public static void agentmain(String agentArgs,Instrumentation instrument){ System.out.println("now invoking method 'agentmain(String agentArgs,Instrumentation instrument)'"); Agent.agentArgs = agentArgs; Agent.instrumentation = instrument; instrumentation.addTransformer(new CustomClassTransformer()); } public static void agentmain(String agentArgs){ System.out.println("now invoking method 'agentmain(String agentArgs)'"); Agent.agentArgs = agentArgs; } }
CustomClassTransformer:maven
package com.aruforce.myAop.jvmagent; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; public class CustomClassTransformer implements ClassFileTransformer { private static final String doChangeClassName = ""; @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("now ["+className+"] is loaded"); return null;//return null 至关于没有对文件进行修改,实际上可使用AspectJ等工具在这里对类文件进行加强,classfileBuffer 就是输入的class文件字节序列(并不必定是原始的类文件,可能时上个transformer处理事后的byte[]),不容许修改,本身new一个返回 } }
pom:ide
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.aruforce.myAop</groupId> <artifactId>myAop</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>com.aruforce.myAop</groupId> <artifactId>app</artifactId> <version>${parent.version}</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.7.0</version> <scope>system</scope> <systemPath>${java.home}/../lib/tools.jar</systemPath> </dependency> </dependencies> <build> <finalName>${project.artifactId}-${project.version}</finalName> </build> </project>
Test:
package com.aruforce.myAop.app; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import java.io.IOException; public class Test { private static final String agentPath = "D:\\WorkSpacMvn\\myAop\\jvm-agent\\target\\jvm-agent-0.0.1-SNAPSHOT.jar"; public static void main(String[] args) throws IOException, AttachNotSupportedException { try { VirtualMachineAttchTools.attechAgent(agentPath); // 就是这么加载到JVM,(注意这个影响范围JVM级别的,而Spring那套是ClassLoader级别的,原理和触发机制不太同样) } catch (AgentLoadException e) { e.printStackTrace(); } catch (AgentInitializationException e) { e.printStackTrace(); } Logic.doLogic();//展现Logic.class在被类加载器加载到JVM时,会被CustomClassFileTransFormer 处理 } }
VirtualMachineAttchTools: 一个工具类利用JVM tools attech agent到当前JVM 进程
package com.aruforce.myAop.app; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; import java.io.IOException; import java.lang.management.ManagementFactory; public class VirtualMachineAttchTools { public static void attechAgent(String agentPath) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { String processDs = ManagementFactory.getRuntimeMXBean().getName(); String pid = ""; if (processDs.indexOf("@")>0){ pid = processDs.substring(0, processDs.indexOf("@")); } else{ pid = processDs; } VirtualMachine currentVM = VirtualMachine.attach(pid); currentVM.loadAgent(agentPath); currentVM.detach(); } }
Logic:
package com.aruforce.myAop.app; public class Logic { public static void doLogic(){ System.out.println("doLogic invoking"); } }
log:
now invoking method 'agentmain(String agentArgs,Instrumentation instrument)' //这个时attachJVM时执行的 now [java/lang/IndexOutOfBoundsException] is loaded now [com/aruforce/myAop/app/Logic] is loaded doLogic invoking now [java/lang/Shutdown] is loaded now [java/lang/Shutdown$Lock] is loaded
JVM提供的一个机制:使JVM编写的Agent可以对运行在JVM内的程序进行修改和调整,(通常是经过修改字节码的形式达成目标);
上面写的command-line(permain)或者vm.loadAgent(agentmain)
addTransformer(ClassFileTransformer transformer,boolean canRetransform)
retransformClasses(Class<?>... classes)
功能及执行时对JVM的影响:
- 对一组已经被加载的类文件从新处理(不论是不是处理过).
- 在这个过程当中若是有活动的线程在使用某些method,这些活动线程会继续使用method原来的代码;
- 这个方法不会形成类的再次从新初始化,也就是说静态代码块不会再次执行
- 这个方法要求不容许增长或者减小方法,也不容许修改方法签名,也不容许修改继承关系
tip:
没法理解如何上面的1是如何作到的.
具体的执行过程:
- 输入为原始的字节码(编译后直接生成的字节码)
- 对于不支持的从新处理的Class文件的transformer,他们以前处理的结果会被复用,而相似于直接跳过执行tansform方法;
- 对于支持的从新处理的transformer,他们的transform会被直接调用
- 处理完的结果会被JVM从新安装
注解参看下面的ClassTransformer执行顺序
- 不支持retransform的Java 编写的transformer
- 不支持retransform的Native的transformer(好比C编写的JVM扩展dll什么的)
- 支持retransform的Java 编写的transformer
- 支持retransform的Native的transformer
运行逻辑大概以下代码:
触发逻辑通常就是ClassLoader在Load或者redifineClass时间发生:
public class Instrumention{ private ArrayList<ClassFileTransformer> retransCapbleformers = new ArrayList<ClassFileTransformer>(); private ArrayList<ClassFileTransformer> retransInCapbleformers = new ArrayList<ClassFileTransformer>(); Map<ClassFileTransformer,Map<String,byte []>> tranResult = new ConcurrentHashMap<ClassFileTransformer,Map<String,byte []>>; Map<String,byte [] > originBytes = new HashMap<String,byte []>(); public void addTransformer(ClassFileTransformer former,boolean retransCapble){ if(retransCapble){ retransCapbleformers.add(former); }else{ retransInCapbleformers.add(former); } sort(transformers);//主要是排序 } public byte [] transform(String className,byte [] classBytes){ originBytes.put(className,classBytes); byte result = classBytes; //先由 不能从新处理的来 for(ClassFileTransformer transformer:retransInCapbleformers){ byte [] transBytes = transformer.tranform(className,classBytes); result = transBytes == null?result:transBytes;//是否是空?不是空就用返回的,是就用原来的 tranResult.get(transformer).put(className,transBytes); } //再由 能从新处理的来 for(ClassFileTransformer transformer:retransInCapbleformers){ byte [] transBytes = transformer.tranform(className,classBytes); result = transBytes == null?result:transBytes;//是否是空?不是空就用返回的,是就用原来的 tranResult.get(transformer).put(className,transBytes); } return result; } public byte [] retransform(String className){ byte result = originBytes.get(className); //先由 不能从新处理的来.主要是获取到原来处理结果 for(ClassFileTransformer transformer:retransInCapbleformers){ byte [] transBytes = tranResult.get(transformer).get(className); result = transBytes == null?result:transBytes;//是否是空?不是空就用返回的,是就用原来的 } //再由 能从新处理的来 for(ClassFileTransformer transformer:retransInCapbleformers){ byte [] transBytes = transformer.tranform(className,result); result = transBytes == null?result:transBytes;//是否是空?不是空就用返回的,是就用原来的 tranResult.get(transformer).put(className,transBytes); } return result; } } class ClassLoader{ Instrumention instrumention; public Class loadClass(String className){ byte [] orignBytes = IOUTIL.loadClassFile(className); byte [] buffedBytes = instrumention.transform(className,orignBytes); return installClass(buffedBytes) } public Class reloadClass(String className){ byte [] buffedBytes = instrumention.retransform(className); return installClass(buffedBytes) } public native Class installClass(byte [] classBytes); }
native installClass
这个是我没法理解,涉及到JVM自己代码实现,到底什么状况什么时机下能够对方法栈进行替换?
就是一个接口,在JVM define某个类前,ClassFileTransformer能够对这个类字节码的转换;虚拟机级别的AOP支持
transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer)
- 分为支持与不支持 retransform的两个类型
- 一旦在JVM内注册完成,任何新类被define或者任何类被从新define
- classfileBuffer 这个就是传入的类文件,read-only方法规定不容许修改, 须要返回一个new byte[] 或者 null
请参看上面的解释性代码;
myAop.git 虽然其彻底不是AOP,等我点了ASM的科技树,我就来还债,稍微运行一下就能够;Agent代码和文章里面稍微有点不同,只是用来讲明ClassTransformer的执行顺序;