Spring & AspectJ:Methode wird nicht aufgerufen

Ich bin ein Stück weiter - habe einige Antworten und noch eine offene Frage:

Wichtig:
Wenn man in einen Controller mit Spring AOP einen Aspekt integrieren möchte, so müssen die Konfiguration der Controller-Beans und der Aspekte in einer xml-Datei stehen!

Wir haben uns in unserem Projekt dafür entshieden, die Konfiguration etwas zu entzerren und ich wollte ide gesamte AOP-Konfiguration mit in die service.xml stecken. Die Controller-Beans waren in der servlet.xml definiert. Aber so passierte an den Controllern überhaupt gar nichts. Nachdem ich den aop-Konfig-Block mit in die servlet.xml gesteckt habe, hatte ich überhaupt erstmal output.

Mein erstes Ziel bestand nun darin in die Methoden, welche ModelAnd View als return haben, einen Aspekt zu integrieren, um diesen zu 'bearbeiten'.
Anschliessend wollte ich auf beliebige Methoden damit zugreifen (Beispiel hier setUserManager()).

Dazu habe ich jetzt folgende AOP-Konfiguration:

Code:
<bean id="AspectBean"  class="mypackage.AspectClass" />

<aop:aspectj-autoproxy/>
    <aop:config proxy-target-class="true">
        <aop:aspect id="myAspect1" ref="AspectBean">
            <aop:pointcut id="saveViewInModelPoinCut"
            expression="execution(org.springframework.web.servlet.ModelAndView
                            myProject.web.controller.*.*(..))"/>
            <aop:after-returning pointcut-ref="saveViewInModelPoinCut"
        method="test1" returning="ModelAndView" />
        </aop:aspect>

        <aop:aspect id="myAspect2" ref="AspectBean">
            <aop:pointcut id="test"
            expression="execution(* org.springframework.web.servlet.mvc.AbstractController.handleRequest(..))" />
            <aop:after-returning pointcut-ref="test"
        method="test2" returning="ModelAndView" />
        </aop:aspect>
        
      <aop:aspect id="myAspect3" ref="AspectBean">
            <aop:pointcut id="test2"
            expression="execution(*
                            myProject.web.controller.*.setUserManager(..))"/>
            <aop:before pointcut-ref="test" method="test3"/>
        </aop:aspect>
    </aop:config>

Was man jetzt beobachtet, ist folgendes:
1. Klassen mit Interfaces (implements Controller) integrieren Apekt1 über ihre eigene handleRequestmethode.

2. Klassen ohne eigene Interfaces (z.B. mit extends SimpleFormController) integrieren nur Aspekt2 über die handleRequestMethode des AbstractControllers.

3. bei keiner Klasse ist AOP in der Lage andere eigene Methoden (z.B. setUsermanager() ) anzugreifen.

Augenscheinlich wird die CGLib nicht benutzt. Meine offene Frage ist jetzt noch, warum!? Habe ich nicht mit 'proxy-target-class="true"' Spring dazu aufgefordert die CGLib immer zu benutzen?

Was mir auffällt ist, dass ich im Log massenhaft Einträge wie den folgenden habe:

"...
21:44:46,633 WARN Cglib2AopProxy:256 - Unable to proxy method [public final org.springframework.context.ApplicationContext org.springframework.context.support.ApplicationObjectSupport.getApplicationContext() throws java.lang.IllegalStateException] because it is final: All calls to this method via a proxy will be routed directly to the proxy.
..."

Hier scheint er zu versuchen, die CgLib zu benutzen und scheitert - vertändlicherweise - an der final-Methode. OK- dass das nicht geht ist mir klar, aber warum versucht er es nicht bei meinen Controllern? Da ist nichts final!

Und wirklich: wenn ich proxy-target-class="false" setze, ändert sich nichts - nur die Fehlermeldungen im log verschwinden ...

Also noch einmal die Frage. warum zieht proxy-target-class="true" nicht bei den Controllern!

beste Grüße - pasternak
 
Ich bin ein Stück weiter - habe einige Antworten und noch eine offene Frage:

Wichtig:
Wenn man in einen Controller mit Spring AOP einen Aspekt integrieren möchte, so müssen die Konfiguration der Controller-Beans und der Aspekte in einer xml-Datei stehen!
Das ist falsch. Absolut nicht notwendig. Du bist vermutlich einer anderen Problematik aufgesessen. Für Webapplikationen werd 2(!) ApplicationContexte erzeugt. Einer mit ContextLoaderListener und einer vom DispatcherServlet. Der aus dem CLL ist der Parentcontext von dem aus dem DS. Das hat zur Folge, dass Beans aus dem DS Context Beans aus dem CLL Context sehen aber nicht umgekehrt. D.h. du kannst NICHT Aspekte, die Beans betreffen sollen, die vom DS geladen werden, in Konfigurationsfiles ablegen die dann vom CLL geladen werden. Aufteilung im COntext des DS ist aber weiterhin problemlos möglich.

Also noch einmal die Frage. warum zieht proxy-target-class="true" nicht bei den Controllern!
IMHO schreit das danach, dass einfach die Pointcuts falsch sind. Liegen die zu interceptenden Beans wirklich in diesem Package. Benutzt du die Spring IDE mit den AOP Features? Die Zeit dir - sobald ein Pointcut wirklich trifft - sowohl an der getroffenen Bean als auch an der Aspektdeklaration Marker an, die jeweils auf die andere Deklaration verweisen.

Gruß
Ollie
 
Danke Ollie,
das mit den Kontexten habe ich verstanden.

Die Sache mit der CgLib bleibt mir noch verschlossen. Ich habe jetzt veschiedene Sachen ausprobiert. Egal was und wie ich konfiguriere, es werden immer nur InterfaceMethoden als Pointcuts akzeptiert. Das heisst, das Spring - bei mir - überhaupt nicht in der Lage ist, die Cglib zu benutzen. Nach meinem Verständnis ist der default ja so, dass beim Vorhandensein von Interfaces der Mechanismus mit "java.lang.reflect.Proxy" genutzt wird - im anderen Fall Proxies über CgLib. Aber auf Klassen ohne Interfaces kann ich überhaupt nicht zugreifen!

Ich habe sicherheitshalber mal im Code ein Log eingebaut:
"log.info("Test CGLIB: "+net.sf.cglib.core.Constants.ACC_SYNCHRONIZED);"
und sehe, dass die CgLib zur Verfügung steht. Außerdem gibt es ja die Warnung von der Klasse Cglib2AopProxy in meinen logs.

Ich bin jetzt wirklich ratlos.
Gibt es irgendetwas in den logs, woran man erkennt, dass die CgLib Proxies benutzt werden?
Kann es sein, dass es Konfikte mit der CgLib von Hibernate gibt (aber die hat eigentlich einen anderen package Namen)?

Gibt es noch etwas anderes als das Jar-Paket und die 'proxy-target-class="true"'-Zeile, damit das ganze laufen kann?

beste Grüße - pasternak

PS : Ich entwickle mit Netbeans - aber wenn wirklich hilft, kann ich ja mal eclipse raufziehen ...
 
Sowas ist halt von remote aus schwer zu debuggen. Aber zurück zu deinem Problem? Warum interceptest du nicht einfach Controller.handleRequest(..) ?

Gruß
Ollie
 
Warum interceptest du nicht einfach Controller.handleRequest(..) ?

Für mein ursprüngliches Problem würde das reichen. Aber ich bin halt gerade dabei mich in das Thema einzuarbeiten. Das nächste Problem wird Logging sein. Da brauche ich es viel umfassender. Letztlich stört mich einfach, dass es nicht geht und mir damit sagt, dass ich irgendwo einen Fehler habe. Damit fällt es mir verdammt schwer zur Tagesordnung überzugehen.

Aber vielleicht kann man ja irgendwie herausbekommen, an welcher Stelle im Code Spring die Entscheidung trifft, welcher Proxy-Mechanismus verwendet wird. Dort könnte man dann debuggen.
Vielleicht gibt es ja auch aussagefähige Debug-Codes.
Ich werde mir mal ein neues Projekt erzeugen und versuchen von start an das einzubauen. Mal schauen, ob es dann funktioniert?

... ich melde mich wieder ...
 
Wenn du ein möglichst einfaches Eclipseprojekt aufsetzt, könnt ich auch mal draufschauen. Die Fehlerdiagnose ist über so ein Forum in der Detailtiefe sehr schwierig.

Grundsätzlich ist es aber auch eine Architekturentscheidung, ob man AOP mit Proxies oder Subklassen einsetzt. Ich hab bisher immer versucht, den Subklassen aus dem Weg zu gehen, da man in den meisten Fällen wenn man mit AOP dazwischengrätschen möchte, eh einen guten Grund hat, ein Interface einzuziehen (grad im Bezug auf Testbarkeit).

Gruß
Ollie
 
Hier mal ein kleines Beispielprojekt auf Maven Basis. Das solltest du auch in die IDE deiner Wahl importiert bekommen.

Beim Ausführen des Testcases sollte
Code:
Aspect invoked
Hello Foo
zu sehen sein. Wenn du die Dependency zu CGLib aus dem pom.xml entfernst und den test wieder ausführst, fliegt eine Exception, die sagt, dass eben CGLib fehlt.

Gruß
Ollie
 

Anhänge

  • org.synyx.samples.spring.aop.zip
    7,3 KB · Aufrufe: 22
Hallo noch einmal - wahrscheinlich abschliessend,

ich habe meine Denkfehler gefunden - es waren doch ein paar mehr. Und da es ja immer wieder Leute gibt, die beim Googeln hier vorbei kommen, möchte ich Euch meine Erkenntnise nicht vorenthalten.
Ollie - vielen Dank für dein kleines Beispiel. Es hat mich dazu gebracht ganz klein anzufangen und es dann so zu verändern, zu dem was ich machen wollte. Das hat vieles klar gemacht. Für die Spring und AOP-Kenner unter Euch mögendie folgenden Erkenntnisse trivial erscheinen - aber wenn man in die Materie einsteigt, geht man halt so manchen Irrweg:

Erkenntnis1:
Das hatten wir oben schon: Will einen Pointcut auf einen Spring-MVC-Controller definieren, so muss man diesen poincut auch in der Servlet-Context-Konfiguration definieren.

Erkenntnis2:
Die Spring-MVC-Controller sind in Ihrer Art etwas anders als andere Klassen, was wohl auch an der Art liegt wie sie erzeugt werden. Deshalb lässt sich bei Ihnen nur der auf java.lang.reflection basierte Proxy-Ansatz umsetzen. Leitet man einen eigenen Controller vom SpringController ab, so kann man einen Pointcut auf die eigene handleRequestMethode setzen (z.B. um das Model als Aspekt zu checken):
Code:
<aop:aspect id="myAspect1" ref="AspectBean">
            <aop:pointcut id="myPoinCut"
            expression="execution(* myProject.web.controller.*.handleRequest(..))"/>
            <aop:after-returning pointcut-ref="myPoinCut" method="handleAspect" returning="ModelAndView" />
        </aop:aspect>

Benutzt man die von Spring mitgelieferten Controller, so leitet man von Ihnen ab und die handleRequest-Methode steckt im AbstractController:

Code:
<aop:aspect id="myAspect2" ref="AspectBean">
            <aop:pointcut id="myPoinCut2"
            expression="execution(* org.springframework.web.servlet.mvc.AbstractController.handleRequest(..))" />
            <aop:after-returning pointcut-ref="myPoinCut2" method="handleAspect" returning="ModelAndView" />
        </aop:aspect>

Beide Ansätze lassen sich auch verbinden.
Darüber hinaus nützt übrigens nichts, sich selbst weitere Interfaces zu schreiben. Auch diese Methoden sind nicht erreichbar(!). Interessant wäre für mich an dieser Stelle noch, ob sich das mit annotation-basierten Controllern ändert? Wenn da jemand Erfahrungen hat, fände ich das sehr interessant, denn dazu war irgendwie nichts zu ergoogeln.

Erkenntnis3:
Es werden nur Pointcuts auf Methoden erreicht, deren Klassen auch als Bean instanziiert wurden. Es genügt auch nicht, sie als Bean zu definieren und dann selbst über den Konstruktor eine Instanz zu erzeugen.
So logisch das beim Nachdenken ist, so wenig klar war mir das vorher. Das bedeutet unter anderem, dass man nicht (so ohne weiteres? oder gar nicht?) auf von Hibernate erzeugte Domainobjekte zugreifen kann. Zumindest wüsste ich nicht wie das dann gehen soll. Außerdem versagt das System bei statischen Methoden. Ss sei dahin gestellt ob man so was braucht - für mich war es ein Erkenntnisgewinn, dass es nicht geht.


So - nochmal Dank an Ollie für die Hilfe und ich hoffe, dass diese Einträge auch anderen weiterhelfen.

beste Grüße - pasternak
 
Hier noch ein paar Erläuterungen, Korrekturen:

Erkenntnis1:
Das hatten wir oben schon: Will einen Pointcut auf einen Spring-MVC-Controller definieren, so muss man diesen poincut auch in der Servlet-Context-Konfiguration definieren.
Genau gesagt muss man bei Kontexthierarchien aufpassen. Die Beans auf die ein Advice angewendet werden soll muss im Kontext des Advices sichtbar sein. Im allgemeinen Fall liegen sowohl Advice als auch Zielbean im gleichen Context. Im Falle von Webapplikationen müssen Advices für Beans aus dem Kontext eines DispatcherServlet eben auch in diesem liegen.

Erkenntnis2:
Die Spring-MVC-Controller sind in Ihrer Art etwas anders als andere Klassen, was wohl auch an der Art liegt wie sie erzeugt werden. Deshalb lässt sich bei Ihnen nur der auf java.lang.reflection basierte Proxy-Ansatz umsetzen. Leitet man einen eigenen Controller vom SpringController ab, so kann man einen Pointcut auf die eigene handleRequestMethode setzen (z.B. um das Model als Aspekt zu checken):
Code:
<aop:aspect id="myAspect1" ref="AspectBean">
            <aop:pointcut id="myPoinCut"
            expression="execution(* myProject.web.controller.*.handleRequest(..))"/>
            <aop:after-returning pointcut-ref="myPoinCut" method="handleAspect" returning="ModelAndView" />
        </aop:aspect>

Benutzt man die von Spring mitgelieferten Controller, so leitet man von Ihnen ab und die handleRequest-Methode steckt im AbstractController:

Code:
<aop:aspect id="myAspect2" ref="AspectBean">
            <aop:pointcut id="myPoinCut2"
            expression="execution(* org.springframework.web.servlet.mvc.AbstractController.handleRequest(..))" />
            <aop:after-returning pointcut-ref="myPoinCut2" method="handleAspect" returning="ModelAndView" />
        </aop:aspect>

Beide Ansätze lassen sich auch verbinden.
Darüber hinaus nützt übrigens nichts, sich selbst weitere Interfaces zu schreiben. Auch diese Methoden sind nicht erreichbar(!). Interessant wäre für mich an dieser Stelle noch, ob sich das mit annotation-basierten Controllern ändert? Wenn da jemand Erfahrungen hat, fände ich das sehr interessant, denn dazu war irgendwie nichts zu ergoogeln.
Hier verstehe ich nicht ganz, worauf du hinaus willst. Die erste Aussage ist jedoch definitiv falsch. Controller sind Beans wie alle andere. Desweiteren gehst du für beide Beispiele davon aus, dass man Spring Controllerklassen erweitert. Im Endeffekt kommt es drauf an, ob man AOP per proxy oder per CGLib benutzt. Das zweite Beispiel kann im proxybasierten nicht funktionieren, da du den Pointcut in diesem Fall auf das Controller Interface formulieren musst und nicht auf die Basisklasse. Falls du noch irgendwelche Fälle findest, in denen etwas nicht so tut wie in der Referenzdoku beschrieben, würd ich gern ein reproduzierendes Miniprojekt sehen ;).

Zu den annotationsbasierten Controllern: die lassen sich vor allem dadurch leichter intercepten und testen, weil die eigentlichen Methodenhalt einfach public sind. Durch die große Flexibilität bei der Methodengestaltung ist es allerdings schwerer einen Pointcut zu finden, der eben *alle* Handlermethoden trifft. Hier hilft es im Allgemeinen AnnotationMethodHandlerAdapter.handle(..) zu intercepten. Das ist konzeptionell recht analog zu der handleRequest aus dem Controller interface.
Erkenntnis3:
Es werden nur Pointcuts auf Methoden erreicht, deren Klassen auch als Bean instanziiert wurden. Es genügt auch nicht, sie als Bean zu definieren und dann selbst über den Konstruktor eine Instanz zu erzeugen.
So logisch das beim Nachdenken ist, so wenig klar war mir das vorher. Das bedeutet unter anderem, dass man nicht (so ohne weiteres? oder gar nicht?) auf von Hibernate erzeugte Domainobjekte zugreifen kann. Zumindest wüsste ich nicht wie das dann gehen soll. Außerdem versagt das System bei statischen Methoden. Ss sei dahin gestellt ob man so was braucht - für mich war es ein Erkenntnisgewinn, dass es nicht geht.
Die Springdoku sagt eindeutig das Spring AOP (proxy und CGLibbasiert) nur mit Springbeans geht. Was allerdings möglich ist, ist das injizieren von Dependencies in Objekte die mit new instantiiert werden. Allerdings ist dafür der Einsatz von Loadtime- oder Compiletimeweaving notwendig. Nähere Infos: http://static.springframework.org/spring/docs/2.5.x/reference/aop.html#aop-atconfigurable

REINHAUN!
 

Neue Beiträge

Zurück