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

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

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

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

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


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

  

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


根据安迪克莱门特的建议,我决定检查AspectJ变压器是否甚至通过了这门课程。 我在ClassPreProcessorAgent.transform(..)放了一个断点,看起来Lang类甚至都没有到达那个方法,尽管它和其他类(Jetty的WebAppClassLoader的一个实例)一样被类加载器加载。

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

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

接下来的线索:我打开了-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; } } 

然后,我有一个使用我们的自定义ControllerBeanDefinitionParser解析的Something类的bean。 这触发了错误的注释检测过程,触发了意外的类加载,这意味着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它,否则不要覆盖getBeanClass 。 实际上,除非你知道自己在做什么,否则不要尝试编写自己的BeanDefinitionParser。

鳍。

如果你的类没有在-verbose / -debug输出中提到,那就告诉我它没有被你认为的加载器加载。 你能100%确定’Lang’不在层次结构中更高级别的类加载器的类路径中吗? 哪个类加载器在触发断点时加载Lang?

另外,你没有提到AspectJ版本 – 如果你在1.6.7上遇到了一个问题,除了一个简单的aop.xml之外什么都没有问题。 你应该是1.6.8或1.6.9。

如何实际工作?

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

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

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

选项2)将您的class级重命名为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”但它可能在那里(或一个bug)