Running AspectJ causes NoSuchMethodError: Aspect.a

2019-02-06 21:17发布

问题:

I have a very simple AspectJ aspect (using @AspectJ) which just prints out a log message. My goal is to advice code in my android application. Now this aspects works perfectly fine as long as I have the aspect class itself in my applications source-code. Once I move the aspect into a different module (either java -> .jar or android lib -> .aar) I get the following runtime exception when running the adviced code in my application:

java.lang.NoSuchMethodError: com.xxx.xxx.TraceAspect.aspectOf

Basically my structure is like this:

Root
 + app (com.android.application)
   - MainActivity (with annotation to be adviced)
 + library (android-library)
   - TraceAspect (aspect definition)

From the ajc compiler, I can see that the ajc compiler picks up my classes and advices them correctly, so I really don't know why it works as long as I have the @AspectJ class in my sourcecode, but stops working once I move it to a jar archive.

I am using gradle. Buildscript for my app is super simple. I followed the instructions in http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/

import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:0.12.+'
    classpath 'org.aspectj:aspectjtools:1.8.1'
  }
}

apply plugin: 'com.android.application'

repositories {
  mavenCentral()
}

dependencies {
  compile 'org.aspectj:aspectjrt:1.8.1'
  compile project (':library')
}


android.applicationVariants.all { variant ->
    AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.5",
                         "-XnoInline",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", plugin.project.android.bootClasspath.join(File.pathSeparator)]

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler)

        def log = project.logger
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}

Not sure if important, but just in case, the code of my aspect:

@Aspect
public class TraceAspect {
  private static final String POINTCUT_METHOD = "execution(@com.xxx.TraceAspect * *(..))";

  @Pointcut(POINTCUT_METHOD)
  public void annotatedMethod() {}

  @Around("annotatedMethod()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Aspect works...");
    return joinPoint.proceed();
  }
}

Classpath

I also checked the javaCompile.classPath and it correctly contains both the library-classes.jar and my app-classes.jar. Adding -log file to the ajc tasks also shows that files are correctly weaved.

Any ideas?

Minimal example to reproduce this problem

https://github.com/fschoellhammer/test-aspectj

回答1:

I played around with a Gradle AspectJ plugin and applied it to the annotation subproject like this:

buildscript {
    repositories {
        maven {
            url "https://maven.eveoh.nl/content/repositories/releases"
        }
    }

    dependencies {
        classpath "nl.eveoh:gradle-aspectj:1.4"
    }
}

project.ext {
    aspectjVersion = '1.8.4'
}

apply plugin: 'aspectj'

project.convention.plugins.java.sourceCompatibility = org.gradle.api.JavaVersion.VERSION_1_7
project.convention.plugins.java.targetCompatibility = org.gradle.api.JavaVersion.VERSION_1_7

Now the app works in the emulator and DDMS from the Android SDK shows that the advice output is on the console as expected. :-)

Please note that I have upgraded the project to AspectJ 1.8.4 and Java 7. I have also changed these settings:

Index: app/build.gradle
===================================================================
--- app/build.gradle    (revision 9d9c3ce4e0f903b5e7c650f231577c20585e6923)
+++ app/build.gradle    (revision )
@@ -2,8 +2,7 @@

 dependencies {
     // aspectJ compiler
-    compile 'org.aspectj:aspectjrt:1.8.1'
-
+    compile 'org.aspectj:aspectjrt:1.8.4'
     compile (project (':annotation'))
 }

@@ -50,13 +49,13 @@
     JavaCompile javaCompile = variant.javaCompile
     javaCompile.doLast {
         String[] args = ["-showWeaveInfo",
-                         "-1.5",
+                         "-1.7",
                          "-XnoInline",
                          "-inpath", javaCompile.destinationDir.toString(),
                          "-aspectpath", javaCompile.classpath.asPath,
                          "-d", javaCompile.destinationDir.toString(),
                          "-classpath", javaCompile.classpath.asPath,
-                         //"-log", "/home/flo/workspace/test-aspectj/weave.log",
+                         "-log", "weave.log",
                          "-bootclasspath", plugin.project.android.bootClasspath.join(File.pathSeparator)]

         MessageHandler handler = new MessageHandler(true);
Index: build.gradle
===================================================================
--- build.gradle    (revision 9d9c3ce4e0f903b5e7c650f231577c20585e6923)
+++ build.gradle    (revision )
@@ -5,7 +5,7 @@
     dependencies {
         classpath 'com.android.tools.build:gradle:0.12.+'
         // aspectj - http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
-        classpath 'org.aspectj:aspectjtools:1.8.1'
+        classpath 'org.aspectj:aspectjtools:1.8.4'
     }
 }


回答2:

The message implies that the aspect file has not gone through the aspectj weaver. The weaver would be responsible for adding the aspectOf() method. Although your annotation style aspects will compile fine with javac, they must be 'finished off' by aspectj at some point to introduce the infrastructure methods that support weaving. If you were load-time weaving this is done as the aspects are loaded but if you are compile time or post-compile time weaving then you need to get them to ajc some other way. If you have a library built like this:

javac MyAspect.java
jar -cvMf code.jar MyAspect.class

then you'd need to get that jar woven to 'complete' the aspects:

ajc -inpath code.jar -outjar myfinishedcode.jar

Or you could just use ajc instead of javac for the initial step

ajc MyAspect.java

Or you could do it at the point the aspects are being applied to your other code:

ajc <myAppSourceFiles> -inpath myaspects.jar 

By including myaspects.jar on the inpath, any aspect classes in there will be 'finished off' as part of this compile step and the finished versions put alongside your compiled application source files. Note this is different to if you used the aspect path:

ajc <myAppSourceFiles> -aspectpath myaspects.jar

Here the aspects on the aspect path are applied to your code but they are only loaded from there, they are not finished off and so you wouldn't get the finished versions alongside your compiled application source files.



回答3:

I run into the same problem but I used Maven instead of Gradle.

Before an aspect class can be applied to a target class it first need to be 'weaved' into an aspect. A weaved aspect class will have two static methods added (aspectOf and hasAspect).

In my particular case I didn't weave my aspects.

It can be done by adding aspectj-maven-plugin to the build section.

<build>
<plugins>
  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <executions>
      <execution>
        <goals>
          <goal>compile</goal> 
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>

Hope it helps!