Miało być miło, bo integracja Vaadin z Guice jest nawet opisana na wiki Vaadin… tyle tylko, że nie działa, bo nie ma prawa działać. Dlaczego, bo żaden kontener serwletów nie potrafi utworzyć instancji serwletu jeżeli ten ma konstruktor z parametrem, a nie ma bezparametrowego. Cóż… u mnie nie działa zatem trzeba znaleźć obejście.
Na początek dwa słowa o Guice i JSR-330. Pierwsze to jest framework DI dostarczony przez Google. Drugie to standard DI dla Java. Generalnie pierwsze implementuje drugie. Przy czym ważna rzecz JSR nie mówi jak ma odbywać się samo wstrzykiwanie zależność, a tylko z jakich adnotacji i interfejsów należy korzystać. Co ciekawe po połączeniu tego JSRa z Guice to i tak całość nie działa… Zatem z JSR-330 zrezygnowałem.

Konfiguracja Guice i własny serwlet

Zaczniemy od napisania własnego serwletu obsługującego Vaadin+Guice. Jest to rozszerzenie AbstractApplicationServlet, który „odwala” całą robotę związaną z zarządzaniem aplikacją i istnieniem w ramach sesji. Rozszerzenie nie jest skomplikowane ponieważ wystarczy, że implementuje dwie metody. Pierwszą, która zwróci nową instancję aplikacji i drugą, która zwróci obiekt Class extends Application>, który jest używany do dupereli typu klasy CSS.

Listing 1. GuiceApplicationServlet

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

import com.vaadin.Application;
import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;

@SuppressWarnings("serial")
@Singleton
public class GuiceApplicationServlet extends AbstractApplicationServlet {

	protected Provider<application> applicationProvider;

	public GuiceApplicationServlet() {
		VaadinGuiceConfiguration.createInjector.injectMembers(this);
	}

	@Override
	protected Class extends Application> getApplicationClass()
			throws ClassNotFoundException {
		return Application.class;
	}

	@Inject
	public void setApplicationProvider(Provider<application> applicationProvider) {
		this.applicationProvider = applicationProvider;
	}

	@Override
	protected Application getNewApplication(HttpServletRequest request)
			throws ServletException {
		return applicationProvider.get();
	}
}</application></application>

W kolejnym kroku należy trochę pozmieniać konfigurację w web.xml. Generalnie musimy dostosować ten plik do współpracy z Guice, czyli przede wszystkim zmusić injector tak by singletony były tworzone per sesja, a nie per aplikacja.

Listing 2. web.xml

<web-app id="WebApp_ID" version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><display-name>Vaadin Web Application</display-name><context-param><description>Vaadin production mode</description><param-name>productionMode</param-name><param-value>false</param-value></context-param><filter><filter-name>guiceFilter</filter-name><filter-class>com.google.inject.servlet.GuiceFilter</filter-class></filter><filter-mapping><filter-name>guiceFilter</filter-name><servlet-name>VAS</servlet-name></filter-mapping><listener><listener-class>pl.koziolekweb.vaadin.guice.servlet.VaadinGuiceConfiguration</listener-class></listener><servlet><servlet-name>VAS</servlet-name><servlet-class>pl.koziolekweb.vaadin.guice.servlet.GuiceApplicationServlet</servlet-class></servlet><servlet-mapping><servlet-name>VAS</servlet-name><url-pattern>/*</url-pattern></servlet-mapping></web-app>

Na zakończenie należy napisać własne rozszerzenie GuiceServletContextListener, które będzie dostarczać nam injector…

Listing 3. GuiceServletContextListener

public class VaadinGuiceConfiguration extends GuiceServletContextListener {

	public static Injector createInjector;

	@Override
	protected Injector getInjector() {
		createInjector = Guice.createInjector(new MyServletModule());
		return createInjector;
	}

}

Aplikacja Vaadin jako moduł Guice

Guice „myśli” modułami. Aplikacja Vaadin powinna być dostarczona w całości jako moduł Guice ze względu na to, że silnik Guice jest tak skonstruowany, że myśli „globalnie” w kontekście całego procesu serwera. Oznacza to, że po podpięciu dodatku do Guice, który pozwala na połączenie injectora z sesją serwera jedyną rozsądną drogą jest dostarczenie aplikacji jako specjalnej wersji modułu przystosowanego do pracy w środowisku webowycm.

Listing 4. MyServletModule

public class MyServletModule extends ServletModule {

	@Override
	protected void configureServlets() {
		serve("/*").with(GuiceApplicationServlet.class);
		bind(Application.class).to(ApplicationRoot.class).in(
				ServletScopes.SESSION);

		bind(ListBookView.class).to(ListBookViewImpl.class);
		bind(BookDao.class).to(SimpleBookDao.class);
		bind(ListBookPresenter.class).to(ListBookPresenterImpl.class);

	}
}

Na koniec należy sobie odpalić wszystko jako aplikację i gotowe:

Listing 5. MyServletModule

public class ApplicationRoot extends Application {
	private Window window;

	private ListBookPresenter listBookPresenter;
	private ListBookView listBookView;

	@Override
	public void init() {
		window = new Window("My Vaadin Application");
		setMainWindow(window);
		Button button = new Button("Zaladuj ksiązki");
		button.addListener(new Button.ClickListener() {
			public void buttonClick(ClickEvent event) {
				listBookPresenter.setListBookView(listBookView);
				listBookPresenter.showAll();
			}
		});
		window.addComponent(listBookView.getComponent());
		window.addComponent(button);

	}

	@Inject
	public void setListBookPresenter(ListBookPresenter listBookPresenter) {
		this.listBookPresenter = listBookPresenter;
	}

	@Inject
	public void setListBookView(ListBookView listBookView) {
		this.listBookView = listBookView;
	}

}

Odpalamy i działa

Podsumowanie

Da się połączyć Vaadin i Guice to jest pocieszające. Sposób połączenia jest trochę inny niż piszą o tym w necie to jest mniej pocieszające.
Generalnie się da.