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?
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.
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;
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.)
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.