如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈( 三 )

最常用的是loadCompleted,所以我们重写loadCompleted类,在里面开启我们的监控类SpringBeanStartMonitor线程 。
而SpringBeanStartMonitor的核心代码如下图:

如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

文章插图
使用Sandbox的doClassFilter过滤出匹配的类,这里我们是BeanFactory 。
使用doMethodFilter过滤出要监听的方法,这里是initializeBean 。
里取initializeBean作为统计耗时的切入方法 。具体为什么选择该方法,涉及到SpringBean的启动生命周期,不在本文赘述范围内 。(本文作者:蛮三刀酱)
如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

文章插图
接着使用moduleEventWatcher.watch(springBeanFilter, springBeanInitListener, Event.Type.BEFORE, Event.Type.RETURN);
将我们的springBeanInitListener监听器绑定到被观测的方法上 。这样每次initializeBean被调用,都会走到我们的监听器逻辑 。
监听器的主要逻辑如下:
如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

文章插图
代码有点长,不必细看,主要就是在原方法的BeforeEvent(进入前)和ReturnEvent(执行正常返回后)执行上述的切面逻辑,我这里便是使用了一个MAP存储每个Bean的初始化开始和结束时间,最终统计出初始化耗时 。
最终 , 我们还需要一个方法来知道我们的原始Spring应用已经启动完毕,这样我们可以手动卸载我们的Sandbox模块,毕竟他已经完成了他的历史使命,不需要再依附在主进程上 。
我们通过一个简陋的办法,检查http://127.0.0.1:8080/是否会返回小于500的状态码 , 来判断Spring容器是否已经启动 。当然如果你的Spring没有使用Web框架,就不能用这个方法来判断启动完成,你也许可以通过Spring自己的生命周期钩子函数来实现,这里我是偷了个懒 。
整个SpringBean监听模块的开发就完成了,你可以感受到 , 你的开发和日常业务开发几乎没有区别 , 这就是JVM Sandbox带给你的最大好处 。
上述源码放在了我的Github仓库:
https://github.com/monitor4all/javaMonitor
JVM Sandbox底层技术整个JVM Sandbox的入门使用基本上讲完了,上文提到了一些JVM技术名词,可能小伙伴们听过但不是特别了解 。这里简单阐述几个重要的概念 , 理清楚这几个概念之间的关系,以便大家更好的理解JVM Sandbox底层的实现 。
JVMTIJVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口,JVMTI可以用来开发并监控虚拟机,可以查看JVM内部的状态 , 并控制JVM应用程序的执行 。可实现的功能包括但不限于:调试、监控、线程分析、覆盖率分析工具等 。
很多java监控、诊断工具都是基于这种形式来工作的 。如果arthas、jinfo、brace等,虽然这些工具底层是JVM TI,但是它们还使用到了上层工具JavaAgent 。
JavaAgent和InstrumentationJavaagent是java命令的一个参数 。参数 javaagent 可以用于指定一个 jar 包 。
-agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof 另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help-agentpath:<pathname>[=<选项>] 按完整路径名加载本机代理库-javaagent:<jarpath>[=<选项>] 加载 Java 编程语言代理, 请参阅 java.lang.instrument在上面-javaagent参数中提到了参阅java.lang.instrument,这是在rt.jar 中定义的一个包 , 该包提供了一些工具帮助开发人员在 Java 程序运行时,动态修改系统中的 Class 类型 。其中,使用该软件包的一个关键组件就是 Javaagent 。从名字上看,似乎是个 Java 代理之类的,而实际上,他的功能更像是一个Class 类型的转换器,他可以在运行时接受重新外部请求 , 对Class类型进行修改 。
Instrumentation的底层实现依赖于JVMTI 。
JVM 会优先加载 带 Instrumentation 签名的方法,加载成功忽略第二种,如果第一种没有 , 则加载第二种方法 。
Instrumentation支持的接口:
public interface Instrumentation {//添加一个ClassFileTransformer//之后类加载时都会经过这个ClassFileTransformer转换void addTransformer(ClassFileTransformer transformer, boolean canRetransform);void addTransformer(ClassFileTransformer transformer);//移除ClassFileTransformerboolean removeTransformer(ClassFileTransformer transformer);boolean isRetransformClassesSupported();//将一些已经加载过的类重新拿出来经过注册好的ClassFileTransformer转换//retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;boolean isRedefineClassesSupported();//重新定义某个类void redefineClasses(ClassDefinition... definitions)throwsClassNotFoundException, UnmodifiableClassException;boolean isModifiableClass(Class<?> theClass);@SuppressWarnings("rawtypes")Class[] getAllLoadedClasses();@SuppressWarnings("rawtypes")Class[] getInitiatedClasses(ClassLoader loader);long getObjectSize(Object objectToSize);void appendToBootstrapClassLoaderSearch(JarFile jarfile);void appendToSystemClassLoaderSearch(JarFile jarfile);boolean isNativeMethodPrefixSupported();void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);}

推荐阅读