Utiliser @Context, @Provider et ContextResolver dans JAX-RS

je me familiarise avec l'implémentation des services REST web en Java en utilisant JAX-RS et j'ai rencontré le problème suivant. L'une de mes classes de ressources nécessite l'accès à un backend de stockage, qui disparaît derrière un StorageEngine interface. Je tiens à injecter le courant StorageEngine instance dans la classe ressource servant les autres requêtes et j'ai pensé qu'une bonne façon de le faire serait d'utiliser le @Context annotation et d'un ContextResolver classe. C'est ce que j'ai donc loin:

MyResource.java:

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

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 ressources des classes automatiquement, et selon les journaux, il reprend la StorageEngineProvider classe bien (horodateurs et les trucs inutiles de gauche intentionnellement):

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

cependant, la valeur de storage dans ma classe de ressource est toujours null - ni le constructeur de StorageEngineProvider ni ses getContext méthode est appelée par Jersey, jamais. Ce que je fais mal?

26
demandé sur Tamás 2010-06-15 21:11:10

4 réponses

Je ne pense pas qu'il y ait une façon spécifique JAX-RS de faire ce que vous voulez. Le plus proche serait à 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 le @ javax.ws.rs.noyau.Annotation de contexte et javax.ws.rs.ext.ContextResolver est vraiment pour les types liés à JAX-RS et les fournisseurs de soutien JAX-RS.

Vous pouvez rechercher des implémentations Java Context et Dependency Injection (JSR-299) (qui devraient être disponibles en Java EE 6) ou d'autres cadres d'injection de dépendances tels que Google Guice pour vous aider ici.

19
répondu Bryant Luk 2010-07-20 01:39:12

implémenter un InjectableProvider. Très probablement en étendant Perrequesttypeinjectable Provider ou Singletontypeinjectable Provider.

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

permet d'avoir:

@Context StorageEngine storage;
12
répondu Chase 2013-03-19 07:04:34

j'ai trouvé une autre façon. Dans mon cas, je veux fournir l'utilisateur actuellement connecté en tant qu'entité utilisateur à partir de ma couche 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;
    }

}

je n'ai utilisé implements ContextResolver<User> et @Provider pour obtenir cette classe découverte par Jax-Rs et get SecurityContext injecté. Pour obtenir l'utilisateur courant J'utilise CDI avec mon qualificatif @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 NULL.)

1
répondu dermoritz 2016-02-05 09:13:17

un modèle qui fonctionne pour moi: ajouter des Champs sur votre sous-classe D'Application qui fournissent les objets que vous devez injecter. Ensuite, utilisez une classe de base abstraite pour faire 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 ont besoin d'accéder à la base de données peuvent maintenant étendre ServiceBase et avoir la base de données disponible automatiquement via le champ protégé (ou un getter, si vous préférez cela).

cela fonctionne pour moi avec le contre-courant et le Resteasy. En théorie, ce n' devrait travail dans toutes les implémentations JAX-RS depuis l'injection de L'Application est pris en charge par les AFAICS standard, mais je ne l'ai pas testé dans d'autres paramètres.

pour moi, l'avantage par rapport à la solution de Bryant est que je n'ai pas besoin d'écrire une classe de résolveur juste pour que je puisse obtenir mes Singleton d'application scoped comme la base de données.

0
répondu Fabian Streitel 2016-11-25 20:29:45