Uso de @ Context, @Provider y ContextResolver en JAX-RS


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 abstrae detrás de una interfaz StorageEngine. Me gustaría inyectar la instancia StorageEngine actual en la clase resource que sirve las solicitudes REST y pensé que una buena manera de hacerlo sería usando la anotación @Context y una clase ContextResolver apropiada. Esto es lo que tengo far:

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 omitidas intencionalmente):

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

Sin embargo, el valor de storage en mi clase de recurso es siempre null - ni el constructor de StorageEngineProvider ni su método getContext es llamado por Jersey, nunca. ¿Qué estoy haciendo mal aquí?

Author: Tamás, 2010-06-15

4 answers

No creo que haya una forma específica de JAX-RS de 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 que soportan proveedores JAX-RS.

Es posible que desee buscar implementaciones de Java Context and Dependency Injection (JSR-299) (que deberían estar disponibles en Java EE 6) u otros marcos de inyección de dependencias como Google Guice para ayudarlo aqui.

 19
Author: Bryant Luk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-20 01:39:12

Implementar un proveedor de inyección . Muy probablemente extendiendo 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;
 12
Author: Chase,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-19 07:04:34

Encontré otra manera. En mi caso, quiero proporcionar el usuario actualmente conectado como una entidad de usuario desde mi capa de perseverancia. 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é implements ContextResolver<User> y @Provider para obtener esta clase descubierta por Jax-Rs y obtener SecurityContext inyectada. Para obtener el usuario actual utilizo 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).

 1
Author: dermoritz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-05 09:13:17

Un patrón que funciona para mí: 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 sus 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 getter, si lo prefiere).

Esto funciona para mí con Resaca y Resteasy. En teoría esto debería funcionar en todos los JAX-RS implementaciones desde la inyección de la Aplicación es compatible con el estándar AFAICS, pero no lo he probado en otros ajustes.

Para mí, la ventaja sobre la solución de Bryant fue que no tengo que escribir alguna clase de resolución solo para poder acceder a mis singletons de ámbito de aplicación como la base de datos.

 0
Author: Fabian Streitel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-25 20:29:45