web-dev-qa-db-fra.com

Utilisation de @Context, @Provider et ContextResolver dans JAX-RS

Je viens juste de me familiariser avec la mise en œuvre de REST services Web en Java à l'aide de JAX-RS et j'ai rencontré le problème suivant. L'une de mes classes de ressources nécessite un accès à un backend de stockage, qui est extrait d'une interface StorageEngine. Je voudrais injecter l'instance actuelle StorageEngine à la classe de ressources servant les demandes REST et j'ai pensé qu'une bonne façon de procéder consiste à utiliser l'annotation @Context et une classe ContextResolver appropriée. Voici ce que j'ai jusqu'à présent:

Dans MyResource.Java:

class MyResource {
    @Context StorageEngine storage;
    [...]
}

Dans StorageEngineProvider.Java:

@Provider
class StorageEngineProvider implements ContextResolver<StorageEngine> {
    private StorageEngine storage = new InMemoryStorageEngine();

    public StorageEngine getContext(Class<?> type) {
        if (type.equals(StorageEngine.class))
            return storage;
        return null;
    }
}

J'utilise com.Sun.jersey.api.core.PackagesResourceConfig pour découvrir les fournisseurs et les classes de ressources automatiquement, et selon les journaux, il récupère bien la classe StorageEngineProvider (horodatages et éléments inutiles laissés intentionnellement de côté):

INFO: Root resource classes found:
    class MyResource
INFO: Provider classes found:
    class StorageEngineProvider

Cependant, la valeur de storage dans ma classe de ressources est toujours null - ni le constructeur de StorageEngineProvider ni sa méthode getContext ne sont jamais appelés par Jersey. Qu'est-ce que je fais mal ici?

27
Tamás

Je ne pense pas qu'il existe un moyen spécifique à JAX-RS de faire ce que vous voulez. Le plus proche serait de faire:

@Path("/something/")
class MyResource {
    @Context
    javax.ws.rs.ext.Providers providers;

    @GET
    public Response get() {
        ContextResolver<StorageEngine> resolver = providers.getContextResolver(StorageEngine.class, MediaType.WILDCARD_TYPE);
        StorageEngine engine = resolver.get(StorageEngine.class);
        ...
    }
}

Cependant, je pense que les annotations @ javax.ws.rs.core.Context et javax.ws.rs.ext.ContextResolver sont réellement destinées aux types liés à JAX-RS et prenant en charge les fournisseurs JAX-RS.

Vous voudrez peut-être rechercher les implémentations JSR-299 (qui devraient être disponibles dans Java EE 6) ou d'autres infrastructures d'injection de dépendances telles que Google Guice pour vous aider ici.

19
Bryant Luk

Implémentez un InjectableProvider . Très probablement en étendant PerRequestTypeInjectableProvider ou SingletonTypeInjectableProvider.

@Provider
public class StorageEngineResolver extends SingletonTypeInjectableProvider<Context, StorageEngine>{
    public MyContextResolver() {
        super(StorageEngine.class, new InMemoryStorageEngine());
    }
}

Vous laisserait avoir:

@Context StorageEngine storage;
12
Chase

J'ai trouvé un autre moyen. Dans mon cas, je souhaite fournir à l'utilisateur actuellement connecté en tant qu'entité utilisateur de ma couche de persitence . C'est la classe:

@RequestScoped
@Provider
public class CurrentUserProducer implements Serializable, ContextResolver<User> {

    /**
     * Default
     */
    private static final long serialVersionUID = 1L;


    @Context
    private SecurityContext secContext;

    @Inject
    private UserUtil userUtil;

    /**
     * Tries to find logged in user in user db (by name) and returns it. If not
     * found a new user with role {@link UserRole#USER} is created.
     * 
     * @return found user or a new user with role user
     */
    @Produces
    @CurrentUser
    public User getCurrentUser() {
        if (secContext == null) {
            throw new IllegalStateException("Can't inject security context - security context is null.");
        }
        return userUtil.getCreateUser(secContext.getUserPrincipal().getName(),
                                      secContext.isUserInRole(UserRole.ADMIN.name()));
    }

    @Override
    public User getContext(Class<?> type) {
        if (type.equals(User.class)) {
            return getCurrentUser();
        }
        return null;
    }

}

J'ai seulement utilisé implements ContextResolver<User> et @Provider pour que cette classe soit découverte par Jax-Rs et que SecurityContext soit injecté .Pour obtenir l'utilisateur actuel, j'utilise CDI avec mon qualificateur @CurrentUser. Donc, à chaque endroit où j'ai besoin de l'utilisateur actuel, je tape:

@Inject
@CurrentUser
private User user;

Et en effet 

@Context
private User user;

ne fonctionne pas (l'utilisateur est null).

1
dermoritz

Un modèle qui fonctionne pour moi: Ajoutez quelques champs dans votre sous-classe Application qui fournissent les objets que vous devez injecter. Ensuite, utilisez une classe de base abstraite pour effectuer "l'injection":

public abstract class ServiceBase {

    protected Database database;

    @Context
    public void setApplication(Application app) {
        YourApplication application = (YourApplication) app;
        database = application.getDatabase();
    }
}

Tous vos services qui doivent accéder à la base de données peuvent maintenant étendre ServiceBase et avoir la base de données automatiquement disponible via le champ protégé (ou un getter, si vous préférez).

Cela fonctionne pour moi avec Undertow et Resteasy. En théorie, cela devrait fonctionner avec toutes les implémentations de JAX-RS car l'injection de l'application est prise en charge par AFAICS standard, mais je ne l'ai pas testée dans d'autres contextes.

Pour moi, l'avantage par rapport à la solution de Bryant était que je n'avais pas besoin d'écrire une classe de résolveur uniquement pour pouvoir accéder à mes singletons d'application tels que la base de données.

0
Fabian Streitel