Vaadin + Guice + JSR-330, czyli jak nie przespałem nocy
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.