Generische Proxy-Generierung mit Spring
Im Moment arbeite ich an einem generischen Verfahren zum Aufruf von Methoden auf entfernten Objekten. Die genaue Beschreibung wird wahrscheinlich in den nächsten Tagen folgen, ein Problem, für das ich heute eine Coole Lösung gefunden habe kommt aber schon heute:
Von einem Klienten aus sollen Methodenaufrufe auf dem Server möglch sein. Dazu wird serverseitig eine Klasse implementiert, die ein beliebiges Interface mit beliebigen Methoden bereit stellt, wobei alle Parameter Serializeable implementieren. Die Klasse registriert sich beim Initialisieren bei einer ServiceReistry, die die Instanze bereitstellt. Das wars dann auch schon serverseitig. Auf der Clientseite möchte ich nun die Methoden des Interfaces nutzen können, aber anstelle von konkreten Aktionen auszuführen sollen bei jeden Aufruf einer Methode Nachrichten, über einen beliebigen Kommunikationskanal Nachrichten, an den Server geschickt. Das klingt nach dem typischen Einsatzgebiet für einen Proxy, der das Interface implementiert und die Methodenaufrufe auf den Server umleitet und anschließend der Rückgabewert zurück gibt.
Als kleines Beispiel hier der Läufer, der eine gewisse Distanze zurücklegen soll. Wir wollen ihm von Klienten durch den Aufruf der Methode run(String) mitteilen, wie groß diese ist.
public interface Runner {
public void run(long distance);
}
Diese Klasse implementiert nun Runner und macht irgendwas tolles beim Aufruf von run(String), was ist für dieses Beispiel unwichtig.
public class RunnerImpl implements Runner {
public void run(long distance) {
..
..
}
}
Nun folge die Erzeugung des Proxies. Es sein noch angemerkt, dass hier werder das Verfahren, mit dem die Methode auf RunnerImpl entgültig aufgerufen wird, noch die Übermittlung des Aufrufs von Belangen ist. Gegeben ist nur, dass wir ein Request-Objekt für jeden Aufruf machen, dieses an den Server leiten und der die richtige Instanz für das Ausführen findet.
Um nun die eine Instanz der Klasse RunnerImpl im Client verwenden zu können erzeugen wir einen Proxy zuerst auf die herkömmliche Art. Dazu implementieren wir das Interface Runner und lenken die Aufrufe aus den Server um.
public class RunnerProxy implements Runner {
public void run(long distance) {
Request r = new Request(Runner.class, "run", new Object [] { distance })
server.handle(r) // hier wird der Request an den Server übergeben
}
}
Sieht einfach aus, ist es auch. Aber leider auch sehr nervig, wenn man viele Methoden hat und zu dem noch mehrere Interfaces. Alternativ könnte man die Request-Objekte natürlich auch für jeden Aufruf manuel erstellen, ohne den Einsatz des Proxies, aber ich bevorzuge es das in ein Objekt auszulagern. So braucht man sich auch nicht um Sessions, Connections usw. zu kümmern.
Nun das Beispiel mit Spring. Die Erzeugung des Proxies soll dann geschehen, wenn wenn eine Bean durch den Container erzeugt wird. Ich möchte dieser Bean ein beliebiges Interface setzen können, für das diese als Proxy dient. In der Konfiguration sieht das dann so aus:
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class ProxyGenerator impements FactoryBean, MethodInterceptor, InitializingBean {
private Class serviceInterface;
private Object proxy;
public Class getServiceInterface() { return serviceInterface; }
public void setServiceInterface(Class serviceInterface) {
this.serviceInterface = serviceInterface;
}
public void afterPropertiesSet() throws Exception {
proxy = ProxyFactory.getProxy(this.serviceInterface, this);
}
public Object getObject() throws Exception {
return proxy;
}
public Class getObjectType() {
return proxy != null ? proxy.getClass() : this.serviceInterface;
}
public boolean isSingleton() { return true; }
public Object invoke(MethodInvocation mi) throws Throwable {
Request r = new Request(serviceInterface, mi.getMethod().getName(), mi.getArguments())
return server.handle(r)
}
}
Zuerst einige Anmerkungen zu den Interfaces. FactoryBean wird von Spring zur Verfügung gestellt, um um Beans auszuzeichnen, die selbst Factories sind. Sie werden nicht direkt Benutzt, sondern ihre Methoden getObejct() und getObjectType(), um ein erzeugtes Object zu instanzieren. Passt natürlich super in diesem Fall, denn genau dieses Verhalten brauchen wir an dieser Stelle.
InitializingBean bring die Methode afterPropertiesSet() mit, die Aufgerufen wird, wenn alls properties gesetzt sind. Sie wird verwendet, um den Proxy zu erzeugen. Dazu kommt eine ProxyFactory zum Einsatz, der das Interface und ein Interceptor übergeben wird.
Schlussendlich wird das MethodInterceptor Interface implementiert, um in den Methodenaufruf einzugreifen. In diesem Fall wird dann das Request-Objekt erzeugt und an den imaginären Server geschickt.
Insgesamt eine sehr coole Art sich die langweilige Arbeit zu ersparen. Je weiter ich in die Spring-Welt eintauche, desto weniger möchte ich das Framework missen. Gerade wegen der Leichtigkeit, mit der man sowas wie hier beschrieben umsetzen kann.
Viel Spaß beim Coden!