Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 423
Fagner Brack Avatar asked May 05 '13 22:05

Fagner Brack


People also ask

How can we inject the beans in EJB?

Like resource injection, you can inject Session bean references into another EJB or other managed class using annotations or deployment XML. If you prefer annotations you can use @javax. ejb. EJB annotations to inject either remote or local EJB references.

What is the difference between @inject and @EJB?

@EJB injects EJBs only, but @Inject can be used to inject POJOs rather than EJBs. However, @Inject requires that your archive be a BDA (contain beans. xml for EE 6, or implicitly in EE 7). @Inject also has additional CDI-specific capabilities (scopes, interceptors, etc.), but those capabilities incur extra overhead.

What is EJB dependency injection?

EJB 3.0 specification provides annotations, which can be applied on fields or setter methods to inject dependencies. EJB Container uses the global JNDI registry to locate the dependency. Following annotations are used in EJB 3.0 for dependency injection. @EJB − used to inject other EJB reference.

What does dependency injection in EJB 3.0 mean?

To facilitate test driven development, the EJB 3.0 specification allows you to use annotations to inject dependencies through annotations on fields or setter methods.


2 Answers

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.

like image 164
Perception Avatar answered Oct 06 '22 00:10

Perception


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)
like image 26
Glen Best Avatar answered Oct 06 '22 02:10

Glen Best