Artikel

 
Mai 2009 | Artikel

Enterprise Eclipse RCP - Teil 2: Remoting und Caching

(Link zum Artikel: http://www.it-republik.de/jaxenter/artikel/2315)

Remoting und Caching in verteilten Eclipse-RCP-Anwendungen

Text: Peter Friese und Stefan Reichert
Nachdem wir im ersten Teil von Enterprise Eclipse RCP einen Überblick über die Entwicklung von Enterprise-RCP-Applikationen gegeben haben, wollen wir uns in diesem Artikel mit dem Themenkomplex der Kommunikation zwischen Frontend und Backend beschäftigen. Wie bereits im Überblicksartikel dargelegt, gibt es verschiedene Varianten für die Architektur von verteilten Eclipse-RCP-Anwendungen: Client/Server, d.h. der Client greift direkt auf die Datenbank zu, und 3+ Tier, d.h. der Client kommuniziert mit einem Backend-System, das die Geschäftslogik enthält. In diesem Artikel wollen wir zeigen, wie eine Eclipse-RCP-Anwendung mit einer klassischen Drei-Schichten-Architektur umgesetzt werden kann. Dazu zeigen wir zunächst, wie die Kommunikation zwischen Frontend und Backend umgesetzt werden kann. Anschließend gehen wir auf mögliche Probleme ein und zeigen Lösungen hierfür auf.
Teil 1   Teil 2   Teil 3   

Für die Kommunikation zwischen zwei verteilten Java-Anwendungen steht eine nahezu unüberschaubare Anzahl an Technologien zur Verfügung – angefangen bei Java RMI (Remote Method Invocation) über Web Services, Remote OSGi und CORBA bis hin zu eher proprietären Protokollen wie Hessian und Burlap. Der Einfachheit halber haben wir uns für Spring HTTPInvoker entschieden, da wir das Backend sowieso mit Spring realisieren wollen. Erfreulicherweise bietet Spring eine gute Abstraktionsschicht für Remoting-Protokolle, sodass ein späterer Wechsel des Transportprotokolls ohne große Änderungen möglich ist. Diese Abstraktionsschicht ist auch dafür verantwortlich, dass der entfernte Aufruf von Methoden völlig transparent und somit nicht von lokalen Aufrufen zu unterscheiden ist. Umständliche Lookups von Remote-Interfaces entfallen somit, was zu übersichtlicherem Quellcode führt. Um diese Illusion zu erzeugen, bedient sich Spring des Proxy-Patterns (siehe: Gamma, Helm, Johnson, Vlissides: "Design Patterns: Elements of Reusable Object-oriented Software", Addison-Wesley [1]). Eine als Proxy agierende Klasse nimmt Aufrufe entgegen und leitet sie an das ursprünglich gemeinte Objekt weiter. In Java lassen sich Proxies mithilfe des Dynamic Proxy API (siehe: Stephan Anft, Peter Friese: "Der Prokurist – praktische Anwendungsszenarien des Dynamic Proxy API", Java Magazin 10/2003. [2]) sehr elegant, weil generisch, implementieren.

Ein Beispiel
Zur Veranschaulichung soll ein einfacher Service zur Verwaltung von Personendaten dienen. Dieser Service stellt Methoden zum Anlegen und Modifizieren von Personendatensätzen bereit. Darüber hinaus implementiert der Service Methoden zum Laden von einzelnen bzw. einer Menge von Personendatensätzen (Listing 1). Die Kommunikation mit der Datenbank, in der diese Daten schlussendlich gespeichert werden, übernimmt eine DAO (DataAccessObject). Service und DAO werden von Spring per Dependency Injection verbunden (Listing 2). Bis hierhin entspricht dies exakt dem Vorgehen, das auch für die Implementierung einer Webapplikation angebracht wäre.

Listing 1: IPersonService.java
  1. public interface IPersonService {
  2. String sayHi(PersonDTO person);
  3. PersonDTO loadPerson(java.lang.Long id);
  4. List<PersonDTO> loadAll();
  5. PersonDTO savePerson(PersonDTO person);
  6. }


Listing 2: applicationContext.xml
  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  3. <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
  4. <property name="dataSource" ref="dataSource"/>
  5. </bean>
  6. <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
  7. <property name="driverClassName" value="${jdbc.driverClassName}"/>
  8. <property name="url" value="${jdbc.url}"/>
  9. <property name="username" value="${jdbc.username}"/>
  10. <property name="password" value="${jdbc.password}"/>
  11. </bean>
  12. <!-- Hibernate session factory -->
  13. <bean class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" id="sessionFactory">
  14. <property name="dataSource">
  15. <ref bean="dataSource"/>
  16. </property>
  17. <property name="configLocation"> <value>classpath:hibernate.cfg.xml</value>
  18. </property>
  19. </bean>
  20. <bean class=" de.eclipsemagazin.eercp.repository.resource.PersonDAO" id="personDAO">
  21. <property name="sessionFactory" ref="sessionFactory"/>
  22. </bean>
  23. <bean class=" de.eclipsemagazin.eercp.service.resource.PersonService" id="personService">
  24. <property name="personDAO" ref="personDAO"/>
  25. </bean>
  26. </beans>


Um den Service nun auch von extern (d.h. außerhalb der VM) ansprechbar zu machen, muss er exportiert werden. Dazu sind mehrere Schritte erforderlich. Zunächst muss der Service mithilfe des passenden Spring Service Exporters exportiert werden. In unserem Fall ist dies der HttpInvokerServiceExporter, der als Remote Proxy agiert. Die Verbindung zum eigentlichen Service wird über eine entprechende Dependency hergestellt (Listing 3). Alle Services einer Applikation, die von außerhalb angesprochen werden sollen, müssen analog in die Datei remoting-servlet.xml aufgenommen werden. Alle anderen Services sind nicht öffentlich und können somit auch nicht von außen aufgerufen werden. Die exportierten Services bilden also den Remoting-Kontext. Der Name der Datei für diesen Remoting-Kontext richtet sich nach dem Namen des Servlets, das die Services exportiert. Spring stellt dazu die Klasse DispatcherServlet zur Verfügung, die wie gewohnt in der Datei web.xml konfiguriert wird (Listing 4).

Listing 3: remoting-servlet.xml
  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
  3. <!-- remoting exporters -->
  4. <bean class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter" name="/PersonService">
  5. <property name="service" ref="personService"/>
  6. <property name="serviceInterface" value=" de.eclipsemagazin.eercp. service.resource.IPersonService"/>
  7. </bean>
  8. </beans>


Listing 4: web.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
  5. http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  6. <display-name>Dysis Backend</display-name>
  7. <context-param>
  8. <param-name>contextConfigLocation</param-name>
  9. <param-value>/WEB-INF/applicationContext.xml</param-value>
  10. </context-param>
  11. <listener>
  12. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  13. </listener>
  14. <servlet>
  15. <servlet-name>remoting</servlet-name>
  16. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  17. <load-on-startup>1</load-on-startup>
  18. </servlet>
  19. <servlet-mapping>
  20. <servlet-name>remoting</servlet-name>
  21. <url-pattern>/remoting/*</url-pattern>
  22. </servlet-mapping>
  23. </web-app>


Auf Clientseite muss der Service nun importiert werden, hierzu stellt Spring die Klasse HttpInvokerProxyFactoryBean bereit, die als lokaler Proxy für den Service fungiert. Damit der Proxy den lokalen Aufrufern das entsprechende Interface vorgaukeln kann, muss er mit dem zu implementierenden Interface konfiguriert werden. Weiterhin muss die URL angegeben werden, unter der der entfernte Service tatsächlich erreichbar ist. In Listing 5 sieht man, wie durch geschickten Einsatz eines PropertyPlaceHolderConfigurers dafür gesorgt wird, dass die Vorgabewerte für die URL mithilfe von System-Properties überschrieben werden können. So wird sichergestellt, dass der Client nicht neu kompiliert werden muss, wenn der Server umzieht und unter einer anderen URL erreichbar sein sollte. Auch die Umschaltung zwischen verschiedenen Instanzen (z.B. Integration/Produktion) des Systems kann mit diesem Mechanismus realisiert werden.

Listing 5: clientContext.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  4. <import resource="clientContextCustom.xml"/>
  5. <bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  6. <property name="properties">
  7. <props>
  8. <prop key="remoteServer">localhost</prop>
  9. <prop key="remotePort">8080</prop>
  10. <prop key="remoteContext">dysis/remoting</prop>
  11. </props>
  12. </property>
  13. <property name="systemPropertiesModeName"> <value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
  14. </property>
  15. </bean>
  16. <bean id="personService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
  17. <property name="serviceUrl" value="http://${remoteServer}:${remotePort}/${remoteContext}/PersonService" />
  18. <property name="serviceInterface" value=" de.eclipsemagazin.eercp.service.resource.IPersonService" />
  19. </bean>
  20. </beans>


Will ein Client eine Methode auf einem Service im Server aufrufen, muss er zunächst Zugriff auf den Proxy erhalten. Nachdem er diese Referenz erhalten hat, kann der Aufruf der Methode wie gewohnt durchgeführt werden und ist, wie bereits geschildert, vollständig transparent:

public Object[] getElements(Object parent) {
List <PersonDTO> allPersons = getPersonService().loadAll();
return allPersons.toArray();
}

Die Referenz auf den Proxy kann der Client auf unterschiedliche Art und Weise erhalten: Bei einer Variante wird das ServiceLocator-Pattern eingesetzt, bei der anderen nutzen wir clientseitige Dependency Injection. Schauen wir uns zunächst den Service-Locator-Ansatz an. Der Service Locator kennt den clientseitige Spring-Kontext (üblicherweise wird der Name der XML-Datei die den clientseitigen Spring-Kontext definiert, als Konstante hinterlegt) und hat für jeden im Client-Kontext verfügbaren Service eine getter-Methode (Listing 6). Die Referenz auf den Service-Proxy kann dann durch einen einfachen Aufruf der entsprechenden getter-Methode erfragt werden:

personService = ClientServiceLocator.instance().getPersonService();

Im Sinne der besseren Übersichtlichkeit sollte der Zugriff auf diese Referenz mithilfe einer privaten getter-Methode im Aufrufer gekapselt werden:

private IPersonService getPersonService() {
if (personService == null) {
personService = ClientServiceLocator.instance().getPersonService();
}
return personService;
}

Listing 6: ClientServiceLocator.java
  1. /**
  2. * Locates and provides all available application services.
  3. */
  4. public class ClientServiceLocator {
  5. private final String DEFAULT_CONTEXT_LOCATION = "clientContext.xml";
  6. private ClassPathXmlApplicationContext context = null;
  7. private String contextLocation;
  8. private ClientServiceLocator() {
  9. // shouldn't be instantiated
  10. }
  11. private final static ClientServiceLocator instance = new ClientServiceLocator();
  12. public static ClientServiceLocator instance() {
  13. return instance;
  14. }
  15. public synchronized void init(String applicationContextLocation) {
  16. contextLocation = applicationContextLocation;
  17. context = null;
  18. }
  19. public synchronized ApplicationContext getContext() {
  20. if (context == null) {
  21. if (contextLocation == null) {
  22. contextLocation = DEFAULT_CONTEXT_LOCATION;
  23. }
  24. Thread currentThread = Thread.currentThread();
  25. ClassLoader originalClassloader = currentThread
  26. .getContextClassLoader();
  27. try { currentThread.setContextClassLoader(this.getClass()
  28. .getClassLoader());
  29. context = new ClassPathXmlApplicationContext(contextLocation);
  30. } finally { currentThread.setContextClassLoader(originalClassloader);
  31. }
  32. }
  33. return context;
  34. }
  35. public final IPersonService getPersonService() {
  36. return (IPersonService) getContext().getBean("personService");
  37. }
  38. }


Wie man leicht sieht, ist der Zugriff auf entfernte Services mithilfe des Service Locator Patterns recht old-school und unnötig explizit. Neben der im Backend eingesetzten Sping-gesteuerten Dependency Injection sieht dieser Code regelrecht altbacken aus.

Teil 1   Teil 2   Teil 3   

andere Artikel dieser Serie


Anzeige

Kommentare


Anzeige

zurück zum Seitenanfang