Guice with GWT
This article shows how to configure Guice with server side code in Google Web Toolkit (GWT) applications1. I use Guice 2.0 with GWT 1.7.0.
Steps include:
- Obtain the Guice JAR files
- Extend GWT’s
RemoteServiceServlet - Extend Guice’s
GuiceServletContextListener - Extend Guice’s
ServletModule - Set all
RemoteServicerelative paths to GWT.rpc - Configure
GuiceFilterand your context listener in web.xml - (optional, but good) Your remote service implementation classes no longer have to extend
RemoteServiceServlet
Phew! That seems like a long list, but I want to ensure I list every step. This approach is based on the excellent GuiceRemoteServiceServlet by Pavel Jbanov. His initial work pointed me in the right direction, making this much easier.
Get Guice
Grab guice-2.0.zip from the Guice downloads page. This ZIP contains all of the required JARs. You may also want guice-2.0-src.zip.
To use Guice with GWT, you’ll need two JARs: guice.jar and guice-servlet.jar. Just drop these into your war/WEB-INF/lib directory.
Extend GWT’s RemoteServiceServlet
This code comes almost verbatim from Pevel Jbanov’s blog, with the modifications mentioned in the first comment on that page. Here is the source:
@Singleton
public class GuiceRemoteServiceServlet extends RemoteServiceServlet {
@Inject
private Injector injector;
@Override
public String processCall(String payload) throws SerializationException {
try {
RPCRequest req = RPC.decodeRequest(payload, null, this);
RemoteService service = getServiceInstance(
req.getMethod().getDeclaringClass());
return RPC.invokeAndEncodeResponse(service, req.getMethod(),
req.getParameters(), req.getSerializationPolicy());
} catch (IncompatibleRemoteServiceException ex) {
log("IncompatibleRemoteServiceException in the processCall(String) method.",
ex);
return RPC.encodeResponseForFailure(null, ex);
}
}
@SuppressWarnings({"unchecked"})
private RemoteService getServiceInstance(Class serviceClass) {
return (RemoteService) injector.getInstance(serviceClass);
}
}
All GWT RPC requests will go through this single servlet. Thus, your remote service implementation classes will not have to extend RemoteServiceServlet.
GuiceFilterwill inject theInjector- The @Singleton annotation is required for Guice-managed servlets
Extend Guice’s GuiceServletContextListener
This step creates a Guice-friendly servlet context listener.
public class MyGuiceContextListener extends GuiceServletContextListener {
@Override
protected Injector getInjector() {
return Guice.createInjector(
new MyServletModule(),
new AnotherOneOfMyModules(), etc...);
}
}
You can hook up as many Guice modules as you want. The first, MyServletModule, replaces much of what traditionally goes in web.xml.
Extend Guice’s ServletModule
In order to use constructor injection with servlets, Guice must construct your servlet instances. Thus, you no longer list each servlet in web.xml. Here is a simple module:
public class MyServletModule extends ServletModule {
@Override
protected void configureServlets() {
serve("/MyApp/GWT.rpc").with(GuiceRemoteServiceServlet.class);
// cannot use @ImplementedBy
bind(MyService.class).to(MyServiceImpl.class);
}
}
Go check out the Guice ServletModule documentation for a full explanation.
You cannot use @ImplementedBy on the MyService interface. That’s because MyService is in GWT client code, and the MyServiceImpl is in GWT server code. Instead, the binding is configured in MyServletModule as shown.
Remember — this replaces most of what you typically place in web.xml.
Set all RemoteService relative paths to GWT.rpc
All of your GWT remote services will dispatch to the GuiceRemoteServiceServlet shown earlier.
@RemoteServiceRelativePath("GWT.rpc")
public interface MyService extends RemoteService {
...
}
Create the web.xml
In old-style GWT, you list your remote service implementations in the GWT module XML file (MyApp.gwt.xml). If you have any servlets listed there, you can remove them. Your GWT module should look something like this:
<module rename-to='MyApp'> <inherits name='com.google.gwt.user.User'/> etc... <entry-point class='com.abc.whatever.client.MyEntryPoint'/> </module>
Your web.xml should list the Guice filter and your custom Guice context listener.
<web-app>
<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.yourcompany.server.MyGuiceContextListener</listener-class>
</listener>
<!-- NOTE: List all Servlets in MyServletModule for Guice injection -->
<welcome-file-list>
<welcome-file>Css.html</welcome-file>
</welcome-file-list>
</web-app>
Clean up Remote Service Implementations
Your GWT remote service implementations do not have to extend RemoteServiceServlet any more.
Old code:
public class MyServiceImpl
extends RemoteServiceServlet implements MyService {
...
}
New code:
public class MyServiceImpl implements MyService {
private final AnInterface anInterface;
// Guice injection here!
@Inject
public MyServiceImpl(AnInterface anInterface) {
this.anInterface = anInterface;
}
...
}
Summary
Once all of the above pieces are in place, you can use normal Guice dependency injection in all of your GWT remote service implementation classes. This includes both constructor and setter injection.
Please let me know if you find any mistakes, and I’ll be sure to update this article.
1 To use Guice with GWT client code, check out the GIN project. (I have not tried GIN)
Updates
Sep 17, 2009
Changed: RPCRequest req = RPC.decodeRequest(payload);
To this: RPCRequest req = RPC.decodeRequest(payload, null, this);
Eric, you continue to be awesome. I’ve linked this from here:
http://code.google.com/docreader/#p=google-guice&s=google-guice&t=ExternalDocumentation
If you’d like to contribute an extension to the framework that supplies this stuff, it would be quite welcome.
Mmmm that code looks purty. Can’t believe I’m currently stuck with f-ing spring xml files currently.
Is it possible to download the whole project somewhere?
In GuiceRemoteServiceServlet, you’ll run into SerializationPolicy issues if you decode your requests as shown in the blog post. You COULD just make all of your rpc classes implement IsSerializable, but the right way to do it (so that you don’t end up using the LegacySerializationPolicy) is to change your decode from:
RPCRequest req = RPC.decodeRequest(payload);
to read this like so:
RPCRequest req = RPC.decodeRequest(payload, null, this);
where we don’t care about an interface to restrict our instantiation to so we pass null, and we use our class as the SerializationPolicyProvider.
@rl337 thanks for the tip, I updated the code to do it that way.
Hey Eric,
Thank you for putting it all together. I am on a GWT project and would like to use it. But I cannot use GWT for all Service implementations. I want to use it for a new SvcImpl I am writing. Is it possible to use Guice selectively. And is so how? Can you give me an example…?
I tried to follow the example above but get an exception while trying to run in hosted mode. Have you been able to use it in Hosted Mode?
Also, is it possible to download the sample project above from somewhere?
Look forward to your reply and thanks once again for this article.
Ravi
Thanks very much for the excellent idea and working code! I used your solution above to also solve another problem–handling the setup of the guice injector (usually done context listener) when running a GWTTestCase. GWTTestCase sets up it’s servlet container with no hook back to stick the context listener on. So, I just override the init() method in this class and since it’s the only servlet to create the Injector needed for the test case. Then, you can put your servlet in a servlet tag in you .gwt.xml and it all just works…
thanks
again
Here is a sketch if it helps:
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// this code is the critical connector for dealing with GWTTestCase
// the ONLY case where you arrive here without the injector having
// been already injected is when you are running in a GWTTestCase
// because it gives you no hook to allow you to attach your context
// listener
if (injector == null) {
// create your context listener here
// you will have to expose the “getInjector” method as public
// assign the inject from the getInjector method
}
}
Thanks Eric for mentioning my work.
I like your version, it’s much cleaner.
Awesome! It worked like a charm, thanks a lot Eric.
In case someone wants an example, my GWT/Guice/JPA app can be downloaded from Github:
http://github.com/tiagofernandez/bazooka
The slight difference is the server-side is implemented with Scala instead of Java.
Thanks for writing this up. No small error to fix in this post. @RemoteServiceRelative should be @RemoteServiceRelativePath.
Cheers.
@Craig Thanks! I fixed that just now.
Without using the extends RemoteServiceServlet, how can I use method like getServletContext().getServerInfo() and getThreadLocalRequest().getHeader(”User-Agent”)? Thanks! -Ming
If you need to use HttpServletRequest, HttpSession, etc, you simply @Inject those objects wherever you need them. This is mentioned here: http://code.google.com/p/google-guice/wiki/ServletModule
Is this what I should do?
Old code:
@SuppressWarnings(”serial”)
public class GreetingServiceImpl extends RemoteServiceServlet implements
GreetingService {
public String greetServer(String input) {
String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader(”User-Agent”);
return “Hello, ” + input + “!I am running ” + serverInfo
+ “.It looks like you are using:” + userAgent;
}
}
New code:
@SuppressWarnings(”serial”)
public class GreetingServiceImpl implements GreetingService {
HttpServletRequest request;
HttpServlet servlet;
@Inject
public GreetingServiceImpl(HttpServletRequest request, HttpServlet servlet) {
this.request = request;
this.servlet = servlet;
}
public String greetServer(String input) {
String serverInfo = servlet.getServletContext().getServerInfo();
String userAgent = request.getHeader(”User-Agent”);
return “Hello, ” + input + “!I am running ” + serverInfo
+ “.It looks like you are using:” + userAgent;
}
}
You probably need to inject Provider<HttpServletRequest> for this reason from that same page I linked to: “Note that if you want access to any of the request or session scoped classes from singletons or other wider-scoped instances, you should inject a Provider<T> instead.”
Here is my modified code. I have to a cast to request. Does this look right? Thanks! -Ming
private Provider request;
private HttpServlet servlet;
@Inject
public GreetingServiceImpl(Provider request, HttpServlet servlet) {
this.request = request;
this.servlet = servlet;
}
public String greetServer(String input) {
String serverInfo = servlet.getServletContext().getServerInfo();
String userAgent = ((HttpServletRequest) request).getHeader(”User-Agent”);
return “Hello, ” + input + “!I am running ” + serverInfo
+ “.It looks like you are using:” + userAgent;
}
Hello
thx for the Info
Did you also tried to implement JPA and EJB3 with it
regards toine
Hello again
I tried to use it but i get the error
Error 404: SRVE0190E: File not found: /bw_gxt_01/GWT.rpc
protected void configureServlets() {
serve(”/bw_gxt_01/GWT.rpc”).with(GuiceRemoteServiceImpl.class);
System.out.println(”BwServletModel.configureServlets()”);
bind(ListService.class).to(ListServiceImpl.class);
}
@RemoteServiceRelativePath(”GWT.rpc”)
public interface ListService extends RemoteService {
Am i forgetting something
regards toine
Solution is simple. I forgot to adapt the the path in serve()
serve( “/myweb/GWT.rpc” ).with( GuiceRemoteServiceServlet.class );