运行时动态改变类的技术

FiAttach传入两个参数,一个是agent.jar的路径,一个是存放希望运行时进行替换的类文件的文件夹路径。

程序自动检测当前的Java应用,将agent.jar附着到虚拟机进程,并将文件夹下的类文件动态替换进去(用新的类替换虚拟机中原来加载的类)。

import java.io.IOException;import java.util.List;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 com.sun.tools.attach.VirtualMachineDescriptor;public class FiAttach {public static void main(String[] args) {List<VirtualMachineDescriptor> vmdList = VirtualMachine.list();if (args.length < 2) {System.out.println(“Error! Run Command: java com.taobao.fi.FiAttach agentJarPath agentArgs”);return;}String agentJarPath = args[0];String agentArgs = args[1];System.out.println(“agentJarPath: ” + agentJarPath);System.out.println(“agentArgs: ” + agentArgs);for (VirtualMachineDescriptor vmd : vmdList) {// 注意,目前只支持jboss和tomcat,否则判断会失效!// vmd.displayName(): org.jboss.Main -b 0.0.0.0 -Djboss.server.home.dir=/home/admin/deploy/.default -Djboss.server.home.url=file:/home/admin/deploy/.defaultif (vmd.displayName().startsWith(“org.jboss.Main”) || vmd.displayName().startsWith(“org.apache.catalina.startup.Bootstrap”)) {try {VirtualMachine vm = VirtualMachine.attach(vmd);vm.loadAgent(agentJarPath, agentArgs);vm.detach();} catch (AttachNotSupportedException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (AgentLoadException e) {e.printStackTrace();} catch (AgentInitializationException e) {e.printStackTrace();}}}}}

程序编译时,需要依赖JDK_HOME/lib/tools.jar

下面看agent.jar的实现,AgentMain.java:

import java.util.Set;import java.util.HashSet;import java.io.File;import java.lang.instrument.Instrumentation;import java.lang.instrument.UnmodifiableClassException;public class AgentMain {private static Set<String> fiClsFileNames = new HashSet<String>();private static Transformer transformer = new Transformer();// 标识是否之前做过故障注入private static boolean hasFi = false;private static void updateClsFileNames(String fiClassFolderPath) {fiClsFileNames.clear();File fiClassFolderFile = new File(fiClassFolderPath);if (!fiClassFolderFile.isDirectory()) {return;}File[] fiClassFiles = fiClassFolderFile.listFiles();for (File fiClassFile : fiClassFiles) {fiClsFileNames.add(fiClassFile.getName());}}// 判断是否是已经进行过故障注入的类 或者是 将要进行故障注入的类private static boolean isPrevFiCls(String clsName) {String clsFileName = clsName + “.class”;return fiClsFileNames.contains(clsFileName);}// 判断是否是将要进行故障注入的类(注意:在这之前,需要调用updateCurrClsFileNames())private static boolean isWillingFiCls(String clsName) {String clsFileName = clsName + “.class”;return fiClsFileNames.contains(clsFileName);}public static void agentmain(String agentArgs, Instrumentation inst)throws ClassNotFoundException, UnmodifiableClassException,InterruptedException {System.out.println(“AgentMain::agentmain!!”);synchronized (AgentMain.class) {String fiClsFolderPath = agentArgs;if (hasFi) {inst.removeTransformer(transformer);Class[] classes = inst.getAllLoadedClasses();for (Class cls : classes) {System.out.println(“AgentMain::agentmain, recover class: “+ cls.getName());if (isPrevFiCls(cls.getName())) {// 触发已加载的类 还原对类的更改inst.retransformClasses(cls);}}}updateClsFileNames(fiClsFolderPath);transformer.setFiClsFolderPath(fiClsFolderPath);// 这里应该不存在线程安全隐患,因为attach动作总是人为触发的transformer.setFiClsFileNames(fiClsFileNames);// 添加转换器inst.addTransformer(transformer, true);// 更改当前已加载的类Class[] classes = inst.getAllLoadedClasses();for (Class cls : classes) {if (isWillingFiCls(cls.getName())) {System.out.println(“AgentMain::agentmain, transform class: “+ cls.getName());inst.retransformClasses(cls);}}hasFi = true;}}}

Transformer.java:

但是至少可以为自己的荷包省钱可以支些招,这点还是很现实的。

运行时动态改变类的技术

相关文章:

你感兴趣的文章:

标签云: