JSP动态解析过程
请求访问一个JSP文件的时候,整个过程是这样的:
JSP文件修改过后,之所以能及时生效,是因为Web容器(Tomcat)会检查请求的JSP文件是否被更改过,如果发生过更改,那么就将JSP文件重新解析翻译成一个新的Sevlet类,并加载到JVM中,之后的请求,都会由这个新的Servet来处理。这里有个问题,根据Java的类加载机制,在同一个ClassLoader中,类是不允许重复的,为了绕开这个限制,Web容器每次都会创建一个新的ClassLoader实例,来加载新编译的Servlet类,之后的请求都会由这个新的Servlet来处理,这样就实现了新旧JSP的切换。
HTTP服务是无状态的,所以JSP的场景基本上都是一次性消费,这种通过创建新的ClassLoader来“替换”class的做法行得通,但是对于其他应用,比如Spring框架,即便这样做了,对象多数是单例,对于内存中已经创建好的对象,我们无法通过这种创建新的ClassLoader实例的方法来修改对象行为。
java.lang.instrument.Instrumentation
看完文档之后,我们发现这么两个接口:reDefineClasses和reTransformClasses。一个是重新定义class,一个是修改class。
直接操作字节码 ASM、CGLib
BTrace
https://github.com/btraceio/btrace
BTrace是基于Java语言的一个安全的、可提供动态追踪服务的工具。BTrace基于ASM、Java Attach Api、Instruments开发,为用户提供了很多注解。依靠这些注解,我们可以编写BTrace脚本(简单的Java代码)达到我们想要的效果,而不必深陷于ASM对字节码的操作中不可自拔。
package com.sun.btrace.samples;
import com.sun.btrace.annotations.*;
import com.sun.btrace.AnyType;
import static com.sun.btrace.BTraceUtils.*;
/**
* This sample demonstrates regular expression
* probe matching and getting input arguments
* as an array - so that any overload variant
* can be traced in "one place". This example
* traces any "readXX" method on any class in
* java.io package. Probed class, method and arg
* array is printed in the action.
*/
@BTrace public class ArgArray {
@OnMethod(
clazz="/java\\.io\\..*/",
method="/read.*/"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
println(pcn);
println(pmn);
printArray(args);
}
}
package com.sun.btrace.samples;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.Export;
/**
* This sample creates a jvmstat counter and
* increments it everytime Thread.start() is
* called. This thread count may be accessed
* from outside the process. The @Export annotated
* fields are mapped to jvmstat counters. The counter
* name is "btrace." + + "." +
*/
@BTrace public class ThreadCounter {
// create a jvmstat counter using @Export
@Export private static long count;
@OnMethod(
clazz="java.lang.Thread",
method="start"
)
public static void onnewThread(@Self Thread t) {
// updating counter is easy. Just assign to
// the static field!
count++;
}
@OnTimer(2000)
public static void ontimer() {
// we can access counter as "count" as well
// as from jvmstat counter directly.
println(count);
// or equivalently ...
println(Counters.perfLong("btrace.com.sun.btrace.samples.ThreadCounter.count"));
}
}
BTrace的架构是怎样的呢?
BTrace主要有下面几个模块:
BTrace脚本:利用BTrace定义的注解,我们可以很方便地根据需要进行脚本的开发。
Compiler:将BTrace脚本编译成BTrace class文件。
Client:将class文件发送到Agent。
Agent:基于Java的Attach Api,Agent可以动态附着到一个运行的JVM上,然后开启一个BTrace Server,接收client发过来的BTrace脚本;解析脚本,然后根据脚本中的规则找到要修改的类;修改字节码后,调用Java Instrument的reTransform接口,完成对对象行为的修改并使之生效。
Ref: