8

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

Fagner Brack
  • 2,365
  • 4
  • 33
  • 69

3 Answers3

3

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)
Glen Best
  • 22,769
  • 3
  • 58
  • 74
  • Ok I tried the `@javax.annotation.ManagedBean` annotation in the class that extends the HttpServlet ( the one that is being mapped dinamically). My bean did not get injected. I tried enabling CDI by putting an empty beans.xml in the META-INF directory. Still did not get injected when using @Inject. Any idea of what could be? I will be doing several tests with the information I got so far though. – Fagner Brack May 08 '13 at 02:40
  • 1
    Thought that might happen when ServletContext creates the Servlet. Force the container to create the Servlet- for CDI, call servletContext.addServlet("your servlet name", @Inject YourServletClass servlet) – Glen Best May 08 '13 at 03:25
  • Just to clarify @GlenBest, "defined as part of Java EE 6 and JSR 299". JSR 299 defines Managed Beans, Java EE 6 defines the other classes supporting injection, including servlets (page 69 of the spec). There's no need to take an extra step to make the Servlet a ManagedBean. – Will Hartung May 08 '13 at 03:42
  • Java EE 6 (JSR 318) defines "base" Managed Beans (http://jcp.org/aboutJava/communityprocess/final/jsr316/index.html). JSR 299 defines extended Managed Beans. There is a need to take the extra step - the Servlet spec explicitly says that injection not supported except for managed beans. – Glen Best May 08 '13 at 06:12
  • The missing piece really is to use an already injected Servlet for creating the dynamic mappings. Thanks Glen – cmonsqpr Aug 11 '15 at 12:30
3

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.

Perception
  • 79,279
  • 19
  • 185
  • 195
  • Any solution without annotating every servlet? =( – Fagner Brack May 12 '13 at 03:42
  • @FagnerBrack - not that I've found so far. Is the issue that you don't have access to the servlet source? Or that there are simply way too many of them? – Perception May 14 '13 at 01:18
  • Actually I am creating a mapping convention to dinamicaly create Http URI paths according to a given jsp and servlet location (file system). The main use of the Java EE is for the back-end management. In this project I am not using JSF due to the restrictive nature of it and leveraging HTML5 Boilerplate, jQuery, requirejs, bootstrap (customized) and all front-end coolness JSF does not have build in. The main issue on using the annotation is that I should apply it on every class in the future instead of delegating such task for an automatic process. Just convenience. – Fagner Brack May 14 '13 at 01:26
0

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.

Will Hartung
  • 115,893
  • 19
  • 128
  • 203
  • Thanks for the response man. Unfortunately it did not worked when loading through the ServletContextListener. I have edited the question above, you may tell me if I have done something wrong. – Fagner Brack May 07 '13 at 00:46