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的執行順序;