Inject an EJB into JAX-RS (RESTful service)

81,735

Solution 1

I am not sure this is supposed to work. So either:

Option 1: Use the injection provider SPI

Implement a provider that will do the lookup and inject the EJB. See:

Example for com.sun.jersey:jersey-server:1.17 :

import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import javax.ejb.EJB;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.ws.rs.ext.Provider;
import java.lang.reflect.Type;

/**
 * JAX-RS EJB Injection provider.
 */
@Provider
public class EJBProvider implements InjectableProvider<EJB, Type> {

    public ComponentScope getScope() {
        return ComponentScope.Singleton;
    }

    public Injectable getInjectable(ComponentContext cc, EJB ejb, Type t) {
        if (!(t instanceof Class)) return null;

        try {
            Class c = (Class)t;
            Context ic = new InitialContext();

            final Object o = ic.lookup(c.getName());

            return new Injectable<Object>() {
                public Object getValue() {
                    return o;
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

Option 2: Make the BookResource an EJB

@Stateless
@Path("book")
public class BookResource {

    @EJB
    private BookEJB bookEJB;

    //...
}

See:

Option 3: Use CDI

@Path("book")
@RequestScoped
public class BookResource {

    @Inject
    private BookEJB bookEJB;

    //...
}

See:

Solution 2

This thread is rather old, nevertheless i fought the same problem just yesterday. Here is my solution:

Just make the BookResource a managed bean through @javax.annotation.ManagedBean at class level.

For this to work you need to enable CDI with a beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_0.xsd">
</beans>

This file needs to be in WEB-INF if the BookResource is part of a war file. If the BookResource is packaged with the ejbs put it into META-INF.

If you want to use @EJB you're done. If you want to inject the EJB through @Inject than a beans.xml must be put into the ejbs jar file into META-INF as well.

What you're doing: You're just telling the container that the resource should be container managed. Therefor it supports injection as well as lifecycle events. So you have your business facade without promoting it to an EJB.

You don't need to extend javax.ws.rs.core.Application for this to work. BookResource is as a root resource automatically request scoped.

Tested with Glassfish 3.1.2 and a maven project.

Happy coding.

Solution 3

You shall be able to do injection in JAX-RS resource without making it EJB or CDI component. But you have to remember that your JAX-RS resource must not be singleton.

So, you setup your application with this code. This makes BookResource class per-request JAX-RS resource.

@javax.ws.rs.ApplicationPath("application")
public class InjectionApplication extends javax.ws.rs.core.Application {
  private Set<Object> singletons = new HashSet<Object>();
  private Set<Class<?>> classes = new HashSet<Class<?>>();

  public InjectionApplication() {
    // no instance is created, just class is listed
    classes.add(BookResource.class);
  }

  @Override
  public Set<Class<?>> getClasses() {
    return classes;
  }

  @Override
  public Set<Object> getSingletons() {
    return singletons;
  }
}

With this setup, you are letting JAX-RS to instantiate BookResource for you on per-request basis and also inject all the required dependencies. If you make BookResource class singleton JAX-RS resource, this is, you put in getSingletons

public Set<Object> getSingletons() {
  singletons.add(new BookResource());
  return singletons;
}

then, you created instance which is not managed by JAX-RS runtime and nobody in container cares to inject anything.

Solution 4

Unfortunately, my answer is too long for a comment, so here goes. :)

Zeck, I hope that you are aware of what exactly you are doing by promoting your bean to an EJB, as suggested by Pascal. Unfortunately, as easy as it is nowadays with Java EE to 'make a class an EJB', you should be aware of the implications of doing so. Each EJB creates overhead along with the additional functionality it provides: they are transaction aware, have their own contexts, they take part in the full EJB life cycle, etc.

What I think you should be doing for a clean and reusable approach is this: extract the access to your servers services (which hopefully are accessed through a SessionFacade :) into a BusinessDelegate. This delegate should be using some kind of JNDI lookup (probably a ServiceLocator - yes, they are still valid in Java EE!) to access your backend.

Okay, off the record: if you really, really, really need the injection because you do not want to write JNDI access manually, you could still make your delegate an EJB, although it ... well, it just feels wrong. :) That way at least it will be easy to replace it later with something else if you do decide to switch to a JNDI lookup approach...

Solution 5

I was trying to do the exact same thing. I'm using EJB 3.1 and have a deployed my app as an EAR with separate EJB project. As Jav_Rock pointed out, I use context lookup.

@Path("book")
public class BookResource {

  @EJB
  BookEJB bookEJB;

  public BookResource() {
    try {
        String lookupName = "java:global/my_app/my_ejb_module/BookEJB";
        bookEJB = (BookEJB) InitialContext.doLookup(lookupName);
    } catch (NamingException e) {
        e.printStackTrace();
    }
  }

  @GET
  @Produces("application/xml")
  @Path("/{bookId}")
  public Book getBookById(@PathParam("bookId") Integer id) {
    return bookEJB.findById(id);
  }
}

See the link below for very useful JNDI look up tips

JNDI look up tips

Share:
81,735
Zeck
Author by

Zeck

Updated on January 11, 2020

Comments

  • Zeck
    Zeck over 4 years

    I'm trying to inject a Stateless EJB into my JAX-RS webservice via annotations. Unfortunately the EJB is just null and I get a NullPointerException when I try to use it.

    @Path("book")
    public class BookResource {
    
        @EJB
        private BookEJB bookEJB;
    
        public BookResource() {
        }
    
        @GET
        @Produces("application/xml")
        @Path("/{bookId}")
        public Book getBookById(@PathParam("bookId") Integer id)
        {
            return bookEJB.findById(id);
        }
    }
    

    What am I doing wrong?

    Here is some information about my machine:

    • Glassfish 3.1
    • Netbeans 6.9 RC 2
    • Java EE 6

    Can you guys show some working example?

  • LeChe
    LeChe over 12 years
    Errr, that is what you get when you do not read the question completely. In the case of your RESTful service, arguably, the service itself is your BusinessDelegate. So there you have it: the first and last part of my statement are still totally valid, while I would probably not create a BusinessDelegate for the BusinessDelegate... :)
  • Jin Kwon
    Jin Kwon almost 12 years
    Dear @Pascal Thivent Is that making JAX-Resources as @Stateless the only way to go?
  • Jin Kwon
    Jin Kwon almost 12 years
    The 'singleton' thing is not relevant with @EJB problem, right?
  • Michael Simons
    Michael Simons over 11 years
    I wasn't comfortable making my api endpoints EJBs neither, but you can just use @ManagedBean. See my answer, if you like
  • Viacheslav Dobromyslov
    Viacheslav Dobromyslov about 11 years
    Your offer of JNDI is developed in Option 1: Use the injection provider SPI example.
  • NBW
    NBW about 11 years
    @Pascal Option 3 can also look like this: @EJB private BookEJB bookEJB and it will work (GF 3.1.2)
  • Laird Nelson
    Laird Nelson almost 11 years
    I know this answer is extremely old but: META-INF/beans.xml doesn't have anything to do with the Java EE @ManagedBean annotation; you shouldn't need it. I too use @ManagedBean to get my resource class to permit @EJB references inside it and didn't need META-INF/beans.xml involved. It's worth noting as well that if you do this you are hosed if you want to override the @EJB annotations later in a deployment descriptor, because there isn't one for @ManagedBeans.
  • user613114
    user613114 almost 10 years
    In my case, my REST resource was singleton. So even if I inject session bean (using @EJB or @Inject), by bean instance was always null, causing NullPointerException. Your last hint regarding singleton, helped me to resolve this issue. I made sure that my REST resource is not singleton.
  • Ryan
    Ryan over 9 years
    "I am not sure this is supposed to work." - Why? I would expect you could just inject an EJB without playing a bunch of games
  • Viacheslav Dobromyslov
    Viacheslav Dobromyslov about 9 years
    @Ryan: no it's not easy at all because of Glassfish 4.1 HK2 bugs. I faced the same issue today.
  • peez80
    peez80 about 9 years
    Old post but thank you so much @Martin! I was struggeling with this problem since yesterday. Your hint with singleton made it.
  • nilesh
    nilesh about 9 years
    @Pascal Thivent: Are there any disadvantages of 2nd option? like.. it will increase beans in jboss?
  • mwangi
    mwangi almost 9 years
    I did use this method and it did work.. However, the same code works in jboss well... So I am really not sure why this happens..
  • Christian13467
    Christian13467 about 8 years
    Option 2 and 3 worked for me only by adding beans.xml to application.
  • Christian13467
    Christian13467 about 8 years
    I had the same problem. I needed beans.xml to get it running. My environment was JBoss EAP 6.4 with jersey rest service. I ran into JBAS011859. But at the end it was running.
  • DavidS
    DavidS about 7 years
    People often talk about EJB "overhead" as if it were a well-established fact that they are heavyweight, but most developers have never bothered to measure it. Adam Bien showed that the performance difference between EJBs and POJOs was small (< 10%) in 2008 (adam-bien.com/roller/abien/entry/is_it_worth_using_pojos), and performance has only improved since then. In short, if you're trying to optimize your application for performance by comparing POJOs to EJBs, you're looking in the wrong place.
  • LeChe
    LeChe about 7 years
    @DavidS: There are a few points here: first of all, 10% is huge! Even if it is only 5 or 2% now, there is no logical reason to accept this kind of impact: it is an easy performance gain with no disadvantages. Also, as you know, the performance degradation is non-linear. Hence it does make sense to keep this in mind when building your system. And lastly, while I must admit that my response strongly focusses on the performance part, I think that there are other, maybe more important reasons for not making everything an EJB, like simplicity of your code and having a clear dependency chain.
  • DavidS
    DavidS about 7 years
    I think you're misinterpreting that 10% number, LeChe. It's not "my application is now 10% slower", it's one specific type of operation in the application that is 10% slower. Using EJBs instead of POJOs doesn't make network, DB, file IO, and rendering take 10% more time, and these operations make up the bulk of many systems. The overall performance impact of using EJBs versus POJOs on a real application is probably negligible. As for the latter part of your comment, I agree there are good reasons to not make everything an EJB: fortunately Pascal suggested two alternatives.
  • Black.Jack
    Black.Jack almost 7 years
    I won't put an EJB annotation for a Ws. Just annotate the ws pojo as @ManagedBean. This should let server container know that this pojo is something to be care about.
  • Matsu Q.
    Matsu Q. over 6 years
    On WebLogic 12c: Option 1 and 2 both worked, but no combination of annotations could get Option 3 working for me. I was intentionally trying to avoid making every class an EJB just to get injection to work, so I ended up settling on Option 1. If anyone ever gets a working example of option 3 in WebLogic, I'd love to see it!
  • cabaji99
    cabaji99 over 6 years
    I had to use this for an legacy app. yet in jboss 6.4 i found that only the complete jndi name worked: java:global/completeEar/completeEar-ejb-1.0-SNAPSHOT/TestEJB‌​Impl!personal.ejb.Te‌​stEJBRemote