GWT: Code Splitting mit ProviderBundle

Möchte hier mal wieder kurz was notieren, damit ich es selber nicht vergesse ;)

Code Splitting von GWT ist eine tolle Sache. Da ich kein Guru bin werde ich mal versuchen das in meinen laienhaften Worten zu erklären. GWT liefert alles was man programmiert an den Client aus, da das teilweise sehr viel sein kann wurde Code Splitting eingeführt. Presenter, deren Proxies mit der ProxyCodeSplit Annotation versehen sind werden erst vom Server nachgeladen, sobald diese das erste mal mit Inject verwendet werden. Dadurch kann der Client den GWT Code schneller laden, da dort nur die notwendigen Sachen hinterlegt werden. Der Code von Presentern die nicht von Anfang an benötigt werden, wird also erst bei deren erster Verwendung vom Server zum Client übertragen.

Diese Möglichkeit lässt es sinnvoll erscheinen, im gesamten Anwendungscode bzw. soviel wie möglich, Code Splitting zu verwenden. Ein zusätzlicher Vorteil ist, die Funktionalität der einzelnen Klassen kann schön klein gehalten werden, da der Presenter notwendige Klassen injiziert bekommen kann. Die Sache hat nur einen Haken: Das Nachladen benötigt erstens etwas Zeit und zweitens muss für jeden gesplitteten Code eine Anfrage an den Server gestellt werden. Jede Anfrage benötigt durch den Overhead noch zusätzlich Zeit, außerdem bearbeiten Browser i.d.R. nur zwei Anfragen an eine Domain gleichzeitig. Wird also ein Presenter angezeigt, der selber mehrere gesplittete Teile enthält, kann dies den Benutzereindruck trüben.

Die Lösung hierfür haben die Entwickler von GWT mit Provider Bundles geschaffen. In einem ProviderBundle legt man alle zusammenhängende Teile fest, die gemeinsam nachgeladen werden sollen. In der Anwendung an der ich gerade arbeite verwenden wir das, um bestimmte Aktionen auszulagern, die von mehreren Presentern verwendet werden können. Dadurch kann jede Aktion in einem übersichtlichen Class File programmiert werden das zudem noch in einer entsprechenden Package Hirarchie für eine leichtere Wartbarkeit abgelegt werden kann. Für jeden Presenter gibt es dann ein ProviderBundle das festlegt, welche Actions mit dem Presenter geladen werden sollen. Code Splitting und Provier Bundles können aber auch anders verwendet werden.

Die GWT bzw. GWTP Dokumentation erklärt das Vorgehen leider (noch) nicht. Für viele mag sich die Verwendung auch aus den Kommentaren der ProviderBundle Klasse erschließen, ich hatte hierbei jedoch einige Anlaufschwierigkeiten. Daher möchte ich hier mal kurz die Vorgehensweise skizzieren:

public class ShowGalleriesA extends UiAction {
	...
}

Das ist die Action Klasse. Die Implementierung ist für den Mechanismus nicht wichtig, daher hab ich sie weg gelassen. Man muss hier eigentlich nur beachten, das es nichts zu beachten gibt ;)
Es ist eine ganz gewöhnliche Java Klasse, es sind keine Annotations oder ähnliches notwendig (außer natürlich das was für die Funktion der Klasse benötigt wird). Auch UiAction, die Basisklasse, enthält nichts was mit dem Code Splitting zu tun hat.

public class DashboardPr extends PlacePresenter {
	    
	@ProxyCodeSplitBundle(bundleClass = DashboardBundle.class, id = DashboardBundle.ID_DashboardPresenter)
	@NameToken(NameTokens.dashboard)
	public interface MyProxy extends ProxyPlace {
	}

	@Inject
	public DashboardPr(
            DashboardIf view, 
            MyProxy proxy,
            final ShowGalleriesA aShowGalleries) {
		super(view, proxy);
		
		setActions(aShowGalleries);
	}
	...
}

Der Presenter hingegen ist schon interessanter. Die normalerweise verwendete @ProxyCodeSplit Annotation wird durch @ProxyCodeSplitBundle ersetzt. In den Parametern der Annotation muss angegeben werden welche Klasse das Bundle definiert und welche ID daraus der des annotierten Proxies entspricht. Siehe Definition des Provider Bundles:

public class DashboardBundle extends ProviderBundle {
	public final static int ID_DashboardPresenter = 0;
	public final static int ID_ActionShowGalleries = 1;
	public final static int BUNDLE_SIZE = 2;
	
	@Inject
	public DashboardBundle(
			final Provider dashboardPresenter,
			final Provider actionShowGalleries) {
		super(BUNDLE_SIZE);

		providers[ID_DashboardPresenter] = dashboardPresenter;
		providers[ID_ActionShowGalleries] = actionShowGalleries;
	}
}

Sollte eigentlich relativ selbsterklärend sein ;)
Alle Teile des Bundles (die also zusammen geladen werden sollen) werden mit @Inject dem Konstruktor übergeben und in einem Feld providers der Basisklasse ProviderBundle hinterlegt. Wichtig ist hierbei, dass die Größe des providers Array dem Superklassen Konstruktor richtig mitgegeben wird. Am einfachsten macht man das über die gezeigten (und auch von Google empfohlenen) statischen Konstanten mit denen man dann auch auf die jeweiligen IDs der einzelnen Teile des Bundles verweisen kann (siehe Parameter der @ProxyCodeSplitBundle Annotation in der Presenter Klasse).

Nun müssen im Ginjector die AsyncProvider für die Einzelteile definiert werden, damit das Injizieren funktioniert:

public interface MyGinjector extends Ginjector {
	...
	AsyncProvider getCustomerDetailPresenter();
	AsyncProvider getShowGalleriesAction();
	AsyncProvider getDashboardBundle();
	...
}

Und schon wird die Aktion automatisch mit dem Presenter geladen. Einfach oder? ^^


About this entry