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