java中可以传入的一个参数:-D,它的说明是这样的:
-D<name>=<value>setasystemproperty设置一个系统参数和值
可以通过下面的代码获取到java-D的系统属性参数列表,代码如下:
public static void main(String[] args) { printProperties(); } public static void printProperties(){ Properties prop = System.getProperties(); prop.list(System.out); }
输出结果如下:
-- listing properties -- java.runtime.name=Java(TM) SE Runtime Environment sun.boot.library.path=D:\java\jdk1.6.0_13\jre\bin java.vm.version=11.3-b02 java.vm.vendor=Sun Microsystems Inc. java.vendor.url=http://java.sun.com/ path.separator=; java.vm.name=Java HotSpot(TM) Client VM file.encoding.pkg=sun.io user.country=CN sun.java.launcher=SUN_STANDARD sun.os.patch.level=Service Pack 3 java.vm.specification.name=Java Virtual Machine Specification user.dir=D:\myspace\monitor java.runtime.version=1.6.0_13-b03 java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment java.endorsed.dirs=D:\java\jdk1.6.0_13\jre\lib\endorsed os.arch=x86 java.io.tmpdir=C:\DOCUME~1\YONGKA~1.QIY\LOCALS~1\Temp\ line.separator= java.vm.specification.vendor=Sun Microsystems Inc. user.variant= os.name=Windows XP sun.jnu.encoding=GBK java.library.path=D:\java\jdk1.6.0_13\bin;.;C:\WINDOWS\... java.specification.name=Java Platform API Specification java.class.version=50.0 sun.management.compiler=HotSpot Client Compiler os.version=5.1 user.home=C:\Documents and Settings\yongkang.qiyk user.timezone= java.awt.printerjob=sun.awt.windows.WPrinterJob file.encoding=GBK java.specification.version=1.6 user.name=yongkang.qiyk java.class.path=D:\myspace\monitor\bin;D:\myspace\mon... java.vm.specification.version=1.0 sun.arch.data.model=32 java.home=D:\java\jdk1.6.0_13\jre java.specification.vendor=Sun Microsystems Inc. user.language=zh awt.toolkit=sun.awt.windows.WToolkit java.vm.info=mixed mode monitor.file=profile.txt java.version=1.6.0_13 java.ext.dirs=D:\java\jdk1.6.0_13\jre\lib\ext;C:\WI... sun.boot.class.path=D:\java\jdk1.6.0_13\jre\lib\resources... java.vendor=Sun Microsystems Inc. file.separator=\ java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport... sun.cpu.endian=little sun.io.unicode.encoding=UnicodeLittle sun.desktop=windows sun.cpu.isalist=pentium_pro+mmx pentium_pro pentium+m...
其中红色部分的参数是我自己在运行时JVM参数中设置进去的。
这样就可以将一个配置文件的值通过-D这个参数传入系统。
下面是一个具体的例子,这个例子和第三节说的例子基本一样,不一样的地方就在于要监测的方法不用写死在java代码中,而是放在了外部的一个配置文件中。
1.首先定义了一个读取-D参数和解析监测配置的接口:
package monitor.agent; /** * 读取系统设置的参数,已经从参数中解析监测配置 * @author yongkang.qiyk * */ public interface MonitorConfig { public String getStringValue(String key,String defaultValue); public boolean getBooleanValue(String key,boolean defautValue); public Integer getIntegerValue(String key,Integer defaultValue); }
实现类如下:
package monitor.agent; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * TODO Comment of MonitorConfigImpl * @author yongkang.qiyk * */ public class MonitorConfigImpl implements MonitorConfig { private String MONITOR_CONF = "monitor.conf"; private Properties prop = null; public MonitorConfigImpl() throws IOException{ prop = System.getProperties(); String monitorConf = prop.getProperty(MONITOR_CONF); File monitorFile = new File(monitorConf); InputStream inputStream = new FileInputStream(monitorFile); prop.load(inputStream); } /* (non-Javadoc) * @see monitor.agent.MonitorConfig#getStringValue(java.lang.String, java.lang.String) */ @Override public String getStringValue(String key, String defaultValue) { return prop.getProperty(key, defaultValue); } /* (non-Javadoc) * @see monitor.agent.MonitorConfig#getBooleanValue(java.lang.String, boolean) */ @Override public boolean getBooleanValue(String key, boolean defaultValue) { String value = prop.getProperty(key, null); return (null!=value) ? Boolean.valueOf(value) : defaultValue; } /* (non-Javadoc) * @see monitor.agent.MonitorConfig#getIntegerValue(java.lang.String, java.lang.Integer) */ @Override public Integer getIntegerValue(String key, Integer defaultValue) { String value = prop.getProperty(key, null); return (null!=value) ? Integer.valueOf(value) : defaultValue; } }
2.读取配置文件的接口实现完毕后,接着就是修改我们的修改字节码的类MonitorTransformer了。
修改如下:
/** * TODO Comment of MonitorTransformer * @author yongkang.qiyk * */ public class MonitorTransformer implements ClassFileTransformer { final static String prefix = "\nlong startTime = System.currentTimeMillis();\n"; final static String postfix = "\nlong endTime = System.currentTimeMillis();\n"; final static char point_regex = ';'; final static List<String> methodList = new ArrayList<String>(); // static{ // methodList.add("monitor.agent.MyTest.sayHello"); // methodList.add("monitor.agent.MyTest.sayHello2"); // } public MonitorTransformer(){ MonitorConfig config; try { //读取配置文件 config = new MonitorConfigImpl(); String methodStr = config.getStringValue("methodList", null); Iterable<String> it = Splitter.on(point_regex).split(methodStr); //将读取的配置文件加入要检测的方法列表 if(null!=it){ Iterator<String> itor = it.iterator(); while (itor.hasNext()) { methodList.add(itor.next()); } } } catch (IOException e) { e.printStackTrace(); } } /* (non-Javadoc) * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[]) */ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { //先判断下现在加载的class的包路径是不是需要监控的类,通过instrumentation进来的class路径用‘/’分割 if(className.startsWith("monitor/agent")){ //将‘/’替换为‘.’m比如monitor/agent/Mytest替换为monitor.agent.Mytest className = className.replace("/", "."); CtClass ctclass = null; try { // 用于取得字节码类,必须在当前的classpath中,使用全称 ,这部分是关于javassist的知识 ctclass = ClassPool.getDefault().get(className); //循环一下,看看哪些方法需要加时间监测 for(String method : methodList){ if (method.startsWith(className)){ //获取方法名 String methodName = method.substring(method.lastIndexOf('.')+1, method.length()); String outputStr = "\nSystem.out.println(\"this method "+methodName+" cost:\" +(endTime - startTime) +\"ms.\");"; //得到这方法实例 CtMethod ctmethod = ctclass.getDeclaredMethod(methodName); // 新定义一个方法叫做比如sayHello$impl String newMethodName = methodName + "$impl"; // 原来的方法改个名字 ctmethod.setName(newMethodName); //创建新的方法,复制原来的方法 ,名字为原来的名字 CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null); //构建新的方法体 StringBuilder bodyStr = new StringBuilder(); bodyStr.append("{"); bodyStr.append(prefix); // 调用原有代码,类似于method();($$)表示所有的参数 bodyStr.append(newMethodName + "($$);\n"); bodyStr.append(postfix); bodyStr.append(outputStr); bodyStr.append("}"); // 替换新方法 newMethod.setBody(bodyStr.toString()); // 增加新方法 ctclass.addMethod(newMethod); } } return ctclass.toBytecode(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CannotCompileException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return null; } }
可以看到最大的改进在哪里:原来写死的methodList被注释掉了,改为了在构造函数中通过刚才的配置文件读取类来读取要监测的方法,然后加入methodList中。如图:
3、MyAgent类没有任何修改,
public class MyAgent { public static void premain(String agentArgs, Instrumentation inst){ inst.addTransformer(new MonitorTransformer()); } }
4、MyTest也没有任何变化,只是运行时的JVM参数不一样了:
-Dmonitor.conf=D:/tools/java/profile.txt参数指定了配置文件的路径。profile.txt的文件内容如下:
我将main方法也加入被监测的方法列表了,执行结果:
这样,我们下次要在监测另外一些方法的耗时时,再也不用修改任何代码,只要在profile.txt文件中配置要监测的方法就够了。。good..