Many posts, reference docs and demos I've seen on the Internet so far, do not employ one of my most favorite features in Guice: the ServletModule to configure Wicket's filter. I really like this feature because I really don't like configuration files and the Web Descriptor (a.k.a web.xml) file is one of the most disliked of them all :) but Thanks to Guice this file's annoyance goes almost unnoticed.
So just to make it clear, the purpose of this integration is to bypass wicket's filter configuration in web.xml and put it in code instead.
Before we start here are several links to posts and reference docs about Wicket and Guice integration that are more common (but do use web.xml to config wicket):
Wicket Guice GuiceWebApplicationFactory javadoc
Alastair Maw’s blog - Wicket gets guicy
Wicket examples - Guice
Atomic Gamer dev blog - Wicket, Guice and Warp-Persist
We start with the web.xml file:
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <filter> <filter-name>guiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class> </filter> <filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>com.codeark.wicketguicealt.Bootstrap</listener-class> </listener> </web-app>This web.xml only configures Guice filter and a GuiceServletContextListener as per the instructions here.
The Bootstrap class loads our modules:
public final class Bootstrap extends GuiceServletContextListener {
@Override
protected Injector getInjector() {
return Guice.createInjector(new MyModule(), new WebModule());
}
}
The binding and configuration of Wicket happens in WebModule (a subclass of ServletModule):public class WebModule extends ServletModule {
@Override
protected void configureServlets() {
bind(WebApplication.class).toProvider(WicketGuiceAppProvider.class);
// avoids "Error initializing WicketFilter - you have no <filter-mapping> element..."
// IllegalArgumentException
Map<String, String> params = new HashMap<String, String>();
params.put(WicketFilter.FILTER_MAPPING_PARAM, "/*");
filter("/*").through(WicketGuiceFilter.class, params);
}
} In the first line of configureServlets() we bind WebApplication to a provider, this provider will be our real application factory and will do all the work instead of GuiceWebApplicationFactory.
The GuiceWebApplicationFactory implementation requires that the injector will be stashed in the Servlet Context or that modules will be listed in the web.xml in an init-param like this:
Back to WebModule, the second important thing we do in configureServlets() is to configure the Wicket filter and filter mapping. We are using our own WicketFilter subclass called WicketGuiceFilter in order to utilize our provider and wire it into wicket's IWebApplicationFactory:
This concludes our integration, would love to hear any comments.
You can download just the full source of this post and web.xml here
OR
You can download a full eclipse dynamic web project with dependencies included, you will need to import it to your workspace and reconfigure a servlet container runtime (such as apache tomcat) before you can build.
OR
Get the above eclipse project via svn or simply browse the code for your convenience here:
https://www.codeark.com/svn/wicket.guice.alt/trunk
This repository is read only for anonymous access.
The GuiceWebApplicationFactory implementation requires that the injector will be stashed in the Servlet Context or that modules will be listed in the web.xml in an init-param like this:
<init-param>
<param-name>module</param-name>
<param-value>com.company.MyModule,com.company.MyOtherModule</param-value>
</init-param>
I think both options contradicts the real notion of Guice: "Rather than using an external XML file for configuration, Guice modules are written using regular Java code. Java is familiar, works with your IDE, and survives refactoring" and therefor I opted not to use GuiceWebApplicationFactory. Here is the provider we will use:public class WicketGuiceAppProvider implements Provider<WebApplication> {
private final Injector injector;
@Inject
public WicketGuiceAppProvider(Injector injector) {
this.injector = injector;
}
@Override
public WebApplication get() {
WicketGuiceApp app = new WicketGuiceApp(injector);
return app;
}
}
It would be awesome if we could call addComponentInstantiationListener(new GuiceComponentInjector(app, injector)) here instead of passing the injector in the constructor (on injecting it) but GuiceComponentInjector uses InjectorHolder in its constructor:public GuiceComponentInjector(Application app, Injector injector)
{
app.setMetaData(GuiceInjectorHolder.INJECTOR_KEY, new GuiceInjectorHolder(injector));
InjectorHolder.setInjector(this);
}
and InjectorHolder has a dependency on the application instance being tied to the current thread (Application.get() uses ThreadLocal):public static void setInjector(ConfigurableInjector newInjector)
{
Application application = Application.get();
application.setMetaData(INJECTOR_KEY, newInjector);
}Consequently, we can only call addComponentInstantiationListener() after Application.set() is called and the best place to do so is in the init() method of our WebApplication implementation. we could use Application.set() ourselves (tried that and it works fine) but the scary warnings in the javadoc are scary :-), so here is our WicketGuiceApp class with its init() method:public class WicketGuiceApp extends WebApplication {
private transient Injector injector;
public WicketGuiceApp(Injector injector) {
this.injector = injector;
}
@Override
protected void init() {
addComponentInstantiationListener(new GuiceComponentInjector(this, injector));
}
@Override
public Class<? extends Page> getHomePage() {
return HomePage.class;
}
}
Please note that I've marked the Injector as transient. Under the hood, GuiceComponentInjector will handle the Injector's reference serialization and deserialization.Back to WebModule, the second important thing we do in configureServlets() is to configure the Wicket filter and filter mapping. We are using our own WicketFilter subclass called WicketGuiceFilter in order to utilize our provider and wire it into wicket's IWebApplicationFactory:
@Singleton
public class WicketGuiceFilter extends WicketFilter {
@Inject private Provider<WebApplication> appsProvider;
@Override
protected IWebApplicationFactory getApplicationFactory() {
return new IWebApplicationFactory() {
@Override
public WebApplication createApplication(WicketFilter filter) {
return appsProvider.get();
}
};
}
}
The reason I chose to subclass WicketFilter instead of doing this inside configureServlets():Map<String, String> params = new HashMap<String, String>();
params.put(WicketFilter.APP_FACT_PARAM, WicketGuiceAppFactory.class.getName());
filter("/*").through(WicketFilter.class, params);
Is that WicketFilter instantiates the factory via reflection and the prevents Guice from doing its work.This concludes our integration, would love to hear any comments.
You can download just the full source of this post and web.xml here
OR
You can download a full eclipse dynamic web project with dependencies included, you will need to import it to your workspace and reconfigure a servlet container runtime (such as apache tomcat) before you can build.
OR
Get the above eclipse project via svn or simply browse the code for your convenience here:
https://www.codeark.com/svn/wicket.guice.alt/trunk
This repository is read only for anonymous access.
Thanks for sharing!
ReplyDeleteYeah nice more the Wicket and Guice way, remember to post it to the wicket user list..
ReplyDeleteThanks!
ReplyDelete(Posted to wicket list a few days ago)
Here some propositions :
ReplyDelete1. Don't use a Provider but inject directly the Application created by guice. That way the application can also have injected fields:
@Singleton
public class WicketGuiceFilter extends WicketFilter {
private final WebApplication app;
@Inject public WicketGuiceFilter(WebApplication app) {
this.app = app;
}
protected IWebApplicationFactory getApplicationFactory() {
return new IWebApplicationFactory() {
public WebApplication createApplication(WicketFilter filter) {
return app;
}};
}
}
2. If you use WARP, use a pattern to exclude the resources from the PersistenceFilter :
filterRegex("/*^(?!.*(swf|jpg|jpeg|png|ico|gif|css|js)).*$").through(PersistenceFilter.class);
Hi, thank you for your excellent input...
ReplyDelete1. I like this approach of injecting the app in the constructor, its very functional and clean, however, imo it breaks the semantics of createApplication(), in my mind this method should return a new instance each time its called.
I'm thinking about changing the provider to an assisted guice factory, so I can bind to different implementations of WebApplication in different project (which your approach supports, by the way)
2. Awesome tip! I think you should shoot it to the warp list so everyone will enjoy it :)
Hi, nice route!!
ReplyDeletecould you tell me how could I inject Shiro filters using this Guice approach?
thanks
One question, even using this approach we still need to use org.apache.wicket.guice.GuiceComponentInjector in app init()?
ReplyDeletepublic class WicketGuiceApp extends WebApplication {
...
protected void init() {
addComponentInstantiationListener(new GuiceComponentInjector(this, injector));
}
...
thanks
@Cristiano: Unfortunately I'm not familiar enough with Shiro to provide you with an answer
ReplyDelete@Anonymous: yes ;)
This comment has been removed by the author.
ReplyDeleteHi Yaniv, thanks for sharing this... It works nicelly with wicket 1.4.
ReplyDeleteBut when I tried to do the same with 1.5 it doesn't work yet..
Have you tried this approach with version 1.5 ?
thanks
Hi,
ReplyDeleteSorry for the delayed response, I have been a little bit away from the wicket world of pleasure lately :(
If you could provide more details here, I'll try to get into it more.
Yaniv
In the following piece of code:
ReplyDeleteparams.put(WicketFilter.APP_FACT_PARAM, WicketGuiceAppFactory.class.getName());
What is WicketGuiceAppFactory? Shouldn't it be WicketGuiceAppProvider?
Thanks for the post, very useful. For your information, I tried it with Wicket 1.5.4 and it seems to work (except a small serialization error: java.io.NotSerializableException: com.google.inject.internal.InjectorImpl$4...) trying to fix that.
Found it. In my previous comment, the class should be replaced by GuiceWebApplicationFactory instead... Maybe it's something specific to Wicket 1.5 ?
ReplyDeleteHTH
Its been a while and then some, since I dabbled in this code, but if memory serves, you shouldn't be in a situation where you are serializing the Injector, this means you're doing something wrong.
ReplyDeleteThis post is two years old, many things have changed in both Guice and Wicket since it was written.