0

Currently I am using AspectJ Load Time Weaving to intercept the constructor of a base entity for auditing purposes. However when running the application I am getting incredibly inconsistent results revolving around the aspectOf() method that aspectJ weaves into LTW classes.

In some cases the application runs, the weaving is done correctly, and the code is functioning as expected. Other times I am met with:

java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf() 

Currently I am using https://github.com/subes/invesdwin-instrument to dynamically attach the instrumentation agent into the JVM so our deployment guys don't need to do any extra configuration.

My Spring application main:

@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);
  }

The LTW Aspect:

@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() {}
}

The Aspect config class with a temporary factory method using the Aspects utils to let spring know it should ask aspectJ for the 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>

In a lot of cases when I run with this configuration I get the following:

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$$1cae4c58.CGLIB$getBaseEntityAspect$0(<generated>) ~[classes/:na]
    at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$$1cae4c58$$FastClassBySpringCGLIB$$84edb9e.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$$1cae4c58.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

However this is not always the case, occasionally the application runs just fine, the code executes as expected and there are no issues. This has been eluding me for a couple weeks now and I cannot seem to figure out what could be causing this code to occasionally work and occasionally not work. Could it be that CGLIB proxies are hiding the aspectOf() method from the compiler??

EDIT / UPDATE: I was able to remove the use of the above third party dependency for dynamically loading the java agent into the Spring application context. I instead used javaagent arguments and it is working fine from within my IDE. However building and running via Maven in a terminal is still causing issues. I've specified the javaagent argument via the Environment variable : MAVEN_OPTS. After doing so maven seems to be picking it up but my class still isn't being woven.

Pensai
  • 9
  • 7
  • Or maybe your IDE build and maven build are clobbering each other? One is configured correctly, and the other is not? – Kieveli Sep 04 '19 at 16:32

2 Answers2

0

As per Kieveli's comment. My TL was thinking similarly. I'm new to Maven, Spring boot, and AOP. Looking into it externally run Maven and the build from within Eclipse seem to be "clobbering" one another.

I've been using maven via MINGW64 to clean / install / build, and the project doesn't specify usage of the AspectJ compiler which is likely why the factory method is not being woven into the class.

We're discussing a solution to this as we use Jenkins for build automation on our server. I am thinking that this Maven plugin may be the solution. https://www.mojohaus.org/aspectj-maven-plugin/

Pensai
  • 9
  • 7
0

With regard to your own answer: The AspectJ Maven plugin helps you with compile-time weaving (CTW), not load-time weaving (LTW). For LTW you need to make sure that the weaving agent is active before any of the target classes have been loaded because LTW works at class-loader level. AspectJ Maven is only necessary if either you use aspects in native syntax (not annotation-based) or you want to use CTW.

I don't know about this invesdwin-instrument tool, but the AspectJ weaver offers its own capability to attach it during runtime. A third-party tool should not be necessary. Anyway, I do recommend to modify the Java command line in order to ensure the weaving agent is in place before anything else is loaded in your container. Your deployment guys ought to help you make sure that the -javaagent:... parameter is there. It is their job! To work around that in order to make life easier for them but the behaviour of your application potentially unpredictable is not going to improve its stability.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Interesting, thanks for the additional details. The aspectJrt, weaver, and tools are all on my classpath. With LoadTimeWeaving enabled you're saying I shouldn't need any additional tools to ensure the agent is available at run time so long as I specify the -javaagent parameter on the command line? – Pensai Sep 05 '19 at 12:59
  • One more thing, in terms of the impact on the rest of the development team I assume they would also need to reconfigure their Eclipse environment to account for this change. The other issue is that some people are using Maven from a terminal which is what seems to not be aware of or loading the appropriate javaagent. I feel like something like this may be useful: https://stackoverflow.com/questions/14777909/specify-javaagent-argument-with-maven-exec-plugin – Pensai Sep 05 '19 at 13:18
  • (1) You only need _aspectjweaver.jar_ on the classpath, not the other two. Background: _Weaver_ is a superset of _runtime_, _tools_ is a superset of _weaver_ which in this case you don't need because you don't want to use the compiler contained therein. (2) No additional tools needed if configured correctly, see [Spring manual](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-using-aspectj). (3) Yes, the team needs to adjust their IDE run configurations. (4) Starting tests or applications with Java agent from Maven is possible. – kriegaex Sep 06 '19 at 01:52
  • The easy and safe alternative to all that is to use compile-time weaving via AspectJ Maven plugin and get rid of load-time weaving altogether. Then your application will also start up faster because no weaving is necessary during load-time. Plus, you only need _aspectjrt.jar_ (AspectJ runtime) on the classpath, a very small JAR of just 120K size. For production aspects which should always be woven this is always my choice. Only debugging or tracing aspects which should only be active when you want to analyze problems indicate using LTW. – kriegaex Sep 06 '19 at 01:56
  • I appreciate your in depth answers on this kriegaex they've been very insightful. I'll take a look at compile time weaving, if it's faster I think that will be something important to consider. As our application has been growing the start up time has been increasing and we'd like to keep that down. I'm going to accept your post as the answer as our discussions have been insightful enough to get me going in the correct direction and have a better grasp on things. – Pensai Sep 06 '19 at 12:59