Изпълнението на AspectJ причинява NoSuchMethodError: Aspect.aspectOf

Имам много прост 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. Buildscript за моето приложение е супер прост. Следвах инструкциите в 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


person Flo    schedule 07.11.2014    source източник
comment
Бих искал да помогна с частта за компилатора на AspectJ, но никога не съм използвал Gradle, само Maven. Можете ли да направите проблема възпроизводим, като публикувате минимална, напълно самосъгласувана версия на вашия проект (може би само с няколко фиктивни класа) в GitHub?   -  person kriegaex    schedule 16.11.2014
comment
Благодаря! Актуализирах въпроса, моля, проверете го.   -  person Flo    schedule 17.11.2014
comment
Съжалявам, не мога да създам това. Изглежда, че трябва да инсталирам Android ADK, включително SDK, и мисля, че го направих, въпреки че отне много време. Но по някакъв начин не е намерен или е грешната версия. Обикновено компилацията на Maven получава всички свои зависимости правилно разрешени и изтеглени, различно ли е това в Gradle? Можете ли по някакъв начин да създадете възпроизводима компилация за мен без всички Android неща, само съсредоточавайки се върху обикновена Java + AspectJ?   -  person kriegaex    schedule 17.11.2014
comment
Добре, с много опити и грешки (ANDROID_BUILD_TARGET_SDK_VERSION беше грешен за моята инсталация) успях да компилирам проекта. Имаше и NPE, защото в подпроекта app трябваше да променя параметър ajc на "-log", "weave.log",, защото вашият локален път не съществуваше на моята машина. Сега трябва ли да стартирам проекта локално, за да възпроизведа проблема?   -  person kriegaex    schedule 17.11.2014
comment
Актуализирах репото и извадих този локален път за регистриране на ajc. Можете просто да отворите /build.gradle с Android studio или можете да използвате gradle clean installDebug за инсталиране на вашето устройство/емулатор.   -  person Flo    schedule 18.11.2014
comment
Нямам устройство и първо трябва да разбера как да инсталирам емулатор. Аз съм начинаещ в Android. Наистина ли не може да се възпроизведе без Android? Може би ще опитам утре.   -  person kriegaex    schedule 18.11.2014
comment
Инсталирах Android SDK 21 и създадох емулация на Nexus 5 (друга по-добра ли е, нямам представа?). Устройството се зарежда, мога да разположа приложението, но когато го стартирам, просто виждам За съжаление, com.test.sample спря.   -  person kriegaex    schedule 18.11.2014
comment
Съжалявам, че ви карам да преминавате през всички тези проблеми, но все пак трябва да накарам това да работи на Android. Така или иначе не бих знаел как да конфигурирам gradle да работи с ajc с плъгин за Java, а дори и да знаех, това няма да реши непременно проблема, пред който съм изправен.   -  person Flo    schedule 18.11.2014
comment
Грешката, която срещате, е точно проблемът, който описва въпросът ми. Ако погледнете изхода на logcat (който ще видите, ако стартирате AndroidStudio и имате свързано устройство), трябва да видите проследяването на стека, причината за изключението трябва да е java.lang.NoSuchMethodError. Както описах, ако преместите TraceAspect.java от модула annotation към модула app, той магически започва да работи.   -  person Flo    schedule 18.11.2014
comment
Мога да възпроизведа проблема сега, но аз съм нуб на Gradle в комбинация с нуб на Android. Това, което мога да кажа в съответствие с Анди, е, че компилирате своя аспект с обикновен Java компилатор, т.е. той все още не е завършен. След това по някакъв начин вашият процес на компилация получава незавършения аспект от annotation.jar в APK в края. Препоръчвам просто да изградите своя подпроект за анотация и с Ajc, тогава трябва да сте добре. Алтернативата е да се уверите, че завършеният, а не незавършеният клас аспект влиза в APK. За целта вземете annotation.jar на inpath.   -  person kriegaex    schedule 19.11.2014


Отговори (3)


Поиграх си с добавка Gradle AspectJ и я приложих към анотацията подпроект като този:

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

Сега приложението работи в емулатора и DDMS от Android SDK показва, че изходът на съвета е на конзолата, както се очаква. :-)

Дневник на конзолата за монитор за отстраняване на грешки

Моля, обърнете внимание, че надстроих проекта до 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'
     }
 }
person kriegaex    schedule 21.11.2014
comment
Уау - мога да потвърдя, че работи, невероятно! Благодаря много за цялото разследване, наистина съм благодарен, че се заровихте в толкова много нови технологии, за да стигнете до дъното на това. Случайно да знаете защо предишният ми скрипт ще се провали и защо ще работи с вашите модификации? - person Flo; 22.11.2014
comment
Вече обясних преди малко в последния си коментар под първоначалния ви въпрос и отговорът на Анди обяснява какво сте направили: Вие сте компилирали аспекта с нормален javac, създавайки незавършен аспект без метод aspectOf() (проверете байтовия код, произведен от стария ви вариант за теб). След това поставяте аспекта aspectpath (но не в inpath!) на вашия родителски модул, т.е. аспектът е намерен за компилация, но не завършен от компилатора AspectJ. Така се оказахте с незавършен аспект във вашия APK. Моето решение компилира аспекта с ajc като начало. - person kriegaex; 22.11.2014

Съобщението предполага, че аспектният файл не е преминал през aspectj weaver. Уивърът ще отговаря за добавянето на метода 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 

Чрез включването на myaspects.jar в inpath, всички аспектни класове там ще бъдат „завършени“ като част от тази стъпка на компилиране и завършените версии ще бъдат поставени заедно с вашите компилирани изходни файлове на приложението. Обърнете внимание, че това е различно от това, ако сте използвали аспектния път:

ajc <myAppSourceFiles> -aspectpath myaspects.jar

Тук аспектите в пътя на аспекта се прилагат към вашия код, но те се зареждат само оттам, те не са завършени и така че няма да получите готовите версии заедно с вашите компилирани изходни файлове на приложението.

person Andy Clement    schedule 10.11.2014
comment
Вярвам, че изпълнявам ajc правилно в моя файл за изграждане на gradle (чрез new Main().run(args, handler)). Добавянето на -log file изход към ajc показва, че файловете са правилно вплетени. Освен това моят код не беше изтъкан, тогава анотацията @TraceDebug би била просто анотация на обикновен метод и програмата просто щеше да я игнорира по време на изпълнение. Но в моя случай програмата се срива всеки път, когато се опитам да изпълня анотиран метод. - person Flo; 11.11.2014

Сблъсквам се със същия проблем, но използвах 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>

Hope it helps!

person Eugene Maysyuk    schedule 11.10.2016