我在“ 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转换器是否曾经通过该类。我在中设置了一个断点,尽管该类由与其他类(Jetty的WebAppClassLoader
的一个实例)相同的类加载器加载,但该类ClassPreProcessorAgent.transform(..)
似乎Lang从未达到该方法。
然后,我继续在中设置一个断点InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..)
。连一个都没有被击中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是否可能由于不可知的原因在缓存类实例?
好,我已经解决了问题。本质上,与一些自定义扩展一起,这是一个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