Fagner Brack
Fagner Brack

Reputation: 2403

Injecting an EJB into a dynamic mapped servlet

I have a filter where I am dinamically mapping servlet classes:

    @Override
    public void init( FilterConfig filterConfig ) throws ServletException {
        servletContext = filterConfig.getServletContext();

        File directory = getConventionDirectory();
        FileSystemInspector fileInspector = new FileSystemInspector();
        Set<ActionInfoData> actions = fileInspector.getActions( directory );

        for ( ActionInfoData action : actions ) {
            servletContext
                .addServlet( action.getServletName(), action.getClassName() )
                .addMapping( action.getServletMapping() );
        }

    }

Then when I access a given mapping the EJB is not injected.

    @EJB
    private I18nManager i18nManager;

    @Override
    protected void doGet( HttpServletRequest request, HttpServletResponse response )
    throws ServletException, IOException {
        I18nManager i18n = i18nManager; //null
    }

If I manually create a mapping in the web.xml the given EJB is working in that servlet. It makes me wonder if the fact I am registering the servlets at runtime the container does not consider those servlets as managed.

If that is the case what is the proper way to inject the EJBs into my servlets without changing the way they are dinamically registered via filter?

Is via JNDI the only way to inject my EJBs?

EDIT 1: I have tried to implement a ServletContextListener class as suggested by "Will" using the following code in the web.xml:

<listener>
        <listener-class>com.megafone.web.filter.convention.InitServlet</listener-class>
    </listener>

And the relevant part of the implementation:

...

@Override
    public void contextInitialized( ServletContextEvent sce ) {
        ServletContext servletContext = sce.getServletContext();

        FileSystemInspector fileInspector = new FileSystemInspector();
        Set<ActionInfoData> actions = fileInspector.getActions( getConventionDirectory() );

        for ( ActionInfoData action : actions ) {
            servletContext
                .addServlet( action.getServletName(), action.getClassName() )
                .addMapping( action.getServletMapping() );
        }
    }

...

Unfortunately it does not make the container inject the EJBs and the null pointer remains. I am currently making a custom type safe JNDI lookup to the service. Obviously this is far more expensive than using the proper injection (correct me if I am wrong, have done no experiments regarding performance yet).

Using:
Java EE 6
JBoss AS 7.1

Upvotes: 8

Views: 3684

Answers (3)

Perception
Perception

Reputation: 80603

The problem seems related to this reported bug which is as yet unresolved. Resource resolution works just fine for Managed Beans as defined by the JSF specification, but not for CDI Managed Beans. Simply annotating your dynamic servlet classes with @javax.faces.bean.ManagedBean should fix the problem (yes, its quite an ugly solution):

@ManagedBean
public class DynServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @EJB
    private LoginService loginService;

    public DynServlet() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        response.getOutputStream().println(
                "Request made to: " + getClass().getSimpleName());
        response.getOutputStream().println("Login Service: " + loginService);

        return;
    }
}

@WebListener
public class DynamicServletLoadListener implements ServletContextListener {

    public DynamicServletLoadListener() {
        super();
    }

    @Override
    public void contextDestroyed(final ServletContextEvent contextEvent) {
        return;
    }

    @Override
    public void contextInitialized(final ServletContextEvent contextEvent) {
        contextEvent.getServletContext().addServlet("dynservlet", DynServlet.class)
                .addMapping("/services/dynservlet");
    }
}

Tested with JEE6 (ofc) and both JBoss 7.1.1 and 7.2.0 (EAP 6.1.0 Alpha).

Edit:

The problem with dynamic mapped servlets is actually pretty deep in the base JBoss architecture. They use JBossWeb (a forked version of Tomcat) as the servlet implementation, and in the guts of its context management code it makes a determination wether to instantiate new components via injection or regular new. Afaik as of date, your servlets would need to be annotated in some fashion in order for them to be processed via injection: I had mentioned @ManagedBean in my original answer but it looks like annotating with @WebServlet works as well.

Upvotes: 3

Will Hartung
Will Hartung

Reputation: 118641

First, in my test, this worked fine using a version of Glassfish V3.

But, second, you may well be running afoul of this clause of the Servlet 3.0 spec.

The following methods are added to ServletContext since Servlet 3.0 to enable programmatic definition of servlets, filters and the url pattern that they map to. These methods can only be called during the initialization of the application either from the contexInitialized method of a ServletContextListener implementation or from the onStartup method of a ServletContainerInitializer implementation.

Notably, these methods can NOT be called from a Filter.init() method. I originally tried this in a Servlet.init() method, and the init method failed because the context was already initialized.

So, my experiment did not duplicate your test exactly -- I did not use a Filter.init() method for this, rather I put the code in a ServletContextListener. And when I did that, my @EJB annotation was honored.

Edit:

As un-helpful as it sounds, I would suggest this is a bug in JBoss. When I initially tried your original code with the injection from the Filter, Glassfish threw an exception, since you're not allowed to do the injection save where I mentioned earlier. Now perhaps that's an "added feature" of JBoss, but apparently the @EJB injection processing simply isn't working. According to spec, this should work as advertised.

Upvotes: 0

Glen Best
Glen Best

Reputation: 23105

Servlet 3.0 Spec, Sect. 4.4.3.5

Resource injection [e.g. @EJB] on all components (Servlets, Filters and Listeners) added programmatically or created programmatically, other than the ones added via the methods that takes an instance, will only be supported when the component is a Managed Bean. For details about what is a Managed Bean please refer to the Managed Bean specification defined as part of Java EE 6 and JSR 299.

Managed Bean Declaration

A Java EE 6 managed bean is annotated @javax.annotation.ManagedBean and has a no-arg constructor. A JSR 299 (CDI) managed bean merely needs a no-arg constructor or a constructor annotated @javax.inject.Inject.

Answer

To enable resource injection you need to:

  • place @ManagedBean annotation on dynamically added servlet

    OR

  • enable CDI & include an empty beans.xml


Edit

Even though you're creating the servlet dynamically, it's important that the container does the creation. Don't think creation within ServletContext will support injection. Servlet doc very vague here.

With CDI try:

 servletContext.addServlet("your servlet name", @Inject YourServletClass servlet)

Upvotes: 3

Related Questions