jax - web service rest java eclipse
Uso de @Context, @Provider y ContextResolver en JAX-RS (4)
Me estoy familiarizando con la implementación de servicios web REST en Java utilizando JAX-RS y me encontré con el siguiente problema. Una de mis clases de recursos requiere acceso a un backend de almacenamiento, que se StorageEngine
detrás de una interfaz StorageEngine
. Me gustaría inyectar la instancia actual de StorageEngine
en la clase de recurso que sirve las solicitudes REST y pensé que una buena forma de hacerlo sería mediante el uso de la anotación @Context
y una clase apropiada de ContextResolver
. Esto es lo que tengo hasta ahora:
En MyResource.java
:
class MyResource {
@Context StorageEngine storage;
[...]
}
En 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;
}
}
Estoy usando com.sun.jersey.api.core.PackagesResourceConfig
para descubrir los proveedores y las clases de recursos automáticamente, y de acuerdo con los registros, recoge la clase StorageEngineProvider
muy bien (marcas de tiempo y cosas innecesarias que se StorageEngineProvider
intencionalmente):
INFO: Root resource classes found:
class MyResource
INFO: Provider classes found:
class StorageEngineProvider
Sin embargo, el valor del storage
en mi clase de recurso siempre es null
; StorageEngineProvider
ni Jersey, llama al constructor de StorageEngineProvider
ni a su método getContext
. ¿Qué estoy haciendo mal aquí?
Encontré otra manera. En mi caso, quiero proporcionar al usuario que ha iniciado sesión actualmente como una entidad de Usuario desde mi capa de persitencia. Esta es la clase:
@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;
}
}
Solo usé los implements ContextResolver<User>
y @Provider
para obtener esta clase descubierta por Jax-Rs e inyectar SecurityContext
. Para obtener el usuario actual, uso CDI con mi Calificador @CurrentUser
. Así que en cada lugar donde necesito el usuario actual escribo:
@Inject
@CurrentUser
private User user;
Y de hecho
@Context
private User user;
no funciona (el usuario es nulo).
Implementar un InjectableProvider . Lo más probable es que se extienda PerRequestTypeInjectableProvider o SingletonTypeInjectableProvider.
@Provider
public class StorageEngineResolver extends SingletonTypeInjectableProvider<Context, StorageEngine>{
public MyContextResolver() {
super(StorageEngine.class, new InMemoryStorageEngine());
}
}
Te dejaría tener:
@Context StorageEngine storage;
No creo que haya una forma específica de JAX-RS para hacer lo que quieres. Lo más cercano sería hacer:
@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);
...
}
}
Sin embargo, creo que la anotación @ javax.ws.rs.core.Context y javax.ws.rs.ext.ContextResolver son realmente para tipos relacionados con JAX-RS y proveedores de JAX-RS compatibles.
Puede buscar implementaciones de Contexto de Java y de Inyección de Dependencias (JSR-299) (que deberían estar disponibles en Java EE 6) u otros marcos de inyección de dependencias como Google Guice para que lo ayuden aquí.
Un patrón que me funciona: agregue algunos campos en su subclase de aplicación que proporcionen los objetos que necesita inyectar. Luego use una clase base abstracta para hacer la "inyección":
public abstract class ServiceBase {
protected Database database;
@Context
public void setApplication(Application app) {
YourApplication application = (YourApplication) app;
database = application.getDatabase();
}
}
Todos los servicios que necesitan acceder a la base de datos ahora pueden extender ServiceBase y tener la base de datos disponible automáticamente a través del campo protegido (o un receptor, si así lo prefiere).
Esto me funciona con Undertow y Resteasy. En teoría, esto debería funcionar en todas las implementaciones de JAX-RS ya que la inyección de la aplicación es compatible con el estándar AFAICS, pero no la he probado en otras configuraciones.
Para mí, la ventaja sobre la solución de Bryant era que no tengo que escribir algunas clases de resolución solo para poder acceder a mis singletons de ámbito de aplicación como la base de datos.