@EJB inside utility class is null

2019-08-04 15:23发布

问题:

I have a form in my application for which I'm trying to validate that the userName and/or email is not already on the DB.

This is service class:

@Stateless
public class CustomerFacade extends AbstractFacade<Customer> {

  @PersistenceContext(unitName = "OnlineStorePU")
  private EntityManager em;

  @Override
  protected EntityManager getEntityManager() {
    return em;
  }

  public CustomerFacade() {
    super(Customer.class);
  }

}

This is the entity class:

@Entity
@Table(name = "customer")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c"),
    @NamedQuery(name = "Customer.findById", query = "SELECT c FROM Customer c WHERE c.id = :id"),
    @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c WHERE c.name = :name"),
    @NamedQuery(name = "Customer.findByMobile", query = "SELECT c FROM Customer c WHERE c.mobile = :mobile"),
    @NamedQuery(name = "Customer.findByEmail", query = "SELECT c FROM Customer c WHERE c.email = :email"),
    @NamedQuery(name = "Customer.findByAddress", query = "SELECT c FROM Customer c WHERE c.address = :address"),
    @NamedQuery(name = "Customer.findByFax", query = "SELECT c FROM Customer c WHERE c.fax = :fax"),
    @NamedQuery(name = "Customer.findByTelephone", query = "SELECT c FROM Customer c WHERE c.telephone = :telephone"),
    @NamedQuery(name = "Customer.findByUsername", query = "SELECT c FROM Customer c WHERE c.username = :username")})
public class Customer implements Serializable {

//ALL GETTERS AND SETTERS + CONSTRUCTOR

}

This is the class checking the form:

public class ErrorUtils {

  @EJB
  private CustomerFacade customerFacade;

  //**the arg String uName is the username input from the form**
  private boolean isUserNameInDBOK(String uName) {
    boolean OKBD = true;
    //            Checking UserName is not in the DB
    for (Customer customer : customerFacade.findAll()) {
        if (customer.getUsername().equals(uName)) {
            OKBD = false;
            break;
        }
    }
    return OKBD;
  }
}

The error I'm getting is that the customerFacade is null:

Warning:   StandardWrapperValve[ControllerServlet]: Servlet.service()     
for servlet ControllerServlet threw exception java.lang.NullPointerException

How is this caused and how can I solve it?

回答1:

Injection via @EJB works only in container managed artifacts. I.e. instances of classes which are created by the container itself and thus not by you via the new operator.

E.g. a servlet:

@WebServlet("/your-servlet")
public class YourServlet extends HttpServlet {

    @EJB
    private YourService yourService;

}

The container does new YourServlet() all by itself. The container takes care of performing dependency injection. The container takes care of calling init(). The container takes care of calling appropriate doXxx() method. Etc. It's a container managed artifact.

However, you attempted to inject it in some utility class:

public class ErrorUtils {

    @EJB
    private CustomerFacade customerFacade;

}

This does not look like a container managed artifact. Given the complete absence of any container management related annotations on the class such as @Stateless, @ApplicationScoped, @WebServlet, etc, it's very much likely that you was just manually creating an instance of it yourself like below:

ErrorUtils errorUtils = new ErrorUtils();

And then you expected that @EJB magically gets injected.

Wrong.

You manually created an instance of ErrorUtils class using new keyword. You're manually managing it. So you basically also need to manually grab and set that EJB. Usually, you use JNDI for that, see also Inject EJB bean from JSF managed bean programmatically.

errorUtils.customerFacade = (CustomerFacade) new InitialContext().lookup("java:/.../CustomerFacade");

But, much better is to just turn that class into an container managed artifact. E.g. a CDI managed bean:

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ErrorUtils {

And then inject it in your servlet instead of manually crafting it:

@WebServlet("/your-servlet")
public class YourServlet extends HttpServlet {

    @Inject
    private ErrorUtils errorUtils;

}

It will take care that all of its dependencies like @EJB are also properly injected.


Unrelated to the concrete problem, the below doesn't make sense:

private boolean isUserNameInDBOK(String uName) {
    boolean OKBD = true;
    //            Checking UserName is not in the DB
    for (Customer customer : customerFacade.findAll()) {
        if (customer.getUsername().equals(uName)) {
            OKBD = false;
            break;
        }
    }
    return OKBD;
}

You're basically defeating the purpose of a relational database system and underestimating the powers of the structured query language SQL. You're basically copying the entire database table fully into Java's memory and then manually exploring every single record if it matches a given condition.

This is plain inefficient. You should be interacting with the database in such way that it returns exactly the information you need and nothing more. You can use among others the WHERE clause for this. It appears that your IDE was smart enough to autogenerate such a named query for you: Customer.findByName. Make use of it.

private boolean isUserNameInDBOK(String uName) {
    return customerFacade.findByName(name) != null;
}

This way the DB will return 1 or 0 records matching the given condition. You just have to check if it returned a record or not. Yet more efficient is to add a named query which does a COUNT(*) returning a long or perhaps boolean instead of a whole Customer. Even then, the whole ErrorUtils class is pretty superfluous in this construct. Just invoke the service method directly.