java中可以传入的一个参数:-D ,它的说明 是这样的:
-D<name>=<value> set a system property 设置一个系统参数和值
可以通过下面的代码获取到 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..