当前位置: 首页 > 面试题库 >

AspectJ加载时间weaver不会检测到所有类

姜永贞
2023-03-14
问题内容

我在“ aspectj”模式下使用Spring的声明式事务(@Transactional批注)。在大多数情况下,它的工作原理与应有的情况完全相同,但有一种情况却没有。我们可以调用它Lang(因为这就是它的实际名称)。

我已经能够找出问题的原因是加载时间的编织者。通过在aop.xml中打开调试和详细日志记录,它列出了所有编织的类。Lang确实在日志中根本没有提到有问题的类。

然后,我在的顶部放置一个断点Lang,导致EclipseLang加载类时挂起线程。在LTW编织其他类时会遇到此断点!因此,我猜想它要么尝试编织Lang并失败并且不输出,要么其他类具有一个引用,迫使它Lang在实际上没有机会编织之前进行加载。

但是我不确定如何继续调试它,因为我无法以较小的比例复制它。有什么建议吗?

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

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

我忘记了要提到它:spring-agent用于允许LTW,即InstrumentationLoadTimeWeaver

根据Andy Clement的建议,我决定检查AspectJ转换器是否曾经通过该类。我在中设置了一个断点,尽管该类由与其他类(Jetty的WebAppClassLoader的一个实例)相同的类加载器加载,但该类ClassPreProcessorAgent.transform(..)似乎Lang从未达到该方法。

然后,我继续在中设置一个断点InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..)。连一个都没有被击中Lang。而且我相信应该为所有已加载的类调用该方法,无论它们使用的是哪种类加载器。它开始看起来像:

  1. 我的调试有问题。Lang当Eclipse报告它已被加载时,可能未加载
  2. Java错误?牵强,但我想它确实发生了。
    下一条线索:我打开电源-verbose:class,好像Lang 是过早加载了-可能是在将变压器添加Instrumentation之前。奇怪的是,我的Eclipse断点无法捕获此负载。

这意味着Spring是新的犯罪嫌疑人。在ConfigurationClassPostProcessor加载类中似乎要进行一些检查。这可能与我的问题有关。

这些行ConfigurationClassBeanDefinitionReader导致Lang读取该类:

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

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


问题答案:

好,我已经解决了问题。本质上,与一些自定义扩展一起,这是一个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定义并BeanDefinitionRegistryPostProcessor开始执行之后,就会出现问题。在此阶段,一个名为的类ConfigurationClassPostProcessor开始浏览所有bean定义,以搜索带有注释@Configuration或具有方法的Bean类@Bean

在读取bean的注释的过程中,它使用AnnotationMetadata接口。对于大多数常规bean,使用一个称为的子类AnnotationMetadataVisitor。但是,在解析Bean定义时,如果您已重写getBeanClass()方法以返回类实例(如我们以前的方法),则将StandardAnnotationMetadata使用实例。当StandardAnnotationMetadata.hasAnnotatedMethods(..)被调用时,它调用Class.getDeclaredMethods(),这又导致的类加载器加载用作该类参数的所有类。以这种方式加载的类未正确卸载,因此从不进行编织,因为这是在AspectJ转换器注册之前发生的。

现在,我的问题是我上了这样的课:

public class Something {
private Lang lang;
public void setLang(Lang lang) {
this.lang = lang;
}
}
然后,我有了一个Something使用我们的custom解析的类bean ControllerBeanDefinitionParser。这触发了错误的注释检测过程,从而触发了意外的类加载,这意味着AspectJ从未有过编织的机会Lang

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

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

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

// code to parse XML omitted for brevity

}

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



 类似资料:
  • 如果我使用的是基于AspectJ的Spring AOP,那么我是否需要配置我的方面来使用加载时间编织?或者Spring AOP在使用基于AspectJ的方法时也支持运行时/编译时编织吗?

  • 和位于同一个包中,编织工作在直接实例化的包上,而不是bean返回的包上。 我搜索了Spring AOP文档,但似乎找不到任何与此相关的内容。对于自动代理,您需要做一些魔术,对于SpringAOP也需要做一些限制,但是加载时间编织就我所知应该可以工作--例如,我已经尝试过私有方法,它可以工作。

  • 我参与了一个较老项目的审查任务。任务是将某些库更新到更新的版本。这个项目成功地使用了spring(4.3.14.Release),以及JDK 8下的AspectJ(1.9.0)和Tomcat8.0.20。现在spring将更新到最新版本(目前为5.3.3),Tomcat版本也将提升到最新版本(目前为9.0.37)。服务器应该在JDK11下运行。升级库后,我们意识到AspectJ不再工作了。所以我开

  • 我通过jUnit测试执行这个。我正在分叉执行,以便可以传入JavaAgent。 并设置了aop.xml文件 生成的日志看起来一切都很好。 [junit]信息[main](DefaultContextLoadTimeWeaver.java:73)-找到Spring的JVM instrumentation代理[junit][AppClassLoader@12360BE0]信息AspectJ Weave

  • 尝试运行我的XML时出现以下错误。看起来它有问题加载类,但我有正确的类路径。 org.testng.testngexception:org.testng.testng.initializeSuitesandjarFile(testng.java:340)(org.testng.remote.abstractremotetestng.run(abstractremotetestng.java:109