Eine der wichtigsten Sprachfunktionen von Java ist der dynamische Proxy. Irgendwann muss die Fähigkeit, dynamische Proxies zu erstellen, Java davor bewahrt haben, in der öden Welt der streng objektorientierten Sprachen zu verschwinden. Heutzutage werden dynamische Proxies hauptsächlich von Frameworks wie Spring AOP und Hibernate verwendet, aber hin und wieder ergibt sich eine Gelegenheit, einen dynamischen Proxy im Anwendungscode zu verwenden.
Dynamische Proxies Der dynamische Proxy-Mechanismus ermöglicht es dem Entwickler, eine Implementierung einer Schnittstelle zu erstellen, ohne direkt die implementierende Klasse zu schreiben. Stattdessen schreibt der Entwickler einen java.lang.reflect.InvocationHandler und bindet ihn an eine java.lang.reflect.Proxy-Instanz. Die Proxy-Instanz erhält ebenfalls die Schnittstelle und die Java-Plattform sorgt dafür, dass die resultierende Instanz die Schnittstelle implementiert. Alle Methodenaufrufe auf der Instanz laufen über die invoke-Methode des InvocationHandlers. Wozu soll das gut sein? Wir müssen immer noch eine Klasse schreiben! Anstatt einfach eine Implementierung der Schnittstelle zu schreiben, müssen wir eine Klasse mit einer einzigen Methode schreiben, die die gesamte Geschäftslogik enthält. Der Mechanismus ist besonders nützlich bei übergreifenden Belangen wie Sicherheit, transparente Persistenz oder Transaktionsunterstützung. Dies sind Funktionen, die Sie auf eine Vielzahl von Methoden in ähnlicher Weise anwenden möchten. Mit dem dynamischen Proxy kann eine Implementierung dieser Funktion auf alle Methoden in einer Schnittstelle angewendet werden. Dafür verwenden die Frameworks diese Funktion. Im Allgemeinen wird ein Delegat verwendet. Im Aufruf-Handler wird das übergreifende Anliegen auf den Aufruf angewandt und dann wird dieselbe Methode auf dem Delegaten aufgerufen. Der Delegat implementiert also die gleiche Schnittstelle wie der Proxy. Transformation In einem meiner letzten Projekte bin ich auf eine andere Situation gestoßen, in der ein dynamischer Proxy nützlich war. Bei dem Projekt handelte es sich um eine sehr einfache Webanwendung, die eine Benutzeroberfläche auf der Grundlage einer Reihe von Webdiensten bereitstellen sollte. Die Webdienste waren bereits implementiert und eine WSDL-Datei wurde bereitgestellt. Ich verwendete Apache Axis, um den Java-Code zu generieren, der für die Kommunikation mit den Webdiensten erforderlich war. Dieser generierte Code war sehr komplex. Er verwendete einen einzigen "In"- und einen einzigen "Out"-Parameter für jeden Webservice. Diese Klassen waren für die Webanwendung, die ich erstellen wollte, nicht geeignet. Sie mussten also in die einfachen Objekte umgewandelt werden, die im Rest der Anwendung verwendet werden.
Es gab eine Reihe von Webmethoden, die die Anwendung verwenden würde, und daher war dieser Transformationsprozess ein übergreifendes Anliegen. Die Schnittstelle zu den Webdiensten, die die Anwendung verwenden würde, wäre nun viel einfacher als die Schnittstelle, die die von AXIS generierten Klassen bieten. Genauer gesagt, würde sie mehrere einfache Argumenttypen für jede Methode haben, anstatt eines komplexen und eines einfachen Rückgabetyps. Eine Delegation wäre daher nicht möglich. Wie würde also der InvocationHandler aussehen? Er sollte wissen, welcher Transformer für welchen Methodenaufruf aufgerufen werden muss. Der Transformer sollte wissen, wie er die einfachen Eingabetypen der Anwendungsschnittstelle in den einzelnen komplexen Eingabetyp des Webservice umwandeln kann und wie er den komplexen Ausgabetyp des Webservice in den einfachen Ausgabetyp der Anwendungsschnittstelle umwandeln kann. Außerdem könnten diese Transformatoren keine gemeinsame Schnittstelle haben, da die Signatur der Transformationsmethoden für jede Methode unterschiedlich wäre. Die Transformationsmethode für die Eingabe hätte die gleichen Parametertypen wie die Methode auf der Anwendungsschnittstelle und den Eingabetyp des Webdienstes als Ergebnistyp. Die Transformationsmethode für die Ausgabe würde den Ausgabetyp des Webdienstes als einzelnen Parametertyp und den Ergebnistyp der Anwendungsschnittstelle als Ergebnistyp haben. Die Transformation würde also Reflexion und eine Namenskonvention verwenden, um die richtigen Transformationsmethoden zu finden. Der Aufruf auf dem Delegaten (in diesem Fall die von AXIS generierten Klassen, die den Aufruf des Webdienstes ausführen) wird ebenfalls Reflection verwenden. Die Methode, die aufgerufen werden soll, unterscheidet sich in mindestens zwei Punkten von der Methode auf der Schnittstelle, die aufgerufen wurde: die Eingabeparametertypen und der Rückgabetyp. Der Einfachheit halber wird davon ausgegangen, dass der Name der Methode derselbe ist. Der Kern der Implementierung ist ziemlich einfach: [java] public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Object transformer = transformers.get(methodName); if (transformer == null) { throw new ConfigurationException("Kein Transformator gefunden für: '"+methodName+"'."); } Object actualArgument = transformInput(method, args, transformer); Object soapAnswer = callDelegate(methodName, actualArgument); return transformOutput(transformer, soapAnswer); } [/java] Die Komplexität liegt hauptsächlich in der Fehlerbehandlung der Reflexion. Diese ist nun in den Methoden versteckt, die im obigen Codefragment verwendet werden (siehe Anhang für den vollständigen Quellcode). Wenn ein Fehler auftritt, entweder während der Transformation oder während der Delegation, wird der Fehler in eine java.lang.reflect.UndeclaredThrowableException verpackt und aus dem generierten Proxy-Code geworfen. Dies ist wichtig, wenn Sie dem Benutzer umfassende Fehlermeldungen anzeigen möchten, z.B. im Falle eines Kommunikationsfehlers.
Das Bild oben zeigt die Interaktion zwischen der Proxy-Instanz, dem Transformator und dem Delegaten. Es versucht auch, die "Schnittstelle" zu erklären, die die Transformatoren implementieren sollten. Ein Transformator sollte demnach über eine [code]ComplexTypeIn transformIn (SimpleTypeIn1, SimpleTypeIn2, ...)[/code] Methode und eine [code]SimpleTypeOut transformOut(ComplexTypeOut)[/code] Methode. Spring-Konfiguration Die Webanwendung, die ich erstellt habe, verwendet Spring für die Konfiguration, daher musste ich den Proxy in einen Spring-Kontext einbinden. Um den korrekten Proxy als Spring-Bean zu erstellen, könnte eine Bean-Factory verwendet werden, genau wie die org.springframework.aop.framework.ProxyFactoryBean, die zur Erstellung von Spring-AOP-Proxies verwendet wird. Ich habe mich jedoch dafür entschieden, ihn direkt mit den konkreten Klassen zu konfigurieren. [xml] <bean id="..." class="java.lang.reflect.Proxy" factory-method="newProxyInstance"> <konstruktor-arg index="0"> <bean class="org.springframework.util.ClassUtils" factory-method="getDefaultClassLoader"/> </constructor-arg> <constructor-arg index="1" value="MyInterface"/> <konstruktor-arg index="2"> <bean class="com.xebia.proxy. Transformator"> <konstruktor-arg index="0"> <ref bean="delegate"/> </constructor-arg> <konstruktor-arg index="1"> <Karte> <entry key="methodenname1"> <ref bean="transformer1"/> </entry> <entry key="methodenname2"> <ref bean="transformer2"/> </entry> ... </map> </constructor-arg> </bean> </constructor-arg> </bean> [/xml] Der standardmäßig verwendete Classloader gibt den Thread-Kontext-Classloader zurück, falls verfügbar. Dadurch kann dieser Kontext in einen Spring-Kontext eingebunden werden, der aus einem anderen Bereitstellungspaket (.JAR, .WAR oder .EAR) geladen wird. Dadurch kann der Transformations-Proxy, sowohl der Code als auch die Konfiguration, von mehreren (Web-)Anwendungen gemeinsam genutzt werden.
Verfasst von
Maarten Winkels
Unsere Ideen
Weitere Blogs
Contact



