web-dev-qa-db-fra.com

Injecter un EJB dans JAX-RS (service RESTful)

J'essaie d'injecter un EJB sans état dans mon service Web JAX-RS via des annotations. Malheureusement, l'EJB est simplement null et j'obtiens un NullPointerException lorsque j'essaie de l'utiliser.

@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);
    }
}

Qu'est-ce que je fais mal?

Voici quelques informations sur ma machine:

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

Pouvez-vous montrer un exemple de travail?

70
Zeck

Je ne suis pas sûr que cela soit censé fonctionner. Donc soit:

Option 1: utiliser le fournisseur d'injection SPI

Implémentez un fournisseur qui fera la recherche et injectera l'EJB. Voir:

Exemple pour 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: faire de BookResource un EJB

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

    @EJB
    private BookEJB bookEJB;

    //...
}

Voir:

Option 3: utilisez CDI

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

    @Inject
    private BookEJB bookEJB;

    //...
}

Voir:

111
Pascal Thivent

Ce fil est assez ancien, néanmoins j'ai combattu le même problème hier. Voici ma solution:

Faites simplement de BookResource un bean géré via @ javax.annotation.ManagedBean au niveau de la classe.

Pour que cela fonctionne, vous devez activer CDI avec un 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>

Ce fichier doit être dans WEB-INF si le BookResource fait partie d'un fichier war. Si le BookResource est emballé avec les ejbs, mettez-le dans META-INF.

Si vous souhaitez utiliser @EJB, vous avez terminé. Si vous souhaitez injecter l'EJB via @Inject, un beans.xml doit également être placé dans le fichier jar ejbs dans META-INF.

Ce que vous faites: vous dites simplement au conteneur que la ressource doit être gérée par le conteneur. À cet effet, il prend en charge l'injection ainsi que les événements du cycle de vie. Vous avez donc votre façade commerciale sans la promouvoir en EJB.

Vous n'avez pas besoin d'étendre javax.ws.rs.core.Application pour que cela fonctionne. BookResource est une ressource racine dont la portée est automatiquement demandée.

Testé avec Glassfish 3.1.2 et un projet maven.

Codage heureux.

14
Michael Simons

Vous pourrez faire l'injection dans la ressource JAX-RS sans en faire un composant EJB ou CDI. Mais vous devez vous rappeler que votre ressource JAX-RS ne doit pas être singleton.

Donc, vous configurez votre application avec ce code. Cela fait BookResource classe par demande ressource JAX-RS.

@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;
  }
}

Avec cette configuration, vous laissez JAX-RS instancier BookResource pour vous sur une base par demande et injectez également toutes les dépendances requises. Si vous créez BookResource class singleton ressource JAX-RS, c'est-à-dire que vous mettez getSingletons

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

ensuite, vous avez créé une instance qui n'est pas gérée par le runtime JAX-RS et personne dans le conteneur ne se soucie d'injecter quoi que ce soit.

10
Martin

Malheureusement, ma réponse est trop longue pour un commentaire, alors voici. :)

Zeck, j'espère que vous savez ce que vous faites exactement en promouvant votre bean en EJB, comme l'a suggéré Pascal. Malheureusement, aussi simple qu'il soit de nos jours avec Java EE pour 'faire une classe un EJB', vous devez être conscient des implications de le faire. Chaque EJB crée des frais généraux avec les fonctionnalités supplémentaires qu'il fournit: ils sont conscients des transactions, ont leurs propres contextes, ils participent au cycle de vie complet de l'EJB, etc.

Ce que je pense que vous devriez faire pour une approche propre et réutilisable est la suivante: extraire l'accès à vos services serveurs (qui, espérons-le, sont accessibles via un SessionFacade :) dans un BusinessDelegate . Ce délégué doit utiliser une sorte de recherche JNDI (probablement un ServiceLocator - oui, ils sont toujours valides dans Java EE!) Pour accéder à votre backend.

D'accord, si vous avez vraiment, vraiment, vraiment besoin de l'injection parce que vous ne voulez pas écrire manuellement l'accès JNDI, vous pouvez toujours faire votre déléguer un EJB, même si ... eh bien, ça fait juste du mal. :) De cette façon, au moins, il sera facile de le remplacer plus tard par autre chose si vous décidez de passer à une approche de recherche JNDI ...

5
LeChe

J'essayais de faire exactement la même chose. J'utilise EJB 3.1 et ai déployé mon application en tant qu'EAR avec un projet EJB distinct. Comme Jav_Rock l'a souligné, j'utilise la recherche de contexte.

@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);
  }
}

Voir le lien ci-dessous pour des conseils de recherche JNDI très utiles

Conseils de recherche JNDI

3
duvo

Arjan a raison. J'ai créé une autre classe pour initialiser l'EJB au lieu de créer un bean pour RS

@Singleton
@LocalBean
public class Mediator {
    @EJB
    DatabaseInterface databaseFacade;

pour éviter le pointeur nul avec:

@Path("stock")
public class StockResource {
    @EJB
    DatabaseInterface databaseFacade;
...

cela fonctionne réellement sur GF

1
Lukasz Ronikier

J'ai le même problème, et je l'ai résolu en appelant te EJB par une recherche de contexte (l'injection était impossible, j'ai eu la même erreur NullPointerException).

0
NIkoas