Spring’s AOP Proxies

Wenn Spring’s AOP Framework Proxies erzeugt um eigene Klassen mit zusätzlichen Features zu versehen, kann das mitunter seltsame Effekte haben. Beispielsweise würde man doch vermuten, dass eine SpringBean die auf folgende Weise definiert ist:

<bean name="testBean" class="com.rinke.solutions.spring.test.SpringTestImpl" />

sich in einer Applikation immer in etwa in der Art verwenden läßt:

package com.rinke.solutions.spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring {

    /**
     *
@param args
     */
   
public static void main(String[] args) {
       
ApplicationContext ctx = new ClassPathXmlApplicationContext(
               
new String[] {"applicationContext.xml" });
       
        SpringTestImpl myBean =
(SpringTestImpl) ctx.getBean("testBean");
       
// ... do something
   
}

}

Weit gefehlt!

Je nach dem, ob die Klasse ein Interface implementiert oder nicht, funktioniert der Cast auf "SpringTestImpl" nicht mehr, sondern liefert eine ClassCastException.

Dieses Verhalten rührt daher, dass Spring beim Erzeugen von Proxies auf zwei unterschiedliche Mechanismen zurückgreift:

  1. Die dynamischen Proxies aus dem JDK (seit Version 1.4)
  2. Auf dynamisch erzeugte Subclasses deren Bytecode zur Laufzeit mit der CGLib generiert wird.

Das erste Verfahren ist das per Default bevorzugte, funktioniert aber nur, wenn die in Frage stehende Klasse wenigstens ein Interface implementiert. Wieso kommt es nun zu der ClassCastException? Ganz einfach, der erzeugte Proxy ist ein Proxy eines Interfaces, welches die Klasse implemeniert und dieser ist vom Typ her nicht auf die Implementierungsklasse "herunter castbar". Obwohl man also weiss, dass eine ganz bestimmte Implementierung hinter einer Bean steckt, kann sie auf diesen Typ nicht gecastet werden.

Jetzt mag man einwenden, dass es sowieso besserer Stil ist ein Interface zu definieren gegen das man programmiert. Aber die Alttag läßt einen oft genug diesen Schritt überspringen, wenn weiter kein Nutzen davon entsteht, weil dieses Interface keine zusätzlich Abstraktion bringt. Der Fehler kommt dann "durch die Hintertür", wenn die Implementierungsklasse aus anderen Gründen ein Interfaces implementieren muss (z.B. "Serializable"). Plötzlich entstehen so Exceptions, die da vorher nicht zu sehen waren.

Ausweg?

Schaut man einigermassen tief in die Spring Dokumentation, so findet sich eine Konfigurationseinstellung, die bewirkt, das das Erzeugen der Proxies immer mit Hilfe der CGLib gemacht wird. Das bewirkt als Nebeneffekt, dass der Cast aus dem Beispiel oben wieder funktioniert, denn nun ist der erzeugte Proxy eine echte Subklasse von SpringTestImpl und damit auch "castbar".

Die vollständige BeanFactory Konfiguration sieht dann z.B. so aus:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="proxyTargetClass" value="true"/>
</bean> <bean class="com.rinke.solutions.spring.test.aop.AnnotationSourceAdvisor">
<property name="annotationToCheck" value="com.rinke.solutions.spring.test.Writing"/>
<property name="advice" ref="writeableCheckInterceptor"/>
</bean> <bean id="writeableCheckInterceptor" class="com.rinke.solutions.spring.test.aop.WriteableCheckInterceptor">
<property name="readOnly" value="true"/>
<property name="exceptionToThrow" value="com.rinke.solutions.spring.test.ReadOnlyException"/>
</bean> <bean name="testBean" class="com.rinke.solutions.spring.test.SpringTestImpl" /> </beans>

Der entscheidende Konfiguration Schalter ist hier <property name="proxyTargetClass" value="true"/> womit eben immer auf CGLib zurückgegriffen wird.

Dieser Beitrag wurde unter Java veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *