AspectJ 加载时编织器未检测到所有类

发布于 2024-09-16 17:30:45 字数 2046 浏览 8 评论 0原文

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

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

然后我在 Lang 的顶部放置了一个断点,导致 Eclipse 在加载 Lang 类时挂起线程。当 LTW 编织其他类时会命中断点!所以我猜测它要么尝试编织 Lang 并且失败并且不输出,或者其他一些类有一个引用,强制它在实际获取之前加载 Lang有机会编织它。

然而,我不确定如何继续调试它,因为我无法以较小的规模重现它。关于如何继续下去有什么建议吗?


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

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

我忘了汤姆之前提到过:spring-agent 用于允许 LTW,即 InstrumentationLoadTimeWeaver


根据 Andy Clement 的建议,我决定检查 AspectJ 变压器是否通过了该课程。我在 ClassPreProcessorAgent.transform(..) 中放置了一个断点,看起来 Lang 类甚至从未到达该方法,尽管它是由与其他类(Jetty 的 WebAppClassLoader 的实例)。

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

  1. 我的调试出现问题。当 Eclipse 报告
  2. Java bug 时,可能 Lang 尚未加载?牵强,但我想它确实发生了。

下一条线索:我打开了 -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 是否会出于不可知的原因缓存类实例?

I am using Spring's declarative transactions (the @Transactional annotation) in "aspectj" mode. It works in most cases exactly like it should, but for one it doesn't. We can call it Lang (because that's what it's actually called).

I have been able to pinpoint the problem to the load time weaver. By turning on debug and verbose logging in aop.xml, it lists all classes being woven. The problematic class Lang is indeed not mentioned in the logs at all.

Then I put a breakpoint at the top of Lang, causing Eclipse to suspend the thread when the Lang class is loaded. This breakpoint is hit while the LTW weaving other classes! So I am guessing it either tries to weave Lang and fails and doesn't output that, or some other class has a reference that forces it to load Lang before it actually gets a chance to weave it.

I am unsure however how to continue to debug this, since I am not able to reproduce it in smaller scale. Any suggestions on how to go on?


Update: Other clues are also welcome. For example, how does the LTW actually work? There appears to be a lot of magic happening. Are there any options to get even more debug output from the LTW? I currently have:

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

I forgot tom mention it before: spring-agent is being used to allow LTW, i.e., the InstrumentationLoadTimeWeaver.


Based on the suggestions of Andy Clement I decided to inspect whether the AspectJ transformer is ever even passed the class. I put a breakpoint in ClassPreProcessorAgent.transform(..), and it seems that the Lang class never even reaches that method, despite it being loaded by the same class loader as other classes (an instance of Jetty's WebAppClassLoader).

I then went on to put a breakpoint in InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..). Not even that one is hit for Lang. And I believe that method should be invoked for all loaded classes, regardless of what class loader they are using. This is starting to look like:

  1. A problem with my debugging. Possibly Lang is not loaded at the time when Eclipse reports it is
  2. Java bug? Far-fetched, but I suppose it does happen.

Next clue: I turned on -verbose:class and it appears as if Lang is being loaded prematurely - probably before the transformer is added to Instrumentation. Oddly, my Eclipse breakpoint does not catch this loading.

This means that Spring is new suspect. there appears to be some processing in ConfigurationClassPostProcessor that loads classes to inspect them. This could be related to my problem.


These lines in ConfigurationClassBeanDefinitionReader causes the Lang class to be read:

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

In particular, metadata.hasAnnotatedMethods() calls getDeclaredMethods() on the class, which loads all parameter classes of all methods in that class. I am guessing that this might not be the end of the problem though, because I think the classes are supposed to be unloaded. Could the JVM be caching the class instance for unknowable reasons?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

青巷忧颜 2024-09-23 17:30:46

好的,我已经解决了问题。本质上,这是一个 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 类。

在读取 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 类的 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。

鳍。

OK, I have solved the problem. Essentially, it is a Spring problem in conjunction with some custom extensions. If anyone comes across something similar, I will try to explain step by step what is happening.

First of all, we have a custom BeanDefintionParser in our project. This class had the following definition:

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

}

Now, the problem occurs after all bean definition have been read and BeanDefinitionRegistryPostProcessor begins to kick in. At this stage, a class called ConfigurationClassPostProcessor starts looking through all bean definitions, to search for bean classes annotated with @Configuration or that have methods with @Bean.

In the process of reading annotations for a bean, it uses the AnnotationMetadata interface. For most regular beans, a subclass called AnnotationMetadataVisitor is used. However, when parsing the bean definitions, if you have overriden the getBeanClass() method to return a class instance, like we had, instead a StandardAnnotationMetadata instance is used. When StandardAnnotationMetadata.hasAnnotatedMethods(..) is invoked, it calls Class.getDeclaredMethods(), which in turn causes the class loader to load all classes used as parameters in that class. Classes loaded this way are not correctly unloaded, and thus never weaved, since this happens before the AspectJ transformer registered.

Now, my problem was that I had a class like so:

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

Then, I had a bean of class Something that was parsed using our custom ControllerBeanDefinitionParser. This triggered the wrong annotation detection procedure, which triggered unexpected class loading, which meant that AspectJ never got a chance to weave Lang.

The solution was to not override getBeanClass(..), but instead override getBeanClassName(..), which according to the documentation is preferable:

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

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

// code to parse XML omitted for brevity

}

Lesson of the day: Do not override getBeanClass unless you really mean it. Actually, don't try to write your own BeanDefinitionParser unless you know what you're doing.

Fin.

北音执念 2024-09-23 17:30:46

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

另外,你没有提到 AspectJ 版本 - 如果你使用的是 1.6.7,除了一个简单的 aop.xml 之外,其他任何东西都与 ltw 有问题。您应该使用 1.6.8 或 1.6.9。

ltw实际上是如何工作的?

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

安迪·克莱门特
AspectJ 项目负责人

If your class is not mentioned in the -verbose/-debug output, that suggests to me it is not being loaded by the loader you think it is. Can you be 100% sure that 'Lang' isn't on the classpath of a classloader higher in the hierarchy? Which classloader is loading Lang at the point in time when you trigger your breakpoint?

Also, you don't mention AspectJ version - if you are on 1.6.7 that had issues with ltw for anything but a trivial aop.xml. You should be on 1.6.8 or 1.6.9.

How does ltw actually work?

Put simply, an AspectJ weaver is created for each classloader that may want to weave code. AspectJ is asked if it wants to modify the bytes for a class before it is defined to the VM. AspectJ looks at any aop.xml files it can 'see' (as resources) through the classloader in question and uses them to configure itself. Once configured it weaves the aspects as specified, taking into account all include/exclude clauses.

Andy Clement
AspectJ Project Lead

甜中书 2024-09-23 17:30:46

选项 1) Aspect J 是开源的。打开它,看看发生了什么。

选项2)将你的类重命名为Bang,看看它是否开始工作,

如果有硬编码来跳过其中的“lang”,我不会感到惊讶,尽管我不能说出原因。

编辑 -

在源代码中看到这样的代码

        if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object
            superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class);
            superclassname = Utility.compactClassName(superclassname, false);

} else {
            superclassname = "java.lang.Object";
        }

看起来就像他们试图跳过 java.lang.stuff 的编织一样......没有看到任何“lang”的东西,但它可能存在(或错误)

Option 1) Aspect J is open source. Crack it open and see what is going on.

Option 2) Rename your class to Bang, see if it starts working

I would not be surprised if there is hard coding to skip "lang' in there, though I can't say why.

Edit -

Seeing code like this in the source

        if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object
            superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class);
            superclassname = Utility.compactClassName(superclassname, false);

} else {
            superclassname = "java.lang.Object";
        }

Looks like they are trying to skip weaving of java.lang.stuff.... don't see anything for just "lang" but it may be there (or a bug)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文