- 前言
- 第一部分 核心实现
- 第 1 章 Spring 整体架构和环境搭建
- 第 2 章 容器的基本实现
- 第 3 章 默认标签的解析
- 第 4 章 自定义标签的解析
- 第 5 章 bean 的加载
- 第 6 章 容器的功能扩展
- 第 7 章 AOP
- 第二部分 企业应用
- 第 8 章 数据库连接 JDBC
- 第 9 章 整合 MyBatis
- 第 10 章 事务
- 第 11 章 SpringMVC
- 第 12 章 远程服务
- 第 13 章 Spring 消息
7.5.1 Instrumentation 使用
Java 在 1.5 引入 java.lang.instrument,你可以由此实现一个 Java agent,通过此 agent 来修改类的字节码即改变一个类。本节会通过 Java Instrument 实现一个简单的 profiler。当然 instrument 并不限于 profiler,instrument 它可以做很多事情,它类似一种更低级、更松耦合的 AOP,可以从底层来改变一个类的行为。你可以由此产生无限的遐想。接下来要做的事情,就是计算一个方法所花的时间,通常我们会在代码中按以下方式编写。
在方法开头加入 long stime = System.nanoTime();在方法结尾通过 System.nanoTime()-stime 得出方法所花时间。你不得不在想监控的每个方法中写入重复的代码,好一点的情况,你可以用 AOP 来干这事,但总是感觉有点别扭,这种 profiler 的代码还是要打包在你的项目中,Java Instrument 使得这一切更干净。
(1)写 ClassFileTransformer 类。
package org.toy;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
public class PerfMonXformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] transformed = null;
System.out.println("Transforming " + className);
ClassPool pool = ClassPool.getDefault();
CtClass cl = null;
try {
cl = pool.makeClass(new java.io.ByteArrayInputStream(
classfileBuffer));
if (cl.isInterface() == false) {
CtBehavior[] methods = cl.getDeclaredBehaviors();
for (int i = 0; i < methods.length; i++) {
if (methods[i].isEmpty() == false) {
//修改 method 字节码
doMethod(methods[i]);
}
}
transformed = cl.toBytecode();
}
} catch (Exception e) {
System.err.println("Could not instrument " + className
+ ", exception : " + e.getMessage());
} finally {
if (cl != null) {
cl.detach();
}
}
return transformed;
}
private void doMethod(CtBehavior method) throws NotFoundException,
CannotCompileException {
method.insertBefore("long stime = System.nanoTime();");
method.insertAfter("System.out.println(/"leave "+method.getName()+" and
time:/"+(System.nanoTime()-stime));");
}
}
(2)编写 agent 类。
package org.toy;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
public class PerfMonAgent {
static private Instrumentation inst = null;
/**
* This method is called before the application’s main-method is called,
* when this agent is specified to the Java VM.
**/
public static void premain(String agentArgs, Instrumentation _inst) {
System.out.println("PerfMonAgent.premain() was called.");
// Initialize the static variables we use to track information.
inst = _inst;
// Set up the class-file transformer.
ClassFileTransformer trans = new PerfMonXformer();
System.out.println("Adding a PerfMonXformer instance to the JVM.");
inst.addTransformer(trans);
}
}
上面两个类就是 agent 的核心了,JVM 启动时在应用加载前会调用 PerfMonAgent.premain,然后 PerfMonAgent.premain 中实例化了一个定制的 ClassFileTransforme,即 PerfMonXformer 并通过 inst.addTransformer(trans) 把 PerfMonXformer 的实例加入 Instrumentation 实例(由 JVM 传入),这就使得应用中的类加载时,PerfMonXformer.transform 都会被调用,你在此方法中可以改变加载的类。真的有点神奇,为了改变类的字节码,我使用了 JBoss 的 Javassist,虽然你不一定要这么用,但 JBoss 的 Javassist 真的很强大,能让你很容易地改变类的字节码。在上面的方法中我通过改变类的字节码,在每个类的方法入口中加入了 long stime = System.nanoTime(),在方法的出口加入了:
System.out.println("methodClassName.methodName:"+(System.nanoTime()-stime));
(3)打包 agent。
对于 agent 的打包,有点讲究。
JAR 的 META-INF/MANIFEST.MF 加入 Premain-Class: xx,xx 在此语境中就是我们的 agent 类,即 org.toy.PerfMonAgent。
如果你的 agent 类引入别的包,需使用 Boot-Class-Path: xx,xx 在此语境中就是上面提到的 JBoss javassit,即/home/pwlazy/.m2/repository/javassist/javassist/3.8.0 .GA/ javassist-3.8.0.GA.jar。
下面附上 Maven 的 POM。
[xhtml]view plaincopyprint?
<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>org.toy</groupId>
<artifactId>toy-inst</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>toy-inst</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.8.0.GA</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>org.toy.PerfMonAgent</Premain-Class>
<Boot-Class-Path>/home/pwlazy/.m2/repository/javassist/javassist/3.8.0.GA/
javassist-3.8.0.GA.jar</Boot-Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin </artifactId >
<configuration>
<source> 1.6 </source >
<target> 1.6 </target>
</configuration>
</plugin>
</plugins>
</build>
</project>
(4)打包应用。
package org.toy;
public class App {
public static void main(String[] args) {
new App().test();
}
public void test() {
System.out.println("Hello World!!");
}
}
Java 选项中有-javaagent:xx,其中 xx 就是你的 agent JAR,Java 通过此选项加载 agent,由 agent 来监控 classpath 下的应用。
最后的执行结果:
PerfMonAgent.premain() was called.
Adding a PerfMonXformer instance to the JVM.
Transforming org/toy/App
Hello World!!
java.io.PrintStream.println:314216
org.toy.App.test:540082
Transforming java/lang/Shutdown
Transforming java/lang/Shutdown$Lock
java.lang.Shutdown.runHooks:29124
java.lang.Shutdown.sequence:132768
由执行结果可以看出,执行顺序以及通过改变 org.toy.App 的字节码加入监控代码确实生效了。你也可以发现,通过 Instrment 实现 agent 使得监控代码和应用代码完全隔离了。
通过之前的两个小示例我们似乎已经有所体会,在 Spring 中的静态 AOP 直接使用了 AspectJ 提供的方法,而 AspectJ 又是在 Instrument 基础上进行的封装。就以上面的例子来看,至少在 AspectJ 中会有如下功能。
(1)读取 META-INF/aop.xml。
(2)将 aop.xml 中定义的增强器通过自定义的 ClassFileTransformer 织入对应的类中。
当然这都是 AspectJ 所做的事情,并不在我们讨论的范畴,Spring 是直接使用 AspectJ,也就是将动态代理的任务直接委托给了 AspectJ,那么,Spring 怎么嵌入 AspectJ 的呢?同样我们还是从配置文件入手。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论