greys-anatomy icon indicating copy to clipboard operation
greys-anatomy copied to clipboard

Spy class是如何在各个业务classloader 中加载的?

Open shidongwa opened this issue 5 years ago • 1 comments

请教各位大神:

看greys-anatomy/core/src/main/java/com/github/ompc/greys/core/advisor/Enhancer.java 中的代码,是把spyClassFromGreysClassLoader中的Spy.class 拷一份到 spyClassFromTargetClassLoader中去加载,前提是spyClassFromTargetClassLoader中找不到Spy.class的话。问题是Spy.class是打包在greys-agent.jar 而不是 grey-core.jar 中。spyClassFromGreysClassLoader实际从grey-core.jar是找不到这个Spy.class的,最终还是通过父classloader加载。这里是bootstrap classloader。既然Spy.class 是通过bootstrap加载了,那么spyClassFromTargetClassLoader走的是父委托方式,都会找到bootstrap中的Spy.class, 也就不需要在spyClassFromTargetClassLoader中分别加载了。这不是没有达到“派遣间谍混入对方的classLoader中”的目的么?

` /* * 派遣间谍混入对方的classLoader中 */ private void spy(final ClassLoader targetClassLoader) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {

    // 如果对方是bootstrap就算了
    if (null == targetClassLoader) {
        return;
    }


    // Enhancer类只可能从greysClassLoader中加载
    // 所以找他要ClassLoader是靠谱的
    final ClassLoader greysClassLoader = Enhancer.class.getClassLoader();

    final String spyClassName = GaStringUtils.SPY_CLASSNAME;

    // 从GreysClassLoader中加载Spy
    final Class<?> spyClassFromGreysClassLoader = loadSpyClassFromGreysClassLoader(greysClassLoader, spyClassName);
    if (null == spyClassFromGreysClassLoader) {
        return;
    }

    // 从目标ClassLoader中尝试加载或定义ClassLoader
    Class<?> spyClassFromTargetClassLoader = null;
    try {

        // 去目标类加载器中找下是否已经存在间谍
        // 如果间谍已经存在就算了
        spyClassFromTargetClassLoader = targetClassLoader.loadClass(spyClassName);
        logger.info("Spy already in targetClassLoader : " + targetClassLoader);

    }

    // 看来间谍不存在啊
    catch (ClassNotFoundException cnfe) {

        try {// 在目标类加载器中混入间谍
            spyClassFromTargetClassLoader = defineClass(
                    targetClassLoader,
                    spyClassName,
                    toByteArray(Enhancer.class.getResourceAsStream("/" + spyClassName.replace('.', '/') + ".class"))
            );
        } catch (InvocationTargetException ite) {
            if (ite.getCause() instanceof java.lang.LinkageError) {
                // CloudEngine 由于 loadClass 不到,会导致 java.lang.LinkageError: loader (instance of  com/alipay/cloudengine/extensions/equinox/KernelAceClassLoader): attempted  duplicate class definition for name: "com/taobao/arthas/core/advisor/Spy"
                // 这里尝试忽略
                logger.debug("resolve #112 issues", ite);
            } else {
                throw ite;
            }
        }

    }


    // 无论从哪里取到spyClass,都需要重新初始化一次
    // 用以兼容重新加载的场景
    // 当然,这样做会给渲染的过程带来一定的性能开销,不过能简化编码复杂度
    finally {

        if (null != spyClassFromTargetClassLoader) {
            // 初始化间谍
            invokeStaticMethod(
                    spyClassFromTargetClassLoader,
                    "init",
                    greysClassLoader,
                    getField(spyClassFromGreysClassLoader, "ON_BEFORE_METHOD").get(null),
                    getField(spyClassFromGreysClassLoader, "ON_RETURN_METHOD").get(null),
                    getField(spyClassFromGreysClassLoader, "ON_THROWS_METHOD").get(null),
                    getField(spyClassFromGreysClassLoader, "BEFORE_INVOKING_METHOD").get(null),
                    getField(spyClassFromGreysClassLoader, "AFTER_INVOKING_METHOD").get(null),
                    getField(spyClassFromGreysClassLoader, "THROW_INVOKING_METHOD").get(null)
            );
        }

    }

}

`

shidongwa avatar Dec 03 '19 15:12 shidongwa

这段代码的目的是在运行时动态地将"Spy"类注入到目标ClassLoader中,以便在目标应用程序中执行特定的监控和拦截操作。该注入的目的是在运行时监控和操纵目标应用程序的方法调用。

以下是代码的关键部分的解释:

首先,代码获取了当前类(Enhancer)的ClassLoader,即greysClassLoader,用于加载Spy类。

然后,它尝试从greysClassLoader中加载Spy类,以便后续在目标ClassLoader中使用。这是为了确保Spy类在greysClassLoader中可用。

接下来,代码检查是否在目标ClassLoader中已经存在Spy类。如果存在,就没有必要重新加载。

如果在目标ClassLoader中没有找到Spy类(ClassNotFoundException),则代码会尝试将Spy类定义到目标ClassLoader中。这是通过读取Spy.class的字节码并使用ClassLoader的defineClass方法完成的。

最后,不管Spy类是否从greysClassLoader还是从目标ClassLoader中加载,都会执行初始化操作,以确保Spy类能够正常工作。

在整个过程中,代码确保Spy类在greysClassLoader中加载,因为Spy类的功能需要使用greys的ClassLoader来注入到目标ClassLoader中,以便在目标应用程序中执行拦截和监控操作。

尽管Spy类可能已经通过bootstrap ClassLoader加载,但在目标ClassLoader中重新加载Spy类是为了确保其在目标应用程序中的可用性,并允许greys在目标应用程序中执行必要的操作。这个过程是动态的,根据需要在运行时进行的。

总之,这段代码的目的是确保Spy类在目标ClassLoader中加载并能够正常工作,以便greys可以监控和拦截目标应用程序中的方法调用。

ljluestc avatar Nov 23 '23 17:11 ljluestc