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("<b>Vaadin " + Version.getFullVersion() + "</b>");
}
@Provides
private Class extends UI> 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.