博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何实现一个可用的javaagent
阅读量:7117 次
发布时间:2019-06-28

本文共 3806 字,大约阅读时间需要 12 分钟。

最近做了一个项目需要用javaagent方式对应用常用的组件(比如httpclient, 数据库连接池等)进行调用追踪和监控,并结合公司的分布式追踪组件,将所有java应用的外部调用情况收集起来方便做系统分析和问题定位。项目定位和开源项目比较像,但了解过pinpoint实现以后,发现其分布式追踪和组件监控的逻辑耦合太过紧密,而且整个项目比较重,实现繁杂,不容易和公司的分布式追踪组件结合起来,所以决定自己搞。这里暂且把这个项目取名叫dagent。

网上其实有很多文章介绍如何编写javaagent,但往往介绍得非常简单,只介绍premain的启动机制,manifest如何编写,但这类文章都没有说明简单实现的javaagent能否实际发挥作用,在实际的项目中可能会有哪些坑。所以,我想把这次项目过程中踩过的坑记录下来,分享给需要的人。

ClassLoader之殇

首先得从spring boot的uber jar说起。所谓uber jar,就是一个all in one的可执行jar包。jar包中包含了Java应用运行所需要的代码、资源以及依赖的jar包,直接执行java -jar xxx.jar即可启动。对于web应用,spring boot还提供了嵌入式的web容器,无需部署tomcat服务器,应用部署运行特别方便。所以最近公司开始采用spring boot。

在测试dagent时发现,对于使用spring boot框架的应用,直接在IDE里面执行main方法运行dagent工作ok,一旦打成uber jar方式后加上dagent启动,就会出现dagent中引用的第三方包中的类报ClassNotFoundException

查看spring boot源码,发现了原因所在。原来,由于uber jar将应用依赖的jar包以nested jar的方式打进包内,为了实现不解压缩就启动,spring boot使用自己的main类org.springframework.boot.loader.JarLauncher启动应用,并自定义一个LaunchedURLClassLoader,再由它加载应用的main类。而LaunchedURLClassLoader在初始化classpath搜索路径时特意把javaagent jar包排除在外,所以javaagent jar包中的类是不能被LaunchedURLClassLoader定义的,所以javaagent中的辅助类如果引用了某个第三方包中的类,而这个类是被LaunchedURLClassLoader定义的,简单引用就会出现ClassNotFoundException

所以,必须让javaagent中用到的辅助类也由定义当前正在增强的Class的LaunchedURLClassLoader定义。

public interface ClassFileTransformer {    byte[] transform(               ClassLoader         loader,            String              className,            Class
classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException;}

更确切地说,应该让每一个被ClassFileTransformer修改的类所用到的自定义以及第三方辅助类都由ClassFileTransformer#transform()方法第一个参数的ClassLoader定义。这样,不管应用的ClassLoader采用了何种类查找策略,都可以保证辅助类可以正常加载到。

ClassLoader注入

那么,如何做到让增强类所用到的辅助类都被同一个类定义呢。一种办法是显示地用对应的ClassLoader定义所有用到的辅助类,这样需要手动注入所有辅助类,比较繁琐;另一种办法是将一组功能相关的辅助类打成jar包,注入到对应的ClassLoader中,这样就不需要一个一个类手动注入。dagent采用的是第二种方案,将一组功能相关的增强辅助类做成一个插件,并打成一个jar包,然后在增强类的时候,将对应的jar包注入到当前执行增强的ClassLoader中。

不同类型的ClassLoader的注入方式有所不同,方法如下:

public class ClassInjector {    private static Method DEFINE_CLASS;    private static Method ADD_URL;    static {        try {            DEFINE_CLASS = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);            DEFINE_CLASS.setAccessible(true);            ADD_URL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);            ADD_URL.setAccessible(true);        } catch (NoSuchMethodException e) {            throw new IllegalStateException(e);        }    }    /**     * 注入到非URLClassLoader的非引导类ClassLoader     * @param classLoader     * @param className     * @param bytes 类定义     * @throws InvocationTargetException     * @throws IllegalAccessException     */    public static void defineClass(ClassLoader classLoader, String className, byte[] bytes) throws InvocationTargetException, IllegalAccessException {        if (classLoader != null) {            DEFINE_CLASS.invoke(classLoader, className, bytes, 0, bytes.length);        }    }    /**     * 注入到URLClassLoader类加载器     * @param classLoader     * @param url     * @throws InvocationTargetException     * @throws IllegalAccessException     */    public static void addURL(URLClassLoader classLoader, URL url) throws InvocationTargetException, IllegalAccessException {        ADD_URL.invoke(classLoader, url);    }    /**     * 注入到引导类加载器     * @param instrumentation     * @param jarFile     */    public static void addURL(Instrumentation instrumentation, JarFile jarFile) {        instrumentation.appendToBootstrapClassLoaderSearch(jarFile);    }}

打包javaagent

采用上面的思路将javaagent用插件的方式实现,会导致每个插件都是一个jar包,不方便部署。可以用maven-assembly-plugin将javaagent核心代码和插件打成一个jar包,agent加载时,再将jar包解压,取出内嵌的插件包。解压后的dagent包结构如下:

或者,也可以借鉴spring-boot uber jar的处理方式,自定义Jar包结构和URL handler,这样就可以直接加载内嵌的jar包,不需要先解压。

转载于:https://www.cnblogs.com/segeon/p/10381080.html

你可能感兴趣的文章
【实战】颠覆银行基础架构的区块链
查看>>
第十六章:SpringCloud Config 配置自动刷新
查看>>
iOS APP内弹窗推送版本更新信息(实现跳转、强制更新等)
查看>>
Flutter 系列文章:Flutter Text 控件介绍
查看>>
二、SpringBoot配置文件讲解
查看>>
HTML基础:web前端建站流程
查看>>
http
查看>>
导航栏与scrollerview(或scrollerview的子类)
查看>>
建立个人Maven仓库
查看>>
阿里架构师手写Tomcat——Session源码解析
查看>>
世界杯来了!小程序赛事操作来一波~
查看>>
一个维护版本日志整洁的Git提交规范
查看>>
单例模式总结
查看>>
bootstrapSwitch bootstrap 的开关组件扩展
查看>>
冒泡排序
查看>>
阿里云 OSS 如何设置防盗链, 上个月图床流量耗费50G+,请求次数10W+,什么鬼?
查看>>
Node.js折腾记一(改进):文件夹目录树获取
查看>>
【机器学习】深度学习开发环境搭建
查看>>
Spring核心技术原理-(1)-通过Web开发演进过程了解一下为什么要有Spring?
查看>>
聊聊Elasticsearch RestClient的RequestLogger
查看>>