AspectJ 加载时间编织器无法检测到所有类

2022-09-04 21:46:50

我在“aspectj”模式下使用Spring的声明性事务(@Transactional注释)。在大多数情况下,它的工作方式与它应该的完全一样,但对于一个人来说,它没有。我们可以称之为(因为这就是它的实际名称)。Lang

我已经能够将问题精确定位到加载时间编织器。通过在 aop.xml 中打开调试和详细日志记录,它会列出所有正在编织的类。有问题的类确实根本没有在日志中提及。Lang

然后我在 的顶部放了一个断点,导致 Eclipse 在加载类时挂起线程。此断点在 LTW 编织其他类时被命中!所以我猜它要么试图编织,但失败了,没有输出,要么其他一些类有一个引用,迫使它在实际有机会编织它之前加载它。LangLangLangLang

但是,我不确定如何继续调试它,因为我无法以较小的规模重现它。关于如何继续的任何建议?


更新:其他线索也欢迎。例如,LTW实际上是如何工作的?似乎有很多魔术在发生。是否有任何选项可以从 LTW 获得更多的调试输出?我目前有:

<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">

我忘了汤姆之前提到过:弹簧剂被用来允许LTW,即.InstrumentationLoadTimeWeaver


根据安迪·克莱门特(Andy Clement)的建议,我决定检查AspectJ变压器是否曾经通过过该课程。我在 中放置了一个断点,似乎该类甚至从未到达该方法,尽管它由与其他类相同的类加载器(Jetty 的 WebAppClassLoader 的实例)加载。ClassPreProcessorAgent.transform(..)Lang

然后,我继续在 中放置了一个断点。甚至没有那个被击中.而且我认为应该为所有已装入的类调用该方法,无论它们使用什么类装入器。这开始看起来像这样:InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..)Lang

  1. 我的调试有问题。可能在 Eclipse 报告时未加载,它是Lang
  2. Java 错误?牵强附会,但我想它确实发生了。

下一个线索:我打开了,它似乎过早地加载了 - 可能是在将变压器添加到仪器仪表之前。奇怪的是,我的 Eclipse 断点没有捕获此加载。-verbose:classLang

这意味着春天是新的嫌疑人。似乎有一些处理,加载类来检查它们。这可能与我的问题有关。ConfigurationClassPostProcessor


中的这些行会导致类被读取:ConfigurationClassBeanDefinitionReaderLang

else if (metadata.isAnnotated(Component.class.getName()) ||
        metadata.hasAnnotatedMethods(Bean.class.getName())) {
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    return true;
}

特别是,调用类,该类加载该类中所有方法的所有参数类。我猜这可能不是问题的结束,因为我认为这些类应该被卸载。JVM 是否出于不可知的原因缓存类实例?metadata.hasAnnotatedMethods()getDeclaredMethods()


答案 1

好的,我已经解决了这个问题。从本质上讲,这是一个与一些自定义扩展相结合的Spring问题。如果有人遇到类似的事情,我会尝试一步一步地解释正在发生的事情。

首先,我们在项目中有一个自定义。此类具有以下定义:BeanDefintionParser

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    protected Class<?> getBeanClass(Element element) {
        try {
            return Class.forName(element.getAttribute("class"));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class " + element.getAttribute("class") + "not found.", e);
        }
    }

// code to parse XML omitted for brevity

}

现在,在所有豆定义都已阅读并开始启动之后,问题就出现了。在此阶段,一个名为 的类开始查看所有 Bean 定义,以搜索带有 注释的 Bean 类或具有 带有 .BeanDefinitionRegistryPostProcessorConfigurationClassPostProcessor@Configuration@Bean

在读取 Bean 的注释的过程中,它使用接口。对于大多数常规 Bean,使用一个名为的子类。但是,在解析 Bean 定义时,如果您已经重写了该方法以返回类实例,就像我们一样,则使用实例。当被调用时,它将调用 ,这反过来又导致类装入器装入该类中用作参数的所有类。以这种方式加载的类不会正确卸载,因此永远不会编织,因为这发生在AspectJ转换器注册之前。AnnotationMetadataAnnotationMetadataVisitorgetBeanClass()StandardAnnotationMetadataStandardAnnotationMetadata.hasAnnotatedMethods(..)Class.getDeclaredMethods()

现在,我的问题是我有一个这样的班级:

public class Something {
    private Lang lang;
    public void setLang(Lang lang) {
        this.lang = lang;
    }
}

然后,我有一个类的豆子,它是使用我们的自定义解析的。这触发了错误的注释检测过程,从而触发了意外的类加载,这意味着AspectJ从未有机会编织 。SomethingControllerBeanDefinitionParserLang

解决方案是不覆盖,而是覆盖,根据文档,这是可取的:getBeanClass(..)getBeanClassName(..)

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    protected String getBeanClassName(Element element) {
        return element.getAttribute("class");
    }

// code to parse XML omitted for brevity

}

每日课程:除非你真的是认真的,否则不要覆盖。实际上,不要尝试编写自己的BeanDefinitionParser,除非您知道自己在做什么。getBeanClass

鳍。


答案 2

如果你的类在 -verbose/-debug 输出中没有提到,这在我看来表明它没有被你认为是它的加载器加载。你能100%确定“Lang”不在层次结构中更高级别的类装入器的类路径上吗?哪个类装入器在您触发断点的时间点加载 Lang?

另外,你没有提到AspectJ版本 - 如果你在1.6.7上,除了一个微不足道的aop之外,ltw有任何问题.xml。您应该使用 1.6.8 或 1.6.9。

ltw是如何工作的?

简而言之,为每个可能想要编织代码的类加载器创建一个AspectJ编织器。系统会询问 AspectJ 是否要在将类定义为 VM 之前修改该类的字节。AspectJ查看任何aop.xml文件,它可以通过有问题的类加载器“看到”(作为资源),并使用它们来配置自身。配置完成后,它会按照指定的方式编织方面,同时考虑到所有包含/排除子句。

安迪·克莱门特
·阿奎德J项目负责人


推荐