Leander Leander - 1 month ago 15
Java Question

NoSuchMethodError with AspectJ

Note: this is a proof of concept implementation and not the final thing.

i have 3rd. party add ons and i want to limit their usage of sound, e.g. limit the duration etc. (not possible with security manager). To achieve this i thought about replacing the return of AudioSystem.getLine() with a wrapper.
this is the test-aspect:

@Aspect
public class MixerAspect {
@Around("execution(* javax.sound.sampled.AudioSystem.getLine(javax.sound.sampled.Line.Info))")
public Object getLineAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AspectJ");
pjp.getArgs();
Line ret = (Line) pjp.proceed();
return ret;
}
}


every add-on has its own class loader and they usually don't share objects, so this is what i have come up with:

@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
....
if (className.equals(AudioSystem.class.getName())) {
.....
byte[] array = buffer.toByteArray();
try {
if (mixed == null)
mixed = weaver.defineClass(className, array, new CodeSource(mixerAspect, (CodeSigner[]) null));
return mixed;
} catch (IOException e) {
throw new ClassNotFoundException("unable to define class", e);
}


this is the initlialization-code of the WeavingURLClassloader:

weaver = new WeavingURLClassLoaderHelper(new URL[0], parent);
weaver.addURL(aspects[0]);


parent is the class loader with access to the Mixer aspect and aspects is an url-array which only element is one pointed to the directory where the MixerAspect is located in.
my aop.xml:

<aspectj>
<aspects>
<aspect name="org.intellimate.izou.security.replaced.MixerAspect"/>
</aspects>

<weaver options="-Xset:weaveJavaxPackages=true -verbose -showWeaveInfo -debug">
<!-- Weave types that are within the javax.* or org.aspectj.*
packages. Also weave all types in the foo package that do
not have the @NoWeave annotation. -->
<include within="javax.*"/>
<include within="org.intellimate.izou.security.replaced.*"/>
<include within="javax.sound.sampled.AudioSystem"/>
</weaver>
</aspectj>


this is the (relevant) log:

....(no relevant log)
TraceFactory.instance=org.aspectj.weaver.tools.Jdk14TraceFactory@53aad5d5
[WeavingURLClassLoaderHelper] info AspectJ Weaver Version 1.8.5 built on Thursday Jan 29, 2015 at 01:03:58 GMT
[WeavingURLClassLoaderHelper] info register classloader ro.fortsoft.pf4j.IzouPluginClassLoader$WeavingURLClassLoaderHelper
[WeavingURLClassLoaderHelper] info using configuration /Users/LeanderK/IdeaProjects/Izou/target/classes/META-INF/aop.xml
[WeavingURLClassLoaderHelper] info register aspect org.intellimate.izou.security.replaced.MixerAspect
[WeavingURLClassLoaderHelper] info AspectJ Weaver Version 1.8.5 built on Thursday Jan 29, 2015 at 01:03:58 GMT
[WeavingURLClassLoaderHelper] info register classloader ro.fortsoft.pf4j.IzouPluginClassLoader$WeavingURLClassLoaderHelper
[WeavingURLClassLoaderHelper] info using configuration /Users/LeanderK/IdeaProjects/Izou/target/classes/META-INF/aop.xml
[WeavingURLClassLoaderHelper] info register aspect org.intellimate.izou.security.replaced.MixerAspect
...(no relevant log)
[WeavingURLClassLoaderHelper] debug weaving 'javax.sound.sampled.AudioSystem'
[WeavingURLClassLoaderHelper] weaveinfo Join point 'method-execution(javax.sound.sampled.Line javax.sound.sampled.AudioSystem.getLine(javax.sound.sampled.Line$Info))' in Type 'javax.sound.sampled.AudioSystem' (AudioSystem.java:410) advised by around advice from 'org.intellimate.izou.security.replaced.MixerAspect' (MixerAspect.class(from MixerAspect.java))
[WeavingURLClassLoaderHelper] debug generating class 'javax.sound.sampled.AudioSystem$AjcClosure1'
2015-05-24 20:12:13,945 FATAL [Thread-5] org.intellimate.izou.threadpool.ThreadPoolManager (ThreadPoolManager.java:59) - unable to provide callback for: org.intellimate.izou.addon.AddOnManager$$Lambda$19/970865974@1f4f3b69
java.util.concurrent.CompletionException: java.lang.NoSuchMethodError: org.intellimate.izou.security.replaced.MixerAspect.aspectOf()Lorg/intellimate/izou/security/replaced/MixerAspect;
at java.util.concurrent.CompletableFuture.internalComplete(CompletableFuture.java:205) [?:1.8.0_25]
at java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:459) [?:1.8.0_25]
at java.util.concurrent.CompletableFuture$Async.run(CompletableFuture.java:428) [?:1.8.0_25]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_25]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_25]
at org.intellimate.izou.threadpool.ThreadPoolManager$LoggingThreadFactory$1.run(ThreadPoolManager.java:73) [classes/:?]
Caused by: java.lang.NoSuchMethodError: org.intellimate.izou.security.replaced.MixerAspect.aspectOf()Lorg/intellimate/izou/security/replaced/MixerAspect;
at javax.sound.sampled.AudioSystem.getLine(AudioSystem.java:410) ~[?:1.8.0_25]
at leanderk.izou.playground.ExampleAddOn.prepare(ExampleAddOn.java:41) ~[?:?]
at org.intellimate.izou.sdk.addon.AddOn.register(AddOn.java:41) ~[?:?]
at org.intellimate.izou.addon.AddOnManager$$Lambda$19/970865974.run(Unknown Source) ~[?:?]
at java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:454) ~[?:1.8.0_25]
... 4 more


it looks like it doesn't weave, but why? I don't know what else to try. I have the following dependencies:

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>


to further complicate things, the class loader is in another project with is a simple maven dependency to this project.
Is there some aspectj-intitialization i am missing?




edit to clarify the answer:
you also have to define the aspect through WeavingURLClassloader, this is my (example - its simplified) code, the same can be applied for every class you want to mix-in. In my case the class was already loaded through the system-classloader, so i had to excplicitly call define (be careful, the same classes loaded from different class loaders are not the same!)

private Class mixer = null;
......
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
.....
else if (className.contains("MixerAspect")) {
InputStream is = this.getResourceAsStream(className.replace('.', '/') + ".class");
ByteArrayOutputStream buffer = new ByteArrayOutputStream();

int nRead;
byte[] data = new byte[16384];

try {
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
} catch (IOException e) {
throw new ClassNotFoundException("unable to load class", e);
}

try {
buffer.flush();
} catch (IOException e) {
throw new ClassNotFoundException("unable to load class", e);
}
byte[] array = buffer.toByteArray();
URL mixerAspect = getResource("org/intellimate/izou/security/replaced/MixerAspect.class");
try {
if (mixer == null) {
mixer = weaver.defineClass(className, array, new CodeSource(mixerAspect, (Certificate[]) null));
}
return mixer;
} catch (IOException e) {
throw new ClassNotFoundException("unable to define class", e);
}
}

Answer

If using annotation style aspects and compiling with javac you need the aspect to be 'finished off' at loadtime with a pass through the weaver itself. If you don't then it can't generate the helper methods like aspectOf() that will be called from other woven types attempting to use the aspect. The NoSuchMethodError for aspectOf() tells me the aspect is not getting woven. The fact that aspectOf() is being called at all is telling me that the other types are being woven successfully. Couple of choices:

  • use ajc to compile your MixerAspect, this will add the extra methods at compile time

  • ensure your weaving process lets MixerAspect be woven. Ensure it is loaded with the other classes being woven rather than a parent loader? Or let the parent loader do weaving too?

Comments