8

I have a basic JAXRS service that I can expose easily, but for once I wish to use a dependency injection API and I suspect Google Guice will be one of the best. With this in mind, I have tried to integrate it, but the documentation is a little heavy going and I've been having to hunt around to try and find the right combination of

  • Web.xml
  • Context Listener (should I use ServletContainer or GuiceContainer)
  • Service
  • Whether to annotate the service with @Singleton or @Request or nothing (should I annotate with @Singleton - docs say I should but then says it defaults to request scope)
  • Whether to annotate the constructor parameters with @InjectParam

But currently I get errors from Google Guice and they change based on whether I use the @InjectParam annotation or not.

If I annotate with @InjectParam then I get

       Mar 29, 2013 9:52:04 PM com.sun.jersey.spi.inject.Errors processErrorMessages
   SEVERE: The following errors and warnings have been detected with resource and/or provider classes:
   SEVERE: The class com.hillingar.server.dao.interfaces.UserDao is an interface and cannot be instantiated.
   SEVERE: Missing dependency for constructor public com.hillingar.server.SessionUtility(com.hillingar.server.dao.interfaces.UserDao) at parameter index 0

If I don't annotate then I get

    Mar 29, 2013 9:54:59 PM com.sun.jersey.spi.inject.Errors processErrorMessages
SEVERE: The following errors and warnings have been detected with resource and/or provider classes:
  SEVERE: Missing dependency for constructor public com.hillingar.server.rest.UserService(com.hillingar.server.dao.interfaces.UserDao,com.hillingar.server.SessionUtility) at parameter index 0
  SEVERE: Missing dependency for constructor public com.hillingar.server.rest.UserService(com.hillingar.server.dao.interfaces.UserDao,com.hillingar.server.SessionUtility) at parameter index 1

This is my web.xml

    <?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <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.hillingar.server.ServletContextListener</listener-class>
    </listener>

    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
</web-app>

This is my ServletContextListener

package com.hillingar.server;

import java.util.logging.Logger;

import javax.servlet.ServletContextEvent;

import com.google.inject.Guice;
import com.google.inject.Singleton;
import com.hillingar.server.dao.jdbcImpl.UserJdbc;
import com.hillingar.server.dao.interfaces.UserDao;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import com.sun.jersey.spi.container.servlet.ServletContainer;

public class ServletContextListener implements javax.servlet.ServletContextListener {

    Logger logger = Logger.getLogger(this.getClass().getName());

    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
    }

        /*
         * Covered in URL
         * https://code.google.com/p/google-guice/wiki/ServletModule
         */
    @Override
    public void contextInitialized(ServletContextEvent arg0) {

            // Note the user of JerseyServletModule instead of ServletModule
            // otherwise the expected constructor injection doesn't happen
            // (just the default constructor is called)
            Guice.createInjector(new JerseyServletModule() {
                @Override
                protected void configureServlets() {

                    /*
                     * Note: Every servlet (or filter) is required to be a 
                     * @Singleton. If you cannot annotate the class directly, 
                     * you must bind it using bind(..).in(Singleton.class), 
                     * separate to the filter() or servlet() rules. 
                     * Mapping under any other scope is an error. This is to 
                     * maintain consistency with the Servlet specification. 
                     * Guice Servlet does not support the 
                     * deprecated SingleThreadModel.
                     */
                    bind(SecurityFilter.class).in(Singleton.class);
                    bind(ServletContainer.class).in(Singleton.class);

                    /*
                     * Filter Mapping
                     * 
                     * This will route every incoming request through MyFilter, 
                     * and then continue to any other matching filters before 
                     * finally being dispatched to a servlet for processing.
                     * 
                     */

                    // SECURITY - currently disabled
                    // filter("/*").through(SecurityFilter.class);

                    /*
                     * Registering Servlets
                     * 
                     * This registers a servlet (subclass of HttpServlet) called 
                     * ServletContainer, the same one that I would have used in 
                     * the web.xml file, to serve any web requests with the 
                     * path /rest/*  i.e. ...
                     * 
                        <servlet>
                            <servlet-name>ServletAdaptor</servlet-name>
                            <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
                            <load-on-startup>1</load-on-startup>
                        </servlet>
                        <servlet-mapping>
                            <servlet-name>ServletAdaptor</servlet-name>
                            <url-pattern>/rest/*</url-pattern>
                        </servlet-mapping>

                     */
                    serve("/rest/*").with(ServletContainer.class); // JAX-RS

                    // Using this and it starts bitching about
                    // com.sun.jersey.api.container.ContainerException: The ResourceConfig instance does not contain any root resource classes.
                    // So presumably wants an Application class that enumerates 
                    // all my services?
                    //serve("/rest/*").with(GuiceContainer.class);

                    /*
                     * Bindings
                     */
                    bind(UserDao.class).to(UserJdbc.class);
                    bind(SessionUtility.class);
                }
            });
    }

}

This is my UserService

package com.hillingar.server.rest;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

import com.hillingar.server.SessionUtility;
import com.hillingar.server.dao.interfaces.UserDao;
import com.hillingar.server.model.User;
import com.hillingar.server.model.dto.AuthenticationResponse;

@Path("/user")
@Produces("application/json")
@Consumes({"application/xml","application/json"})
@Singleton // <-- Added Singleton here
public class UserService {

    private UserDao userDao;
    private SessionUtility sessionManager;

        /*
           Error if I annotate with @InjectParam...

           Mar 29, 2013 9:52:04 PM com.sun.jersey.spi.inject.Errors processErrorMessages
           SEVERE: The following errors and warnings have been detected with resource and/or provider classes:
           SEVERE: The class com.hillingar.server.dao.interfaces.UserDao is an interface and cannot be instantiated.
           SEVERE: Missing dependency for constructor public com.hillingar.server.SessionUtility(com.hillingar.server.dao.interfaces.UserDao) at parameter index 0

           Error If I don't annotate at all...
            Mar 29, 2013 9:54:59 PM com.sun.jersey.spi.inject.Errors processErrorMessages
            SEVERE: The following errors and warnings have been detected with resource and/or provider classes:
              SEVERE: Missing dependency for constructor public com.hillingar.server.rest.UserService(com.hillingar.server.dao.interfaces.UserDao,com.hillingar.server.SessionUtility) at parameter index 0
              SEVERE: Missing dependency for constructor public com.hillingar.server.rest.UserService(com.hillingar.server.dao.interfaces.UserDao,com.hillingar.server.SessionUtility) at parameter index 1

           (both output Initiating Jersey application, version 'Jersey: 1.13 06/29/2012 05:14 PM')
        */
    @Inject
    public UserService(UserDao userDao, SessionUtility sessionManager) {
        this.userDao = userDao;
                this.sessionManager = sessionManager;
    }

    @GET
    public List<User> test(@Context HttpServletRequest hsr) {
                // USER DAO IS ALWAYS NULL - CONSTRUCTOR INJECTION NOT WORKING
        User loggedInUser = userDao.findBySessionId(hsr.getSession().getId());
                ...
        return users;
    }

}
mkobit
  • 34,772
  • 9
  • 135
  • 134
Devology Ltd
  • 1,127
  • 1
  • 13
  • 32
  • What happens if you inject `UserDao` and `SessionUtility` as fields instead of as part of the constructor? – condit Mar 29 '13 at 23:01
  • I changed the constructor to be parameterless (and removed @Inject), then created the getter and setter (the setters with @Inject) for both UserDao and SessionUtility. Still neither get called. – Devology Ltd Mar 30 '13 at 09:50
  • This morning I changed two things, then it started to work as expected, 1) I changed my servlet context listener to extend GuiceServletContextListener and overridden the getInjector method, 2) Within the injector changed back to using serve("/rest/*").with(GuiceContainer.class); and this time it worked! – Devology Ltd Mar 31 '13 at 11:48
  • Hi Spiky, can you please tell us which is your init time in App Engine? I count 15 seconds for just one Resource class. Thanks – Michele Orsi Dec 23 '13 at 23:43
  • Hi Michele - I stopped using GAE in the end, I can't recall the timings. – Devology Ltd Dec 24 '13 at 21:59

1 Answers1

5

Changed the ServletContextListener to

package com.hillingar.server;

import java.util.logging.Logger;

import javax.servlet.ServletContextEvent;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.servlet.GuiceServletContextListener;
import com.hillingar.server.dao.jdbcImpl.UserJdbc;
import com.hillingar.server.dao.interfaces.UserDao;
import com.hillingar.server.rest.UserService;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import com.sun.jersey.spi.container.servlet.ServletContainer;

// (1) Extend GuiceServletContextListener
public class ServletContextListener extends GuiceServletContextListener {

    Logger logger = Logger.getLogger(this.getClass().getName());

    // (1) Override getInjector
    @Override
    protected Injector getInjector() {
        return Guice.createInjector(new JerseyServletModule() {
            @Override
            protected void configureServlets() {
                bind(SecurityFilter.class).in(Singleton.class);
                bind(UserService.class);// .in(Singleton.class);
                bind(ServletContainer.class).in(Singleton.class);

                // (2) Change to using the GuiceContainer
                serve("/rest/*").with(GuiceContainer.class); // <<<<---

                bind(UserDao.class).to(UserJdbc.class);
                bind(SessionUtility.class);
            }
        });
    }
}
Devology Ltd
  • 1,127
  • 1
  • 13
  • 32