60 votes

Rester au sec avec JAX-RS

J'essaie de minimiser code à répétition pour un certain nombre de JAX-RS gestionnaires de ressources, ce qui nécessite un peu de la même chemin d'accès et les paramètres de la requête. La base du modèle d'url pour chaque ressource ressemble à ceci:

/{id}/resourceName

et chaque ressource dispose de plusieurs sous-ressources:

/{id}/resourceName/subresourceName

Donc, ressource/sous-ressource chemins (incl. les paramètres de la requête) peut ressembler

/12345/foo/bar?xyz=0
/12345/foo/baz?xyz=0
/12345/quux/abc?xyz=0
/12345/quux/def?xyz=0

Les pièces communes aux différentes ressources foo et quux sont @PathParam("id") et @QueryParam("xyz"). J'ai pu mettre en œuvre les ressources des classes comme ceci:

// FooService.java
@Path("/{id}/foo")
public class FooService
{
    @PathParam("id") String id;
    @QueryParam("xyz") String xyz;

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}

// QuuxService.java
@Path("/{id}/quux")
public class QuxxService
{
    @PathParam("id") String id;
    @QueryParam("xyz") String xyz;

    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}

J'ai réussi à éviter de répéter le paramètre d'injection dans chaque get* méthode.1 C'est un bon début, mais je voudrais être en mesure d'éviter la répétition à travers les ressources de classes. Une approche qui fonctionne avec le CDI (dont j'ai aussi besoin d') est d'utiliser un abstract classe de base qui FooService et QuuxService peut extend:

// BaseService.java
public abstract class BaseService
{
    // JAX-RS injected fields
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;

    // CDI injected fields
    @Inject protected SomeUtility util;
}

// FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}

// QuuxService.java
@Path("/{id}/quux")
public class QuxxService extends BaseService
{   
    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}

À l'intérieur de l' get* méthodes, le CDI injection (miraculeusement) fonctionne correctement: l' util champ n'est pas nul. Malheureusement, JAX-RS injection n'a pas de travail; id et xyz sont null dans la get* méthodes FooService et QuuxService.

Est-il un correctif ou une solution de contournement pour ce problème?

Étant donné que le CDI fonctionne comme je le voudrais, je me demandais si le défaut d'injecter @PathParams (etc.) dans les sous-classes est un bug ou juste une partie de JAX-RS spec.


Une autre approche, j'ai déjà essayé à l'aide de BaseService comme un point d'entrée unique que les délégués à l' FooService et QuuxService en tant que de besoin. C'est essentiellement comme décrit dans Reposante Java avec JAX-RS à l'aide de sous-ressource locators.

// BaseService.java
@Path("{id}")
public class BaseService
{
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;
    @Inject protected SomeUtility util;

    public BaseService () {} // default ctor for JAX-RS

    // ctor for manual "injection"
    public BaseService(String id, String xyz, SomeUtility util)
    {
        this.id = id;
        this.xyz = xyz;
        this.util = util;
    }

    @Path("foo")
    public FooService foo()
    {
        return new FooService(id, xyz, util); // manual DI is ugly
    }

    @Path("quux")
    public QuuxService quux()
    {
        return new QuuxService(id, xyz, util); // yep, still ugly
    }
}

// FooService.java
public class FooService extends BaseService
{
    public FooService(String id, String xyz, SomeUtility util)
    {
        super(id, xyz, util); // the manual DI ugliness continues
    }

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}

// QuuxService.java
public class QuuzService extends BaseService
{
    public FooService(String id, String xyz, SomeUtility util)
    {
        super(id, xyz, util); // the manual DI ugliness continues
    }

    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}

L'inconvénient de cette approche est que ni CDI injection, ni de JAX-RS des travaux d'injection dans le sous-ressource classes. La raison pour cela est assez évident2, mais ce que cela signifie , c'est que j'ai manuellement ré-injecter les champs dans la sous-classes " constructeur, qui est sale, moche, et ne se laisse pas facilement permettez-moi de personnaliser davantage l'injection. Exemple: dire que je voulais @Inject d'une instance en FooService mais pas QuuxService. Parce que je suis instancier explicitement les sous-classes de BaseService, CDI injection ne fonctionne pas, de sorte que la laideur est poursuivie.


tl;dr Quel est le bon chemin pour éviter les injections répétées de champs de JAX-RS ressource classes de gestionnaire?

Et pourquoi ne pas hérité des champs injecté par JAX-RS, tandis que la CDI n'a pas de problèmes avec cela?


Edit 1

Avec un peu de sens de @Tarlog, je crois que j'ai trouvé la réponse à une de mes questions,

Pourquoi ne sont pas héritées des champs injecté par JAX-RS?

Dans la JSR-311 §3.6:

Si une sous-classe ou méthode de mise en œuvre a tout de JAX-RS annotations puis tous les annotations sur la super-classe ou la méthode de l'interface sont ignorés.

Je suis sûr qu'il y a une vraie raison de cette décision, mais, malheureusement, le fait est contre moi dans ce cas d'utilisation particulier. Je suis toujours intéressé à toutes les solutions possibles.


1 La mise en garde avec l'aide du champ au niveau de l'injection, c'est que je suis maintenant lié par la demande de ressources de l'instanciation de classe, mais je peux vivre avec ça.
2 Parce que je suis le seul appelant new FooService() plutôt que le contenant/JAX-RS de mise en œuvre.

6voto

Lucky Points 31

Voici une solution de contournement que j'utilise:

Définissez un constructeur pour BaseService avec 'id' et 'xyz' comme paramètres:

 // BaseService.java
public abstract class BaseService
{
    // JAX-RS injected fields
    protected String id;
    protected String xyz;

    public BaseService (String id, String xyz) {
        this.id = id;
        this.xyz = zyx;
    }
}
 

Répétez le constructeur sur toutes les sous-classes avec les injects:

 // FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    public FooService (@PathParam("id") String id, @QueryParam("xyz") String xyz) {
        super(id, xyz);
    }

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}
 

4voto

Gepsens Points 442

La recherche à celui de Jax JIRA il semble que quelqu'un a demandé pour l'annotation de l'héritage comme jalon de JAX-RS.

La fonctionnalité que vous recherchez n'existe tout simplement pas dans JAX-RS encore, cependant, serait-ce de travailler? C'est moche, mais empêche récurrents d'injection.

public abstract class BaseService
{
    // JAX-RS injected fields
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;

    // CDI injected fields
    @Inject protected SomeUtility util;

    @GET @Path("bar")
    public abstract Response getBar();

    @GET @Path("baz")
    public abstract Response getBaz();

    @GET @Path("abc")
    public abstract Response getAbc();

    @GET @Path("def")
    public abstract Response getDef();
}

// FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    public Response getBar() { /* snip */ }

    public Response getBaz() { /* snip */ }
}

// QuuxService.java
@Path("/{id}/quux")
public class QuxxService extends BaseService
{   
    public Response getAbc() { /* snip */ }

    public Response getDef() { /* snip */ }
}

Ou une autre solution de contournement :

public abstract class BaseService
{
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;

    // CDI injected fields
    @Inject protected SomeUtility util;

    @GET @Path("{stg}")
    public abstract Response getStg(@Pathparam("{stg}") String stg);

}

// FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    public Response getStg(String stg) {
        if(stg.equals("bar")) {
              return getBar();
        } else {
            return getBaz();
        }
    }
    public Response getBar() { /* snip */ }

    public Response getBaz() { /* snip */ }
}

Mais, en voyant comment délicate vous, franchement, je doute que votre frustration va aller loin avec ce vilain code :)

3voto

rektide Points 370

Dans RESTEasy, on peut construire une classe, annoter avec @ * Param comme d'habitude et terminer en annotant la classe @Form. Cette classe @Form peut alors être une injection de paramètre dans n'importe quel appel de méthode d'un autre service. http://docs.jboss.org/resteasy/docs/2.3.5.Final/userguide/html/_Form.html

2voto

grzes Points 291

Vous pouvez ajouter un fournisseur personnalisé, notamment via AbstractHttpContextInjectable:

 // FooService.java
@Path("/{id}/foo")
public class FooService
{
    @Context CommonStuff common;

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}


@Provider
public class CommonStuffProvider
    extends AbstractHttpContextInjectable<CommonStuff>
    implements InjectableProvider<Context, Type>
{

    ...

    @Override
    public CommonStuff getValue(HttpContext context)
    {
        CommonStuff c = new CommonStuff();
        c.id = ...initialize from context;
        c.xyz = ...initialize from context;

        return c;
    }
}
 

Cela dit, vous devrez extraire les paramètres de chemin d'accès et / ou les paramètres de requête de manière difficile à partir de HttpContext, mais vous le ferez une fois à la même place.

1voto

Tarlog Points 5447

Quelle est la motivation pour éviter les injections de paramètres?
Si la motivation évite de répéter des chaînes codées en dur, vous pouvez ainsi les renommer facilement et réutiliser des "constantes":

 // FooService.java
@Path("/" +  FooService.ID +"/foo")
public class FooService
{
    public static final String ID = "id";
    public static final String XYZ= "xyz";
    public static final String BAR= "bar";

    @PathParam(ID) String id;
    @QueryParam(XYZ) String xyz;

    @GET @Path(BAR)
    public Response getBar() { /* snip */ }

    @GET @Path(BAR)
    public Response getBaz() { /* snip */ }
}

// QuuxService.java
@Path("/" +  FooService.ID +"/quux")
public class QuxxService
{
    @PathParam(FooService.ID) String id;
    @QueryParam(FooService.XYZ) String xyz;

    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}
 

(Désolé d'avoir posté la deuxième réponse, mais c'était trop long pour la mettre dans un commentaire de la réponse précédente)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X