Vaadin 7 i Guice 3.0, czyli o prostocie rzeczy niemożliwych

Wprowadzenie Vaadin 7 oznaczało całkowicie nową filozofię tworzenia aplikacji. Wiele elementów frameworku napisano od nowa. Tym samym na drzewo wysłano cały know-how dotyczący integracji pomiędzy Vaadin i innymi frameworkami. Głównym problemem stało się to jak obecnie wygląda zarządzanie główną klasą aplikacji. We wcześniejszych wersjach pod spodem po prostu tworzono nowy obiekt przez refleksję w serwlecie, teraz jest to trochę bardziej skomplikowane. Dodano nową warstwę UIProvider, która odpowiada za wyszukanie klasy rozszerzającej UI i dalej już przez refleksję. Zaletą tej metody jest możliwość zachowania zgodności wstecznej z poprzednią wersją frameworku dzięki zastosowaniu LegacyApplicationUIProvider.

Pierwsza moja próba integracji Vaadin z Guice 3.0 spełzła na niczym właśnie przez dodanie tej jednej dodatkowej warstwy. Spowodowało to, że zgubiłem się na poziomie DefaultUIProvider, a dokładnie na etapie rozpoznania jak działa wywołanie metod za pomocą zdarzeń.

Druga próba okazała się znacznie bardziej owocna. Podszedłem do tego zagadnienia trochę inaczej niż poprzednio, a mianowicie na chama zacząłem stosować rozwiązania z Vaadin 6.

Generujemy zatem projekt Vaadin i jazda…

Własny UIProvider

Zacząłem od napisania własnego UIProvider.

Listing 1. MyUIProvider

public class MyUIProvider extends UIProvider {

	@Inject
	private Class<? extends UI> uiClass;

	@Override
	public UI createInstance(UICreateEvent event) {
		return MyGuiceFilter.getInjector().getProvider(uiClass).get();
	}

	@Override
	public Class<? extends UI> getUIClass(UIClassSelectionEvent event) {
		return uiClass;
	}

}

W oryginale (to pisząc mam na myśli domyślną implementację) metoda createInstance jest adapterem wywołania metody fabrykującej z obiektu event. Tu brutalnie odpinamy się od mechanizmów frameworku i dalej lecimy po swojemu. Druga metoda jest abstrakcyjna i tak trzeba ją zaimplementować zatem – durnostojka.

Filtr

„Magia” Guice w wersji webowej polega na rozszerzeniu filtru.

Listing 2. MyGuiceFilter

@WebFilter(urlPatterns = "/*")
@WebFilter(urlPatterns = "/*")
public class MyGuiceFilter extends GuiceFilter {

	private static Injector injector;

	public static Injector getInjector() {
		return injector;
	}

	@Override
	public void doFilter(ServletRequest request, 
			ServletResponse response, FilterChain chain) 
			throws ServletException, IOException {
		if(injector==null){
			injector = Guice.createInjector(new SimpleGuiceModule());
		}
		super.doFilter(request, response, chain);
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		if (injector != null) {
			throw new ServletException("Injector already created?!");
		}
		injector = Guice.createInjector(new SimpleGuiceModule());
		filterConfig.getServletContext().
			log("Created injector with " 
				+ injector.getAllBindings().size() 
				+ " bindings.");
		super.init(filterConfig);
	}

}

tu dodatkowy „hak” – jeżeli injector jest null w czasie wykonania filtra oznacza to, że filtr został przeładowany przez JRebel. Na etapie developerki nieocenione rozwiązanie.

Moduł

Filtr dostarcza Guicowi injector, który ma zainstalowany jeden moduł. W tym module mamy:

Listing 3. SimpleGuiceModule

public class SimpleGuiceModule extends ServletModule {

	@Override
	protected void configureServlets() {
		serve("/*").with(MyGucieServlet.class);

		bind(String.class).annotatedWith(Names.named("title"))
			.toInstance("Hello Guice!");
		bind(String.class).annotatedWith(Names.named("version"))
			.toInstance("Vaadin " + Version.getFullVersion() + "");
	}

	@Provides
	private Class provideUIClass() {
		return MyVaadinUI.class;
	}

}

Servlet

Czas na clou, czyli na naszą implementację servletu Vaadin. W Starszej wersji było to proste, tu też jest to proste. Domyślna implementacja została przeze mnie „zaorana” na rzecz czegoś bardziej prymitywnego 😉

Listing 4. MyGucieServlet

@Singleton
public class MyGucieServlet extends VaadinServlet implements SessionInitListener {

	@Inject
	private MyUIProvider myUIProvider;

	@Override
	protected void servletInitialized() {
		getService().addSessionInitListener(this);
	}

	@Override
	public void sessionInit(SessionInitEvent event) throws ServiceException {
		event.getSession().addUIProvider(myUIProvider);
	}

}

Tradycyjnie singleton, który dostarcza naszą wersję UIProvider.

Nasze UI

Na koniec klasa aplilkacji:

Listing 5. MyVaadinUI

@Theme("mytheme")
@SuppressWarnings("serial")
public class MyVaadinUI extends UI {

	@Inject
	@Named("title")
	private String title = "Tytuł";

	@Inject
	@Named("version")
	private String version = "Wersja";


	@Override
	protected void init(VaadinRequest request) {
		final VerticalLayout layout = new VerticalLayout();
		layout.setMargin(true);
		setContent(layout);
		getPage().setTitle(title);
		Button button = new Button("Click Me");
		button.addClickListener(new Button.ClickListener() {
			public void buttonClick(ClickEvent event) {
				layout.addComponent(new Label(version));
			}
		});
		layout.addComponent(button);
	}

}

Podsumowanie

Czasami jest tak, że prowadząc rozpoznanie jakiejś nowej wersji znanej technologii pod kątem jej użycia i integracji z innymi rozwiązaniami trafiamy na mur – nie da się. Warto wtedy spróbować zadziałać zwykłym bruteforcem i spróbować rozwiązać problem tak jak w dotychczasowych przypadkach. Wyniki mogą być nieoczekiwanie dobre.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax