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 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.