Archiv für November 2010

Statische Schleusen – GWT 2.1 RequestFactory mit Spring

Freitag, 26. November 2010

Manchmal ist Vorfreude die schönste Freude, aber wenn die Vorfreude die einzige Freude ist, dann stimmt etwas nicht. So auch bei den heißersehnten RequestFactories von GWT 2.1. In Erwartung, dass damit der Zugriff auf Domain-Objekte/Entities stark vereinfacht werden soll, kam es – zumindest bei mir – zu einem jähen Erwachen, nachdem ich versuchte die RequestFactory auf ein Service-Klasse verweisen zu lassen, die von Spring verwaltet wird.

Die Entwickler der RequestFactory haben sich nämlich ein schönes Konstrukt in der Klasse ReflectionBasedOperationRegistry ausgedacht, welches es nur gestattet entweder statische Methode oder eine Methode der Entity selbst aufzurufen.

boolean isInstance = InstanceRequest.class.isAssignableFrom(requestMethod.getReturnType());
if (isInstance == Modifier.isStatic(domainMethod.getModifiers())) {
  throw new IllegalArgumentException("domain method " + domainMethod
          + " and interface method " + requestMethod
          + " don't match wrt instance/static");
}

Zur allgemeinen Verteidigung: in der kommenden Version GWT 2.1.1 wurde diese Einschränkung angeblich schon ausgebaut. Da zum aktuellen Zeitpunkt allerdings nicht klar ist, wann diese Version veröffentlicht wird, muss man sich mit anderen Mitteln behelfen. Eines dieser Mittel möchte ich kurz vorstellen.

Die Idee hinter dem ganzen ist es einen Adapter zu schreiben, der die notwendigen statischen Methoden für die RequestFactory bereitstellt und diese an den eigentlichen Service delegiert.
Ich gehe davon aus, dass der Leser mit dem Grundprinzip der GWT RequestFactory vertraut ist. Falls nicht, empfehle ich einen Blick auf diese Seite.

Als Erstes möchte ich die RequestFactory vorstellen:

public interface SampleRequestFactory extends RequestFactory {

    @Service(SampleRequestAdapter.class)
    interface SampleRequestContext extends RequestContext {
        Request<List<SampleEntityProxy>> findAll();
    }

    SampleRequestContext sampleRequestContext();
}

Für diejenigen, die sich mit Spring auskennen sei darauf hingewiesen, dass die obige Service-Annotation nicht die von Spring, sondern die von GWT ist.

Weiter im Text …

Der entsprechende Adapter könnte in etwa so aussehen.

public class SampleRequestAdapter {

    private static SampleService _sampleService;

    public static void setApplicationContext(ApplicationContext applicationContext) {
        _sampleService = applicationContext.getBean(SampleService.class);
    }

    public static List<SampleEntity> findAll() {
        return _sampleService.findAll();
    }
}

Soweit, sogut. Bisher ist noch nichts Spannendes passiert. Dennoch ist die Luft bereits erfüllt mit “Entwickler-Magie”. Die offene Frage ist nämlich: Wie wird die Methode setApplicationContext() aufgerufen?

Die Lösung ist zwar nicht zwingend schön, aber wirkungsvoll:

  • Man nehme eine Klasse, die von Spring verwaltet wird.
  • Mache diese Klasse ApplicationContextAware.
  • Gebe dieser Klasse die Adapter als Parameter und rufe für alle die Setter-Methode auf.

Im Code-Gewand sähe dies so aus:

public class ApplicationContextProvider implements ApplicationContextAware {
    private List<Class> _requestAdapters;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (_requestAdapters != null) {
            for (Class requestAdapter : _requestAdapters) {
                try {
                    final Method setter = requestAdapter.getDeclaredMethod("setApplicationContext", ApplicationContext.class);
                    if (Modifier.isStatic(setter.getModifiers())) {
                        setter.invoke(requestAdapter, applicationContext);
                    }
                // ... exception handling code
            }
        }
    }

    public List<Class> getRequestAdapters() {
        return _requestAdapters;
    }

    public void setRequestAdapters(List<Class> requestAdapters) {
        _requestAdapters = requestAdapters;
    }
}

Abschließend muss der Provider selbstverständlich noch für Spring verfügbar gemacht werden.

<bean class="de.sampleproject.ApplicationContextProvider">
  <property name="requestAdapters">
    <list>
      <value>de.sampleproject.SampleRequestAdapter</value>
    </list>
  </property>
</bean>