在 spring 引导应用程序中使用 AspectJ 加载时间编织时生成结果不一致
Inconsistent build results when using AspectJ Load Time Weaving in a spring boot application
目前我正在使用 AspectJ 加载时间编织来拦截基本实体的构造函数以用于审计目的。然而,当 运行 应用程序时,我得到了令人难以置信的不一致结果,围绕着 aspectJ 编织到 LTW classes 中的 aspectOf() 方法。
在某些情况下,应用程序 运行s 编织正确完成,代码按预期运行。其他时候我会遇到:
java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
目前我正在使用 https://github.com/subes/invesdwin-instrument 将检测代理动态附加到 JVM 中,因此我们的部署人员不需要进行任何额外的配置。
我的 Spring 应用程序主要:
@SpringBootApplication
@EntityScan(basePackages = {"ca.gc.cfp.model"})
public class CfpWsApplication {
public static void main(final String[] args) {
DynamicInstrumentationLoader.waitForInitialized();
DynamicInstrumentationLoader.initLoadTimeWeavingContext();
if (!InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
throw new IllegalStateException(
"Instrumentation is not available AspectJ weaver will not function.");
}
SpringApplication.run(CfpWsApplication.class, args);
}
LTW 方面:
@Aspect
public class BaseEntityAspect {
Logger logger = LoggerFactory.getLogger(BaseEntityAspect.class);
/** Application context property needed to fetch the AuditDate bean */
@Autowired private ApplicationContext context;
@AfterReturning(
"onBaseEntityCreated() && !within(ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect)")
public void injectAuditTimeStamp(JoinPoint joinPoint) {
try {
AuditDate auditDate = context.getBean(AuditDate.class);
Object entityTarget = joinPoint.getTarget();
// Inject the auditing date for this Entity instance
if (entityTarget instanceof BaseEntity) {
BaseEntity baseEnt = (BaseEntity) entityTarget;
baseEnt.setAuditDate(auditDate.getAuditingTimeStamp());
}
} catch (NullPointerException e) {
logger.error(
e.getMessage()
+ " Not yet in the conext of an httpRequest, the AuditDate bean has not yet been instantiated.");
}
}
@Pointcut(
"execution(ca.gc.cfp.model.entity.BaseEntity.new(..)) && !within(ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect)")
public void onBaseEntityCreated() {}
}
Aspect 配置 class 使用 Aspects utils 的临时工厂方法让 spring 知道它应该向 aspectJ 请求 woven Aspect:
@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
/**
* Static factory for access to the load time woven aspect. This allows the aspect to be injected
* with the application context and beans from the spring IoC
*/
@Bean
public BaseEntityAspect getBaseEntityAspect() {
return Aspects.aspectOf(BaseEntityAspect.class);
}
}
aop.xml:
<aspectj>
<weaver options="-verbose -showWeaveInfo -Xreweavable -debug">
<include within="ca.gc.cfp.model" />
<include within="ca.gc.cfp.model..*" />
<include within="ca.gc.cfp.core.cfpws.repository.aspect..*"/>
</weaver>
<aspects>
<aspect name="ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect" />
</aspects>
</aspectj>
在很多情况下,当我使用此配置 运行 时,我会得到以下结果:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect]: Factory method 'getBaseEntityAspect' threw exception; nested exception is org.aspectj.lang.NoAspectBoundException: Exception while initializing ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
... 19 common frames omitted
Caused by: org.aspectj.lang.NoAspectBoundException: Exception while initializing ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
at org.aspectj.lang.Aspects.aspectOf(Aspects.java:50) ~[aspectjrt-1.9.4.jar:1.9.4]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig.getBaseEntityAspect(AspectConfig.java:22) ~[classes/:na]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$cae4c58.CGLIB$getBaseEntityAspect[=16=](<generated>) ~[classes/:na]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$cae4c58$$FastClassBySpringCGLIB$edb9e.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$cae4c58.getBaseEntityAspect(<generated>) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_211]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_211]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_211]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_211]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
... 20 common frames omitted
Caused by: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
at java.lang.Class.getDeclaredMethod(Class.java:2130) ~[na:1.8.0_211]
at org.aspectj.lang.Aspects.getSingletonOrThreadAspectOf(Aspects.java:134) ~[aspectjrt-1.9.4.jar:1.9.4]
at org.aspectj.lang.Aspects.aspectOf(Aspects.java:45) ~[aspectjrt-1.9.4.jar:1.9.4]
... 31 common frames omitted
然而,情况并非总是如此,有时应用程序 运行 正常,代码按预期执行,没有任何问题。这已经困扰我几个星期了,我似乎无法弄清楚是什么导致这段代码偶尔工作,偶尔不工作。会不会是 CGLIB 代理对编译器隐藏了 aspectOf() 方法?
编辑/更新:我能够删除使用上述第三方依赖项将 java 代理动态加载到 Spring 应用程序上下文中。我改为使用 java 代理参数,它在我的 IDE 中运行良好。然而,在终端中通过 Maven 构建和 运行ning 仍然会导致问题。我已经通过环境变量指定了 javaagent 参数:MAVEN_OPTS。这样做之后,maven 似乎正在捡起它,但我的 class 仍然没有被编织。
根据 Kieveli 的评论。我的 TL 也有类似的想法。我是 Maven、Spring 引导和 AOP 的新手。从外部查看 运行 Maven 和从 Eclipse 内部构建似乎是 "clobbering" 彼此。
我一直在通过 MINGW64 使用 maven 来清理/安装/构建,并且项目没有指定 AspectJ 编译器的用法,这可能是工厂方法没有被编织到 class 中的原因.
我们正在讨论解决此问题的方法,因为我们使用 Jenkins 在我们的服务器上进行构建自动化。我认为这个 Maven 插件可能是解决方案。 https://www.mojohaus.org/aspectj-maven-plugin/
关于您自己的回答:AspectJ Maven 插件帮助您进行编译时织入 (CTW),而不是加载时织入 (LTW)。对于 LTW,您需要确保编织代理在 在 任何目标 class 加载之前处于活动状态,因为 LTW 在 class-loader 级别工作。 AspectJ Maven 只有在您使用本机语法(不是基于注释的)方面或您想使用 CTW 时才是必需的。
我不知道这个 invesdwin-instrument 工具,但是 AspectJ 编织器提供了它自己的功能,可以在运行时附加它。不需要第三方工具。无论如何,我确实建议修改 Java 命令行,以确保编织代理在容器中加载任何其他内容之前就位。您的部署人员应该帮助您确保 -javaagent:...
参数存在。这是他们的工作!解决这个问题是为了让他们的生活更轻松,但您的应用程序的潜在不可预测的行为不会提高其稳定性。
目前我正在使用 AspectJ 加载时间编织来拦截基本实体的构造函数以用于审计目的。然而,当 运行 应用程序时,我得到了令人难以置信的不一致结果,围绕着 aspectJ 编织到 LTW classes 中的 aspectOf() 方法。
在某些情况下,应用程序 运行s 编织正确完成,代码按预期运行。其他时候我会遇到:
java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
目前我正在使用 https://github.com/subes/invesdwin-instrument 将检测代理动态附加到 JVM 中,因此我们的部署人员不需要进行任何额外的配置。
我的 Spring 应用程序主要:
@SpringBootApplication
@EntityScan(basePackages = {"ca.gc.cfp.model"})
public class CfpWsApplication {
public static void main(final String[] args) {
DynamicInstrumentationLoader.waitForInitialized();
DynamicInstrumentationLoader.initLoadTimeWeavingContext();
if (!InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
throw new IllegalStateException(
"Instrumentation is not available AspectJ weaver will not function.");
}
SpringApplication.run(CfpWsApplication.class, args);
}
LTW 方面:
@Aspect
public class BaseEntityAspect {
Logger logger = LoggerFactory.getLogger(BaseEntityAspect.class);
/** Application context property needed to fetch the AuditDate bean */
@Autowired private ApplicationContext context;
@AfterReturning(
"onBaseEntityCreated() && !within(ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect)")
public void injectAuditTimeStamp(JoinPoint joinPoint) {
try {
AuditDate auditDate = context.getBean(AuditDate.class);
Object entityTarget = joinPoint.getTarget();
// Inject the auditing date for this Entity instance
if (entityTarget instanceof BaseEntity) {
BaseEntity baseEnt = (BaseEntity) entityTarget;
baseEnt.setAuditDate(auditDate.getAuditingTimeStamp());
}
} catch (NullPointerException e) {
logger.error(
e.getMessage()
+ " Not yet in the conext of an httpRequest, the AuditDate bean has not yet been instantiated.");
}
}
@Pointcut(
"execution(ca.gc.cfp.model.entity.BaseEntity.new(..)) && !within(ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect)")
public void onBaseEntityCreated() {}
}
Aspect 配置 class 使用 Aspects utils 的临时工厂方法让 spring 知道它应该向 aspectJ 请求 woven Aspect:
@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
/**
* Static factory for access to the load time woven aspect. This allows the aspect to be injected
* with the application context and beans from the spring IoC
*/
@Bean
public BaseEntityAspect getBaseEntityAspect() {
return Aspects.aspectOf(BaseEntityAspect.class);
}
}
aop.xml:
<aspectj>
<weaver options="-verbose -showWeaveInfo -Xreweavable -debug">
<include within="ca.gc.cfp.model" />
<include within="ca.gc.cfp.model..*" />
<include within="ca.gc.cfp.core.cfpws.repository.aspect..*"/>
</weaver>
<aspects>
<aspect name="ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect" />
</aspects>
</aspectj>
在很多情况下,当我使用此配置 运行 时,我会得到以下结果:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect]: Factory method 'getBaseEntityAspect' threw exception; nested exception is org.aspectj.lang.NoAspectBoundException: Exception while initializing ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
... 19 common frames omitted
Caused by: org.aspectj.lang.NoAspectBoundException: Exception while initializing ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
at org.aspectj.lang.Aspects.aspectOf(Aspects.java:50) ~[aspectjrt-1.9.4.jar:1.9.4]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig.getBaseEntityAspect(AspectConfig.java:22) ~[classes/:na]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$cae4c58.CGLIB$getBaseEntityAspect[=16=](<generated>) ~[classes/:na]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$cae4c58$$FastClassBySpringCGLIB$edb9e.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$cae4c58.getBaseEntityAspect(<generated>) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_211]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_211]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_211]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_211]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
... 20 common frames omitted
Caused by: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
at java.lang.Class.getDeclaredMethod(Class.java:2130) ~[na:1.8.0_211]
at org.aspectj.lang.Aspects.getSingletonOrThreadAspectOf(Aspects.java:134) ~[aspectjrt-1.9.4.jar:1.9.4]
at org.aspectj.lang.Aspects.aspectOf(Aspects.java:45) ~[aspectjrt-1.9.4.jar:1.9.4]
... 31 common frames omitted
然而,情况并非总是如此,有时应用程序 运行 正常,代码按预期执行,没有任何问题。这已经困扰我几个星期了,我似乎无法弄清楚是什么导致这段代码偶尔工作,偶尔不工作。会不会是 CGLIB 代理对编译器隐藏了 aspectOf() 方法?
编辑/更新:我能够删除使用上述第三方依赖项将 java 代理动态加载到 Spring 应用程序上下文中。我改为使用 java 代理参数,它在我的 IDE 中运行良好。然而,在终端中通过 Maven 构建和 运行ning 仍然会导致问题。我已经通过环境变量指定了 javaagent 参数:MAVEN_OPTS。这样做之后,maven 似乎正在捡起它,但我的 class 仍然没有被编织。
根据 Kieveli 的评论。我的 TL 也有类似的想法。我是 Maven、Spring 引导和 AOP 的新手。从外部查看 运行 Maven 和从 Eclipse 内部构建似乎是 "clobbering" 彼此。
我一直在通过 MINGW64 使用 maven 来清理/安装/构建,并且项目没有指定 AspectJ 编译器的用法,这可能是工厂方法没有被编织到 class 中的原因.
我们正在讨论解决此问题的方法,因为我们使用 Jenkins 在我们的服务器上进行构建自动化。我认为这个 Maven 插件可能是解决方案。 https://www.mojohaus.org/aspectj-maven-plugin/
关于您自己的回答:AspectJ Maven 插件帮助您进行编译时织入 (CTW),而不是加载时织入 (LTW)。对于 LTW,您需要确保编织代理在 在 任何目标 class 加载之前处于活动状态,因为 LTW 在 class-loader 级别工作。 AspectJ Maven 只有在您使用本机语法(不是基于注释的)方面或您想使用 CTW 时才是必需的。
我不知道这个 invesdwin-instrument 工具,但是 AspectJ 编织器提供了它自己的功能,可以在运行时附加它。不需要第三方工具。无论如何,我确实建议修改 Java 命令行,以确保编织代理在容器中加载任何其他内容之前就位。您的部署人员应该帮助您确保 -javaagent:...
参数存在。这是他们的工作!解决这个问题是为了让他们的生活更轻松,但您的应用程序的潜在不可预测的行为不会提高其稳定性。