Using Guice to inject an appropriate Dao is kind of a no-brainer goal. Use it to inject one that uses the App Engine datastore for production, and one that's got a dummy at the back end for testing.
Prepare to be Guiced
We need to make a Data Access Object interface that the methods needing it can use. The way that makes most sense to me is to rename our current DAO class to DaoGaeDatastore (correcting the capitalization to match the Java naming conventions), and extract from that a new Dao interface.
Eclipse makes this really easy to accomplish. The Refactor->Rename... menu item handles renaming the class, as well as updating all references to it in the code to the new name. The Refactor->Extract Interface... applied to DaoGaeDatastore pulls out the interface Dao, also with no real trouble. While I was at it, I removed the extra Objectify object I had added to the DAO, and used the ofy() method implemented by its parent DAOBase.
To the interface and implementation class, I added a method named deleteAllUsers(), which does exactly what you'd expect based on the name.
com/lisedex/volinfoman/server/DaoGaeDatastore.java
@Override public void deleteAllUsers() { ofy().delete(ofy().query(User.class).fetchKeys()); }
One of the Objectify.delete() methods accepts an iterable collection of objects that can be a mix of Keys and POJOs (Plain Old Java Object). Executing a query() on User.class will return all Users, and the Query.fetchKeys() method will return an iterable collection of their Keys. We can just pass this collection to Objectify.delete(), and all of our Users in the datastore disappear.
We can now convert our BuildDB class, which was built between the last blog entry and this one, to use Dao.deleteAllUsers(), and remove its dependency on an Objectify object.
Let the Guicing commence
We need the guice-servlet jar.
I used the Guice integration documentation to see how to get Guice set up for the App Engine environment. All servlets where we want to use Guice need to be moved from their web.xml declarations into Guice module configurations. Also, all servlets must be have the Singleton scope, so we can either use the @Singleton notation in the class definitions themselves, or follow the design philosophy we used for Gin and declare them all in a Guice module. I chose the latter for consistency.
First we build an AbstractModule called VolinfomanGuiceModule. Its sole function is to override the configure() method to declare our Guice object bindings.
com/lisedex/volinfoman/server/guice/VolinfomanGuiceModule.java
public class VolinfomanGuiceModule extends AbstractModule { @Override protected void configure() { // Servlets bind(UserServiceImpl.class).in(Singleton.class); bind(BuildDB.class).in(Singleton.class); } }
Since all servlets need to have Singleton scope, and we're not injecting them with replacement objects, this is all the binding we need.
If you've looked at the code before now, you'll notice we're using a library called gwt-log with a remote logging servlet that's been declared in web.xml. We're not moving this to the Guice configuration because it doesn't need Guice injection right now, and it also produces a warning when it's not declared in web.xml. This warning problem is corrected in r504 of gwt-log, but I'm only using the release version.
Next, we need a ServletModule that sets up what URLs each servlet handles, so we build VolinfomanServletModule and override configureServlets().
com/lisedex/volinfoman/server/guice/VolinfomanServletModule.java
public class VolinfomanServletModule extends ServletModule { @Override protected void configureServlets() { serve("/volinfoman/user").with(UserServiceImpl.class); serve("/volinfoman/admin/builddb").with(BuildDB.class); } }
Each serve() call tells Guice which URL to map to which servlet class. Each servlet can handle multiple URLs through multiple calls to serve().
Finally, we need a GuiceServletContextListener that provides a Guice Injector that knows about the modules so it can configure the injection properly.
com/lisedex/volinfoman/server/guice/VolinfomanGuiceServletContextListener.java
public class VolinfomanGuiceServletContextListener extends GuiceServletContextListener { @Override protected Injector getInjector() { return Guice.createInjector( new VolinfomanServletModule(), new VolinfomanGuiceModule()); } }
In web.xml we need to remove references to the servlets we're configuring through Guice, and add the configuration for Guice in their place. After taking out the BuildDB and UserServiceImpl declarations, but leaving the RemoteLoggerServiceImpl for gwt-log, we put in the Guice code.
war/WEB-INF/web.xml
<!-- Configure Guice servlet, other servlets configured in VolinfomanServletModule --> <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.lisedex.volinfoman.server.guice.VolinfomanGuiceServletContextListener</listener-class> </listener>
We set up a GuiceFilter with a URL pattern that describes which URL will be processed by Guice. We also plug our GuiceServletContextListener as a listener so Guice gets configured when the application is deployed.
Moving the BuildDB servlet to Guice configuration stops the authentication we had set up through a <security-constraint> in the servlet container. We'll have to add security in the code itself later, but for now the BuildDB servlet is open to the world.
Guiced!
All that set up for what will appear to be a pretty small payoff, but we're ready to use Guice anywhere we want in our servlets, as well as Gin in our GWT client.
We want to inject an instance of DaoGaeDatastore in place of fields declared as Dao in our servlets. First we need to configure this in VolinfomanGuiceModule.
com/lisedex/volinfoman/server/guice/VolinfomanGuiceModule.java
// Data providers bind(Dao.class).to(DaoGaeDatastore.class).in(Singleton.class);
After adding this to the configure() method, a DaoGaeDatastore singleton will be provided to any servlet that has a class field of type Dao that we also annotate with @Inject. So we need to inject it in UserServiceImpl.
com/lisedex/volinfoman/server/UserServiceImpl.java
@Inject private Dao dao;
This replaces the previous declaration for dao, and tells Guice to do the injection for that field. The rest of the class stays the same, since we were already using the Dao interface to define our interaction with the datastore.
In BuildDB we remove the local variable dao in doGet(), and use the same code as above to declare it as a class variable that needs injection.
That's it. The project is now Guicified.
No comments:
Post a Comment