• Skywalking流程分析_8(拦截器插件的加载)


    前言

    在之前的文章中我们将,静态方法、构造方法、实例方法的增强逻辑都分析完毕,但在增强前,对于拦截类的加载是至关重要的,下面我们就来详细的分析

    增强插件的加载

    • 静态方法增强前的加载
    //clazz 要修改的字节码的原生类
    StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz
                .getClassLoader());
    
    • 1
    • 2
    • 3
    • 构造/实例方法前的加载
    //当前拦截到的类的类加载器
    interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);
    
    • 1
    • 2

    问题

    可以看到静态方法增强可以直接通过clazz.getClassLoader()获取类加载器,而实例方法增强需要从上层方法传递ClassLoader,这是为什么?

    • 静态方法增强直接通过clazz.getClassLoader()获取类加载器是因为静态方法直接绑定了类
    • 构造/实例方法增强需要从上层传递ClassLoader有两个原因
      • 一份字节码可能被多个ClassLoader加载,这样加载出来的每个实例都不相等,所以必须要绑定好ClassLoader
      • 加载插件拦截器可能会出现无法加载的情况,如果把加载过程放到了intercept中,会和加载失败的异常和业务异常会混淆在一起,如果放到ConstructorInter的构造方法中进行加载,就会把异常分割开

    这个问题解决了,下面来详细加载的过程

    InterceptorInstanceLoader

    public class InterceptorInstanceLoader {
    
        private static ConcurrentHashMap<String, Object> INSTANCE_CACHE = new ConcurrentHashMap<String, Object>();
        private static ReentrantLock INSTANCE_LOAD_LOCK = new ReentrantLock();
        /**
         * key -> 当前插件要拦截的这个目标类的类加载器
         * value -> AgentClassLoader类加载器 作用:能加载当前插件,也能加载要拦截的这个目标类
         * */
        private static Map<ClassLoader, ClassLoader> EXTEND_PLUGIN_CLASSLOADERS = new HashMap<ClassLoader, ClassLoader>();
    
        /**
         * Load an instance of interceptor, and keep it singleton. Create {@link AgentClassLoader} for each
         * targetClassLoader, as an extend classloader. It can load interceptor classes from plugins, activations folders.
         *
         * @param className         the interceptor class, which is expected to be found
         * @param targetClassLoader the class loader for current application context
         * @param                expected type
         * @return the type reference.
         */
        public static <T> T load(
                //要进行增强逻辑的增强类
                String className,
                //当前拦截到的类的类加载器
                ClassLoader targetClassLoader) throws IllegalAccessException, InstantiationException, ClassNotFoundException, AgentPackageNotFoundException {
            if (targetClassLoader == null) {
                targetClassLoader = InterceptorInstanceLoader.class.getClassLoader();
            }
            //举例说明这个key值
            //com.test.MyTest_OF_com.test.classloader.MyTestClassLoader@123
            String instanceKey = className + "_OF_" + targetClassLoader.getClass()
                                                                       .getName() + "@" + Integer.toHexString(targetClassLoader
                .hashCode());
            // className所代表的拦截器的实例 对于同一个classloader而言相同的类只加载一次                                                           
            Object inst = INSTANCE_CACHE.get(instanceKey);
            if (inst == null) {
                INSTANCE_LOAD_LOCK.lock();
                ClassLoader pluginLoader;
                try {
                    pluginLoader = EXTEND_PLUGIN_CLASSLOADERS.get(targetClassLoader);
                    if (pluginLoader == null) {
                        /**
                         * <===========!!!重点!!!==========>
                         * 这里用AgentClassLoader并把targetClassLoader传入的原因是
                         * 要进行增强逻辑的增强类是由AgentClassLoader进行加载的,而要增强的目标类不知道是哪个类加载器。
                         * 但是增强类的逻辑是要在目标类中进行切入的,这就要求增强类和目标类的类加载器必须是同一个才行。
                         * 所以这里利用了类加载器的双亲委派机制来进行加载,将目标类的类加载器作为AgentClassLoader的父类加载器
                         * */
                        pluginLoader = new AgentClassLoader(targetClassLoader);
                        EXTEND_PLUGIN_CLASSLOADERS.put(targetClassLoader, pluginLoader);
                    }
                } finally {
                    INSTANCE_LOAD_LOCK.unlock();
                }
                // 通过pluginLoader来实例化拦截器对象
                inst = Class.forName(className, true, pluginLoader).newInstance();
                if (inst != null) {
                    INSTANCE_CACHE.put(instanceKey, inst);
                }
            }
    
            return (T) inst;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    总结

    • 对于每个插件的增强类都初始化了AgentClassLoader来加载增强类
    • 将当前拦截到的类的类加载器传入AgentClassLoader,作为其父的类加载器

    问题1:为什么将当前拦截到的类的类加载器传入AgentClassLoader,作为其父的类加载器

    targetClassLoader作为agentClassLoader的父类加载器,这样通过双亲委派模型模型,targetClassLoader可以加载应用系统中的类

    以阿里数据源druid举例:假设应用系统中数据源DruidDataSourceStatManager的类是由AppClassLoader加载的

    PoolingAddDruidDataSourceInterceptor要修改DruidDataSourceStatManager的字节码,两个类需要能交互,前提就是PoolingAddDruidDataSourceInterceptor能通过某种方式访问到DruidDataSourceStatManager


    AgentClassLoader的父类加载器指向加载druid的AppClassLoader,当PoolingAddDruidDataSourceInterceptor去操作DruidDataSourceStatManager类时,通过双亲委派机制,AgentClassLoader的父类加载器AppClassLoader能加载到DruidDataSourceStatManager

    问题2:为什么每个插件的增强类都要初始化一个AgentClassLoader来加载增强类,不能共用一个吗

    如果只实例化一个AgentClassLoader实例,由于应用系统中的类不存在于AgentClassLoader的classpath下,那此时AgentClassLoader加载不到应用系统中的类。

    比如说第一个业务类是由BootStrapClassLoader加载的,第二个业务类是由AppClassLoader加载的,根据双亲委派机制那么第二个业务类增强就会有问题,因为在一个业务类增强时AgentClassLoader的父的类加载器已经是BootStrapClassLoader了,是加载不到AppClassLoader的内容的

    以上关于非JDK类库的静态方法、构造方法、实例方法都已经分析完毕,后面的文章会详细分析JDK类库中的类是如何被增强拦截的

  • 相关阅读:
    深度学习5 神经网络
    第四章 :Spring Boot 配置文件指南
    linux文件的隐藏属性、特殊属性和ACL权限
    对称加密与非对称加密
    JavaSE 第十二章 IO流
    【Python/Pytorch - 网络模型】-- 高阶SVD算法
    算法排序之冒泡排序及优化
    maven打包时和 deploy时候将不会 依赖包含在生成的项目 jar中方法
    【面试题精讲】Java包装类缓存机制
    Linux操作系统——校招高频考点汇总
  • 原文地址:https://blog.csdn.net/guntun8987/article/details/134435628