【问题标题】:Running AspectJ causes NoSuchMethodError: Aspect.aspectOf运行 AspectJ 导致 NoSuchMethodError: Aspect.aspectOf
【发布时间】:2014-11-07 13:30:03
【问题描述】:

我有一个非常简单的 AspectJ 方面(使用@AspectJ),它只打印出一条日志消息。我的目标是为我的 android 应用程序中的代码提供建议。现在,只要我的应用程序源代码中有方面类本身,这些方面就可以正常工作。 将方面移动到不同的模块(java -> .jar 或 android lib -> .aar)后,在我的应用程序中运行建议的代码时,会出现以下运行时异常

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

基本上我的结构是这样的:

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

从 ajc 编译器中,我可以看到 ajc 编译器选择了我的类并正确地建议它们,所以我真的不知道为什么只要我的源代码中有 @AspectJ 类它就可以工作,但停止工作一旦我将它移动到一个 jar 档案中。

我正在使用 gradle。我的应用程序的构建脚本非常简单。我按照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;
            }
        }
    }
}

不确定是否重要,但以防万一,我方面的代码:

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

类路径

我还检查了javaCompile.classPath,它正确包含library-classes.jar 和我的app-classes.jar。将-log file 添加到ajc 任务也表明文件已正确编织。

有什么想法吗?

重现此问题的最小示例

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

【问题讨论】:

  • 我想帮助 AspectJ 编译器部分,但从未使用过 Gradle,只有 Maven。你能通过在 GitHub 上发布一个最小的、完全自洽的项目版本(可能只有几个虚拟类)来重现问题吗?
  • 谢谢!我更新了问题,请查看。
  • 抱歉,我无法构建它。看来我需要安装 Android ADK 包括 SDK,我想我已经安装了,即使它花了很长时间。但不知何故,它没有找到或者是错误的版本。通常,Maven 构建会正确解析和下载其所有依赖项,这在 Gradle 中是否有所不同?你能以某种方式为我创建可复制的构建,而不需要所有的 Android 东西,只专注于普通的 Java + AspectJ?
  • 好的,经过大量试验和错误(ANDROID_BUILD_TARGET_SDK_VERSION 我的安装错误)我能够编译该项目。还有一个 NPE,因为在 app 子项目中,我必须将 ajc 参数更改为 "-log", "weave.log",,因为您的本地路径在我的机器上不存在。现在我是否应该在本地运行项目以重现问题?
  • 我现在可以重现该问题,但我是一个 Gradle 菜鸟和一个 Android 菜鸟。按照安迪的说法,我可以说的是你用一个普通的 Java 编译器编译你的方面,即它还没有“完成”。然后不知何故,您的构建过程最终将 annotation.jar 中未完成的方面放入 APK。我建议您也只使用 Ajc 构建您的注释子项目,那么您应该没问题。另一种方法是确保已完成而不是未完成的方面类进入 APK。为此,请在 inpath 上获取 annotation.jar

标签: java android gradle aspectj android-gradle-plugin


【解决方案1】:

该消息暗示方面文件尚未通过aspectj 编织器。编织者将负责添加aspectOf() 方法。尽管您的注释样式方面可以使用javac 很好地编译,但它们必须在某些时候由aspectj '完成' 以引入支持编织的基础结构方法。如果您是加载时编织,这是在加载方面时完成的,但如果您是编译时或编译后编织,那么您需要以其他方式将它们发送到ajc。如果你有一个这样构建的库:

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

那么你需要编织那个罐子来“完成”各个方面:

ajc -inpath code.jar -outjar myfinishedcode.jar

或者您可以在初始步骤中使用ajc 而不是javac

ajc MyAspect.java

或者您可以在将方面应用于您的其他代码时执行此操作:

ajc <myAppSourceFiles> -inpath myaspects.jar 

通过在inpath 中包含 myaspects.jar,其中的任何方面类都将作为此编译步骤的一部分“完成”,并将完成的版本与您编译的应用程序源文件放在一起。请注意,这与您使用方面路径不同:

ajc <myAppSourceFiles> -aspectpath myaspects.jar

此处方面路径上的方面应用于您的代码,但它们仅从那里加载,它们没有完成,因此您不会在编译的应用程序源代码旁边获得完成的版本文件。

【讨论】:

  • 我相信我在我的 gradle 构建文件中正确地执行了ajc(通过new Main().run(args, handler))。将-log file 输出添加到ajc 表明文件已正确编织。另外,我的代码没有被编织,那么 @TraceDebug 注释将只是一个简单的方法注释,并且程序会在运行时简单地忽略它。但就我而言,每次我尝试执行带注释的方法时程序都会崩溃。
【解决方案2】:

我遇到了同样的问题,但我使用的是 Maven 而不是 Gradle。

在一个方面类可以应用到一个目标类之前,它首先需要被“编织”到一个方面。 编织的方面类将添加两个静态方法(aspectOf 和 hasAspect)。

在我的特殊情况下,我没有编织我的方面。

可以通过将 aspectj-maven-plugin 添加到构建部分来完成。

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

希望对你有帮助!

【讨论】:

    【解决方案3】:

    我玩弄了一个Gradle AspectJ plugin 并将它应用到 annotation 子项目中,如下所示:

    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
    

    现在应用程序在模拟器中运行,Android SDK 的 DDMS 显示建议输出如预期在控制台上。 :-)

    请注意,我已将项目升级到 AspectJ 1.8.4 和 Java 7。我还更改了这些设置:

    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'
         }
     }
    

    【讨论】:

    • 哇 - 我可以确认它有效,太棒了!非常感谢所有的调查,我真的很感激你深入研究了这么多新技术以查明真相。您是否也碰巧知道,为什么我以前的脚本会失败以及为什么它会与您的修改一起工作?
    • 我已经在我的最后一条评论中解释了你原来的问题,安迪的回答解释了你做了什么:你已经用普通的javac编译了方面,产生了一个没有@987654327的未完成的方面@ 方法(自己检查旧变体生成的字节码)。然后你把你的父模块的切面 aspectpath (但不在 inpath 上!),即找到了切面进行编译,但 not由 AspectJ 编译器完成。因此,您的 APK 中出现了未完成的方面。我的解决方案首先使用 ajc 编译方面。
    最近更新 更多