Im letzten Beitrag über „SpringFramework XML-Config und Annotations mixen“ habe ich ja bereits erwähnt, dass es Probleme geben wird, wenn man Aspects per Annotations konfiguriert, dass aktivieren von AOP aber in der nachgelagerten XML einstellt. Dann erzeugt er zwar die Beans, aber erkennt sie nicht als Aspects. Die Aspects als Beans in der XML-Config zu definieren ist aber unnötig. Denn es gibt eine elegantere Lösung. Oder besser gesagt zwei.
Die erste ist, folgende:
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(AnnotationAwareAspectJAutoProxyCreator.class); annotationConfigApplicationContext.scan(defaultAnnotatedBeanDefinitionClassPath); annotationConfigApplicationContext.refresh();
Wichtig ist hier die Zeile mit dem AnnotationAwareAspectJAutoProxyCreator, die dafür sorgt, dass unsere Aspects als solche erkannt und benutzt werden können. Das schöne daran ist, dass es offensichtlich ist. Der Nachteil allerdings: es werden standardmäßig AOP-Proxies statt CGLib-Proxies erzeugt.
Zum besseren Verständnis:
Bei Spring-AOP werden die betroffenen Beans in Proxy-Objekte verpackt. Wenn bei der BeanFactory eine Bean angefragt wird, liefert diese dann nicht die original Bean zurück, sondern den Proxy. Von diesen Proxies gibt es jetzt allerdings zwei Sorten, die beide Vor- und Nachteile haben. Die Standard AOP-Proxies funktionieren wie ein Wrapper der das Interface der original Bean implementiert. Der Nachteil dabei wird offensichtlich, wenn wir uns das folgende Beispiel anschauen:
Nehmen wir an wir haben ein Interface und eine Implementierung, die von unserem Aspect betroffen ist.
public interface MyInterface { void myMethod(); } public class MyInterfaceImplementation implements MyInterface { public void void myMethod() { // do something } public int getSomeNumber() { return 42; } }
In unserer Businesslogik wollen wir uns jetzt diese Bean holen.
... MyInterface bean = beanFactory.getBean("myInterfaceImplementation", MyInterface.class); // works as expected // will throw a ClassCastException: MyInterfaceImplementation bean = beanFactory.getBean("myInterfaceImplementation", MyInterfaceImplementation.class);
Obwohl die original Bean die richtige Implementierung hat, fällt unser Programm auf die Nase, wenn wir statt des Interface die Implementierung haben wollen. Das lässt sich umgehen, indem wir bei der Definition der Bean „myInterfaceImplementation“ nicht die eigentlich Bean angeben, sondern eine ProxyFactoryBean definieren. Das Problem dabei ist allerdings, dass wir das für jede Bean extra machen müssen! Nicht sonderlich praktikabel, wenn wir unsere Beans nur über einen geschickt definierten „context:component-scan“ erzeugen.
An dieser Stelle kommt die zweite Sorte von Proxies ins Spiel: CGlib-Proxies. Diese erweitern quasi die Implementierung sodass wir auf die ProxyFactoryBean verzichten können. Allerdings hat auch diese Vorgehensweise nicht nur Vorteile. So funktioniert das Ganze nicht mit Klassen die final sind. Außerdem muss ein Default-Constructor vorhanden sein – der allerdings ruhig protected sein darf. Der Default-Constructor wird auch lediglich von CGLib intern benutzt – die eigentliche Bean wird wie gehabt über den vorgesehenen Constructor erzeugt.
Um nun CGlib-Proxies nutzen zu können, müssen wir etwas anders vorgehen als oben. Und zwar benötigen wir eine Instanz von AnnotationAwareAspectJAutoProxyCreator, da wir dort noch ein Property setzen müssen. Das geht entsprechend nicht über die register-Methode von AnnotationConfigApplicationContext, da diese die Angabe einer Klasse erwartet und kein Object.
Aber wir können einfach unseren BeanProvider aus dem letzten Beitrag erweitern um das gewünschte Ergebnis zu erzielen:
@Configuration public class ExampleBeanProvider { @Bean public AnnotationAwareAspectJAutoProxyCreator autoProxyCreator() { AnnotationAwareAspectJAutoProxyCreator autoProxyCreator = new AnnotationAwareAspectJAutoProxyCreator(); autoProxyCreator.setProxyTargetClass(true); return autoProxyCreator; } ... }
Das ist auch schon alles was getan werden muss, um spring-aop programmatisch/via Annotations zu konfigurieren. Es ist zwar ggfs. nicht direkt offensichtlich, aber bietet definitiv mehr Möglichkeiten das Verhalten von spring-aop zu beeinflussen.
Die Einschränkungen der beiden Proxyarten sollten jedoch nicht nur im Hinterkopf behalten werden, sondern vor allem an die Entwickler kommuniziert werden, welche die Beans implementieren und die von den Aspects nichts wissen (müssen).