wake-up-neo.com

Verwendung von @Context, @Provider und ContextResolver in JAX-RS

Ich habe mich gerade mit der Implementierung von REST - Webdiensten in Java unter Verwendung von JAX-RS vertraut gemacht und bin auf das folgende Problem gestoßen. Eine meiner Ressourcenklassen erfordert Zugriff auf ein Speicher-Backend, das hinter einer StorageEngine-Schnittstelle abstrahiert ist. Ich möchte die aktuelle StorageEngine-Instanz in die Ressourcenklasse einfügen, die die REST -Anforderungen bedient, und ich dachte, eine gute Möglichkeit wäre die Verwendung der Annotation @Context und einer entsprechenden ContextResolver-Klasse. Das habe ich bisher:

In MyResource.Java:

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

In 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;
    }
}

Ich benutze com.Sun.jersey.api.core.PackagesResourceConfig, um die Anbieter und die Ressourcenklassen automatisch zu erkennen, und entsprechend den Protokollen nimmt es die Klasse StorageEngineProvider gut auf (Zeitstempel und unnötiges Material, das absichtlich weggelassen wurde):

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

Der Wert von storage in meiner Ressourcenklasse ist jedoch immer null - weder der Konstruktor von StorageEngineProvider noch seine getContext -Methode wird jemals von Jersey aufgerufen. Was mache ich hier falsch?

27
Tamás

Ich glaube nicht, dass es einen JAX-RS-spezifischen Weg gibt, um das zu tun, was Sie wollen. Am nächsten wäre es zu tun:

@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);
        ...
    }
}

Ich denke jedoch, dass die Annotation @ javax.ws.rs.core.Context und javax.ws.rs.ext.ContextResolver wirklich für Typen gedacht ist, die mit JAX-RS in Verbindung stehen und JAX-RS-Anbieter unterstützen.

Sie können nach Implementierungen für Java-Kontext und Abhängigkeitsinjektion (JSR-299) (die in Java EE 6 verfügbar sein sollten) oder anderen Abhängigkeiten für die Abhängigkeitsinjektion wie Google Guice suchen, um Ihnen hier zu helfen.

19
Bryant Luk

Implementieren Sie einen InjectableProvider . Wahrscheinlich durch Erweiterung von PerRequestTypeInjectableProvider oder SingletonTypeInjectableProvider.

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

Lass dich haben:

@Context StorageEngine storage;
12
Chase

Ich habe einen anderen Weg gefunden. In meinem Fall möchte ich den Benutzer angeben, der derzeit als Benutzerentität aus meiner Persistenzschicht angemeldet ist .. Dies ist die Klasse:

@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;
    }

}

Ich habe nur implements ContextResolver<User> und @Provider verwendet, um diese Klasse von Jax-Rs entdecken zu lassen und SecurityContext injed ..__ zu erhalten. Um den aktuellen Benutzer zu erhalten, verwende ich CDI mit meinem Qualifier @CurrentUser. An jedem Ort, an dem ich den aktuellen Benutzer brauche, tippe ich:

@Inject
@CurrentUser
private User user;

Und in der Tat 

@Context
private User user;

funktioniert nicht (Benutzer ist null).

1
dermoritz

Ein Muster, das für mich funktioniert: Fügen Sie Ihrer Anwendungsunterklasse einige Felder hinzu, die die Objekte enthalten, die Sie einfügen müssen. Dann verwenden Sie eine abstrakte Basisklasse für die "Injektion":

public abstract class ServiceBase {

    protected Database database;

    @Context
    public void setApplication(Application app) {
        YourApplication application = (YourApplication) app;
        database = application.getDatabase();
    }
}

Alle Ihre Dienste, die auf die Datenbank zugreifen müssen, können jetzt ServiceBase erweitern und die Datenbank automatisch über das geschützte Feld (oder einen Getter, falls Sie dies wünschen) zur Verfügung stellen.

Dies funktioniert für mich mit Undertow und Resteasy. Theoretisch sollte dies sollte für alle JAX-RS-Implementierungen funktionieren, da die Injektion der Anwendung vom Standard-AFAICS unterstützt wird, aber ich habe es nicht in anderen Einstellungen getestet.

Der Vorteil gegenüber Bryants Lösung bestand für mich darin, dass ich keine Resolver-Klasse schreiben muss, nur um an meine anwendungsspezifischen Singletons wie die Datenbank heranzukommen.

0
Fabian Streitel

Wenn jemand Resteasy benutzt, hat das bei mir funktioniert.

Wenn Sie so etwas hinzufügen:

ResteasyContext.pushContext(StorageEngine.class, new StorageEngine());

in so etwas wie einem jaxrs-Filter können Sie Folgendes tun:

@GET
@Path("/some/path")
public Response someMethod(@Context StorageEngine myStorageEngine) {
 ...
}

Dies ist spezifisch für Resteasy, das nicht so etwas wie SingletonTypeInjectableProvider hat.

0
jsolum