am WE habe ich mich ein wenig intensiver mit AspectJ-AOP und Load Time
Weaving unter Spring beschäftigt, um einserseits eben auch ad hoc
Instantiierungen von Objekten, welche nicht im ApplicationContext
registeriert sind und dessen Lebenszyklus nicht von Spring kontrolliert
wird (typischerweise Geschäftsobjekte oder Objekte, welche
beispielsweise von ORM-Tools angelegt werden) und auch Konstruktoren
advisieren zu koennen.
Setup:
1. Weaving-Agent aktivieren
Die VM wird gestartet mit dem Argument
-javaagent:<path-to-lib>/aspectjweaver.jar
2. LTW Konfiguration
In /META-INF/aop.xml sind die durch den LoadTimeWeaver einzuwebenden
Aspekte registriert.
<?xml version="1.0"?>
<aspectj>
<aspects>
<aspect name="com.mgi.helloworld.HelloFromAspectJ"/>
</aspects>
<weaver>
<exclude within="org.springframework..*"/>
<exclude within="org.apache..*"/>
<exclude within="com.mgi.helloworld.*Config"/>
<exclude within="com.mgi.helloworld.HelloFromAspectJ"/>
</weaver>
</aspectj>
3. Aspect Konfiguration
Die Aspekte werden nach wie vor ueber Spring's ApplicationContext'
konfiguriert,
indem der 'Singleton'-Aspekt mittels factory-method referenziert wird.
Zur Definition der Aspekte habe ich AspectJ-Style genutzt. Im folgenden
Beispiel verwende
ich einen Pointcut, welcher alle sayHello()-Methoden beliebiger Klassen
advisiert:
@Aspect
public class HelloFromAspectJ{
// setup with default config
private HelloFromAspectJConfig config = new HelloFromAspectJConfig();
// here's the real config injected
public void setConfig(HelloFromAspectJConfig config) {
this.config = config;
}
@Pointcut("execution(* sayHello(..))")
public void helloMethods() {}
@Around("helloMethods()")
public void adviceHello( ProceedingJoinPoint pjp ) throws Throwable {
// advice logic goes here ...
}
...
}
Soweit läuft alles ganz gut - alle Klassen werden ordnungsgemäss
advisiert ...
Will ich nun die zu advisierenden Methodenaufrufe im Pointcut
einschränken - etwa indem ich keine sayHello()-Methoden advisieren
möchte, welche beispielsweise im Rahmen der Klasse
EvilHelloWorldCaller aufgerufen werden, so erreiche ich dies
normalerweise mittels 'cflow'.
Der Aspekt würde demnach nun folgendermassen aussehen:
@Aspect
public class HelloFromAspectJ{
// setup with default config
private HelloFromAspectJConfig config = new HelloFromAspectJConfig();
// here's the real config injected
public void setConfig(HelloFromAspectJConfig config) {
this.config = config;
}
// here's the adapted, narrowing pointcut
@Pointcut("execution(* sayHello(..)) && " +
" !cflow( call( * org.of.all.evil.EvilHelloWorldCaller.*(..) ) )")
public void helloMethods() {}
@Around("helloMethods()")
public void adviceHello( ProceedingJoinPoint pjp ) throws Throwable {
// advice logic goes here ...
}
...
}
Starte ich den Test mit diesem Pointcut, so läuft die Anwendung auf
einen Fehler, mit
folgendem Stack-Trace:
Exception in thread "main" java.lang.NoSuchFieldError:
ajc$cflowCounter$0
at com.mgi.helloworld.HelloWorld.sayHello(HelloWorld.java)
at com.mgi.helloworld.HelloWorldMain.main(HelloWorldMain.java:13)
In der AspectJ-Bugliste wurde im Rahmen des Bugs #132080 ein ähnliches
Thema angesprochen.
Der Bug sollte im aktuellen Entwicklerbuild gefixt sein. Ein Austausch
der aspectjweaver.jar
hat jedoch keine Besserung gebracht.
Hat von euch jemand ggf. ein ähnliches Problem mit LTW oder gar eine
Lösung zu dem genannten Phänomen?
Viele Grüsse
Mario Gleichmann
da sich noch niemand gemeldet hat:
- War das AJDT (Eclipse) oder der AspectJ Kommandozeilen-Compiler? Falls
AJDT auch Kommandozeilen-Compiler ausprobieren.
- Ansonsten: Minimales Testprogramm bauen (sollte nicht schwer sein) und
ein Bug submitten.
Ich hoffe, das hilft...
Gruß,
Eberhard
--
Eberhard Wolff Contradiction Is Balance Skype: ebr.wolff
Podcast: http://se-radio.net/
Blog: http://JandIandMe.blogspot.com/
vielen Dank für Deine Antwort.
Die geschilderte Problematik scheint nur wie beschrieben beim Load
TimeWeaving aufzutreten - nicht beim statischen Weben während eines
compiles.
So gesehen arbeite ich nicht mit AJDT bzw. dem AspectJ-Compiler(AJC?),
sondern rein mit Spring und den mit Spring gelieferten jars - in diesem
Fall relevant aspectjweaver.jar.
Ein sehr limitierter Workaround besteht darin, cflow programmatisch
nachzubilden und dabei auf den StackTrace zurückzugreifen, um
festzustellen, ob der aktuelle Aufruf innerhalb des Controllflows einer
bestimmten Klasse (bzw. Methode) stattfindet:
@Aspect
public class HelloFromAspectJ{
...
@Pointcut("execution(* sayHello(..))")
public void helloMethods() {}
@Around("helloMethods()")
public void adviceHello( ProceedingJoinPoint pjp ) throws Throwable {
if( cflow( "com.mgi.helloworld.EvilHelloWorldCaller" ) ){
return pjp.proceed();
}
else{
// call advice logic here ...
}
...
}
private boolean cflow( String className ) {
for( StackTraceElement traceElement :
Thread.currentThread().getStackTrace() ){
if( traceElement.getClassName().equals( className ) ){
return true;
}
}
return false;
}
...
}
Wie gesagt, dieser Workaround ist sehr limitiert, da die einzelnen
StackTraceElemente nur den Namen der jeweiligen Klasse bzw. Methode
preisgeben, nicht aber beispielsweise Anzahl und Parametertypen. Bei
überladenen Methoden kommt man da schnell in Schwierigkeiten ...
Ich habe das Problem auch nochmal direkt im Spring Forum angesprochen -
Ramnivas meinte, dass es sich dabei ggf. tatsächlich um einen Bug in
AspectJ handelt. Schlagen die weiteren Tests fehl, werde ich wohl
tatsächlich einen Bug submitten ...
Einstweilen nochmals vielen Dank für's Feedback!
Mario