Jakarta RESTful Web Services (JAX-RS)

Université de Toulon

LIS UMR CNRS 7020

2025-02-25

Informations techniques

Git Repository Status

Category Details
🌿 Current Branch develop
📝 Latest Commit b798c84 (2024-10-03 15:16:24)
🔗 Remote git@github.com:ebpro/notebook-java-restfulws.git
đŸ·ïž Latest Tag No tags

 

☕ Java Development Environment

Component Details
☕ Java Runtime 21.0.6 (openjdk)
🎯 Maven 3.9.9

Une application REST minimale

Pour crĂ©er des applications REST en Java, il faut utiliser une implantation de Jakarta RESTful Web Services. Par exemple, il est possible d’utiliser le framework Jersey qui est l’implantation de rĂ©fĂ©rence de la spĂ©cification JAX-RS. Dans un premier temps nous allons Ă©tudier une application minimale qui s’appuie sur Jersey intĂ©grĂ© dans un serveur Web en Java Grizzly.

Dans la partie pratique, nous utiliserons une autre approche pour créer des applications REST en Java en utilisant le framework Quarkus.

L’archetype maven suivant permet de crĂ©er un projet de base dans le rĂ©pertoire /home/jovyan/work/src/samples/jaxrs/myresource.

%%shell
mkdir -p /home/jovyan/work/src/samples/jaxrs
cd /home/jovyan/work/src/samples/jaxrs
rm -rf /home/jovyan/work/src/samples/jaxrs/myresource

mvn archetype:generate --batch-mode --no-transfer-progress --quiet \
  -DarchetypeGroupId=org.glassfish.jersey.archetypes \
  -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
  -DarchetypeVersion=3.1.10 \
  -DgroupId=fr.univtln.bruno.demos.jaxrs \
  -DartifactId=myresource

Le serveur peut ĂȘtre compilĂ© puis exĂ©cutĂ©

cd /home/jovyan/work/src/samples/jaxrs/myresource
mvn package && mvn exec:java

Il est maintenant possible d’accĂ©der Ă  la ressource en ligne de commande Ă  partir de l’adresse http://localhost:8080/myapp.

%%shell
cd /home/jovyan/work/src/samples/jaxrs/myresource
mvn --quiet --ntp package
nohup mvn --quiet --ntp exec:java &    

Une application REST JAX-RS est construite autour de deux notions principales l’Application (le serveur) et les Ressources. Une instance d’une ressource est créée pour rĂ©pondre Ă  chaque requĂȘte et dĂ©truite ensuite. Elle peut donc ĂȘtre utilisĂ©e comme une sorte de singleton. Une ressource est dĂ©finie annotant une classe, un ou plusieurs de ses superclasses (y compris abstraites) ou l’une de ses interfaces.

Dans l’exemple, la classe fr.univtln.bruno.demos.jaxrs.Main dĂ©marre le serveur et paramĂštre les packages oĂč le framework va chercher des ressources comme le montre la mĂ©thode ci-dessous.

/**
 * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
 * @return Grizzly HTTP server.
 */
public static HttpServer startServer() {
    // create a resource config that scans for JAX-RS resources and providers
    // in fr.univtln.bruno.demos.jaxrs package
    final ResourceConfig rc = new ResourceConfig().packages("fr.univtln.bruno.demos.jaxrs");
    // create and start a new instance of grizzly http server
    // exposing the Jersey application at BASE_URI
    return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
}

La classe fr.univtln.bruno.demos.jaxrs.MyResource prĂ©sente le fonctionnement minimal d’une ressource. La classe est annotĂ©e avec @Path(...) pour indiquer le chemin Ă  ajouter Ă  l’URL correspondant Ă  cette ressource. D’une façon gĂ©nĂ©rale, les mĂ©thodes sont annotĂ©es avec @POST, @GET, @PUT,@DELETE, 
 pour indiquer le type de verbe HTTP associĂ©.

Les mĂ©thodes peuvent ĂȘtre annotĂ©es avec @Produces qui indique le ou les types MIME dans lequel le rĂ©sultat peut ĂȘtre fourni : @Produces("text/plain"), @Produces("application/json"), 
 Il est possible d’indiquer plusieurs types avec @Produces({"application/json", "application/xml"}). Il existe aussi des constantes Ă©quivalentes MediaType.TEXT_PLAIN. Une valeur par dĂ©faut de @Produces peut ĂȘtre indiquĂ©e en annotant la classe.

/**
 * Method handling HTTP GET requests. The returned object will be sent
 * to the client as "text/plain" media type.
 *
 * @return String that will be returned as a text/plain response.
 */
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getIt() {
    return "Got it!";
}

Le client peut indiquer le type demandĂ© parmi l’un des “produces” avec la valeur Content-Type: de l’entĂȘte de la requĂȘte.

La commande suivante exĂ©cute une requĂȘte GET sur l’URL d’une ressource et affiche le rĂ©sultat en-tĂȘte compris. (Le serveur doit ĂȘtre lancĂ©).

Une application REST plus complĂšte

Pour la suite nous allons Ă©tudier en dĂ©tail l’application https://github.com/ebpro/sample-jaxrs.

Jersey app started with endpoints available at http://localhost:8080/ Hit Ctrl-C to stop it


Les exemples suivants sont accessibles dans le dépÎt :

  • Source: ebpro/notebook-java-rest-sample-jakartarestfull
  • Branch: develop
  • Latest Commit: dbac38c (fix maven wrapper exec bit, 2025-02-24)
  • Cloned to: ${SRC_DIR}=/home/jovyan/work/materials/github/ebpro/notebook-java-rest-sample-jakartarestfull

To get it:

git clone -b develop https://github.com/ebpro/notebook-java-rest-sample-jakartarestfull

L’application peut ĂȘtre compilĂ©e et exĂ©cutĂ©e avec maven. Cela lance le serveur (vous pourrez l’arrĂȘter avec ctrl-c dans le terminal). Cela compile, exĂ©cute les tests unitaires, package et exĂ©cute les tests d’intĂ©gration (en lançant le serveur REST et en exĂ©cutant de vraies requĂȘtes).

cd /home/jovyan/work/materials/github/ebpro/notebook-java-rest-sample-jakartarestfull && \
    mvn clean verify &&
    mvn exec:java

La classe fr.univtln.bruno.samples.jaxrs.server.BiblioServer paramĂštre Jersey, dĂ©marre Grizzly et attend un CTRL-C pour arrĂȘter le serveur.

La classe fr.univtln.bruno.samples.jaxrs.model.LibraryModel définit le modÚle de donnée (Une bibliothÚque qui est une facade pour gérer des Auteurs et des Livres.)

Les classes fr.univtln.bruno.samples.jaxrs.resources.LibraryResource et fr.univtln.bruno.samples.jaxrs.resources.AuthorResource définissent des ressources REST.

Chemins et Verbes

La mĂ©thode sayHello() reprend l’exemple prĂ©cĂ©dent.

/**
 * The simpliest method that just return "hello" in plain text with GET on the default path "biblio".
 *
 * @return the string
 */
@SuppressWarnings("SameReturnValue")
@GET
@Path("hello")
@Produces(MediaType.TEXT_PLAIN)
public String sayHello() {
    return "hello";
}

D’autres verbe peuvent ĂȘtre utilisĂ© sur le mĂȘme chemin. Une mĂ©thode peut aussi ĂȘtre annotĂ©es avec @Path pour dĂ©finir le chemin associĂ© Ă  cette mĂ©thode.

La méthode init() est un simple PUT sans paramÚtre qui initialise la bibliothÚque avec deux auteurs.

/**
 * An init method that add two authors with a PUT on the default path.
 *
 * @return the number of generated authors.
 * @throws IllegalArgumentException the illegal argument exception
 */
@PUT
@Path("init")
public int init() throws BusinessException {
    Library.demoLibrary.removesAuthors();
    Library.Author author1 = Library.demoLibrary.addAuthor(Library.Author.builder().firstname("Alfred").name("Martin").build());
    Library.Author author2 = Library.demoLibrary.addAuthor(Library.Author.builder().firstname("Marie").name("Durand").build());
    Library.demoLibrary.addBook(Library.Book.builder().title("title1").authors(Set.of(author1)).build());
    Library.demoLibrary.addBook(Library.Book.builder().title("title2").authors(Set.of(author1, author2)).build());
    Library.demoLibrary.addBook(Library.Book.builder().title("title3").authors(Set.of(author2)).build());
    Library.demoLibrary.addBook(Library.Book.builder().title("title4").authors(Set.of(author2)).build());
    return Library.demoLibrary.getAuthorsNumber();
}

Les paramĂštres simples

JAX-RS permet d’extraire automatiquement des valeurs de paramĂštres depuis le chemin de la ressources, les paramĂȘtres de la requĂȘte ou l’entĂȘte http. Ces valeurs peuvent alors ĂȘtre “injectĂ©es” (affectĂ©e par annotation aux paramĂštres des mĂ©thodes REST).

L’annotation @PathParam permet d’injecter les valeurs provenant des URL comme des paramùtres.

La mĂ©thode getAuthor(@PathParam("id") final long id) ci dessous-s’exĂ©cute lors d’un GET sur un chemin de forme @Path("author/{id}"). id est est un pas de chemin quelconque qui sera extrait, converti en long et injectĂ© grĂące Ă  @PathParam dans le paramĂštre id de la fonction. Il est possible d’indiquer une expression rĂ©guliĂšre pour contraindre la forme du pas par exemple @Path("authors/{id: [0-9]+}").

Le @Produces sur la classe indique que du XML ou du JSON peuvent ĂȘtre produits. Les mĂ©thodes REST retournent instance de la classe Response qui reprĂ©sente une rĂ©ponse HTTP. Cette classe propose un builder pour construire manuellement. Cependant, JAX-RS permet de construite automatiquement ces rĂ©ponses si le type de retour peut ĂȘtre transformĂ© en un contenu de rĂ©ponse (une entitĂ© http) par une implantation de l’interface MessageBodyWriter. Les implantations de JAX-RS en fournissent gĂ©nĂ©ralement par dĂ©faut par exemple pour String voire pour XML ou JSON (via les mĂ©canismes de marshalling qui seront Ă©tudiĂ©s en dĂ©tail plus tard).

/**
 * Find and return an author by id with a GET on the path "biblio/auteurs/{id}" where  {id} is the needed id.
 * The path parameter "id" is injected with @PathParam.
 *
 * @param id the needed author id.
 * @return the auteur with id.
 * @throws NotFoundException is returned if no author has the "id".
 */
@GET
@Path("{id}")
public Library.Author getAuthor(@PathParam("id") final long id) throws BusinessException {
    return Library.demoLibrary.getAuthor(id);
}

Get author 1 in JSON :

La requĂȘte suivante reprend la prĂ©cĂ©dente et demande du XML. JAX-RS va chercher automatiquement des classes (MessageBodyWriter et Reader) pour crĂ©er le bon format. Ces classes peuvent construites explicitement mais des extensions peuvent ĂȘtre ajoutĂ©es pour produire les types classiques par annotations des entitĂ©s (cf. le pom.xml et les annotations de la classe BiblioModel.Auteur) : jersey-media-jaxb pour XML et jersey-media-json-jackson pour JSON. Jackson n’est pas l’implantatation pas dĂ©faut mais elle est plus efficace et plus configurable.

Get author 2 in XML :

Les collections classiques sont supportĂ©s. Notez qu’ici les collections eclipse sont utilisĂ©es en particulier celles pour les primitifs et qu’elles sont supportĂ©es par Jackson.

Get authors in XML :

/**
 * Gets auteurs.
 *
 * @return the auteurs
 */
@GET
public Collection<Library.Author> getAuthors() {
    return Library.demoLibrary.getAuthors().values();
}

Get authors in JSON

D’une façon similaire les annotations @HeaderParam et @QueryParam permettent d’extraire des valeurs de l’entĂȘte ou des paramĂštres de la requĂȘte http. La mĂ©thode suivante permet de construire un filtre pour des requĂȘtes complexe. L’utilisation d’un chemin diffĂ©rent (“filter”) n’est utile que pour l’exemple dans une application rĂ©elle il n’y aura qu’un seul GET.

/**
 * Gets a list of "filtered" authors.
 *
 * @param name        an optional exact filter on the name.
 * @param firstname     an optional exact filter on the firstname.
 * @param biography an optional contains filter on the biography.
 * @param sortKey    the sort key (prenom or nom).
 * @return the filtered auteurs
 */
@GET
@Path("filter")
public Page<Library.Author> getFilteredAuthors(@QueryParam("name") String name, @QueryParam("firstname") String firstname, @QueryParam("biography") String biography, @HeaderParam("sortKey") @DefaultValue("name") String sortKey) {
    PaginationInfo paginationInfo = PaginationInfo.builder().name(name).firstname(firstname).biography(biography).sortKey(sortKey).build();
    return Library.demoLibrary.getAuthorsWithFilter(paginationInfo);
}

Pour simplifier le traitement des paramĂštres JAX-RX propose l’annotation @BeanParam qui permet de crĂ©er un instance d’une classe Ă  partir des paramĂštres extraits. Pour cela, les propriĂ©tĂ©s de la classe peuvent ĂȘtre annotĂ©es pour indiquer les paramĂȘtres correspondants.

L’exemple suivant montre comment l’utiliser pour mettre en place la pagination qui est essentielle quand le volume des donnĂ©es peut ĂȘtre important. LĂ  aussi le chemin spĂ©cifique (“page”) n’est lĂ  que pour l’exemple.

@FieldDefaults(level = AccessLevel.PRIVATE)
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PaginationInfo {
    @SuppressWarnings("FieldMayBeFinal")
    @QueryParam("page")
    @Builder.Default
    long page = 1;

    @SuppressWarnings("FieldMayBeFinal")
    @QueryParam("pageSize")
    @Builder.Default
    long pageSize = 10;

    @HeaderParam("sortKey")
    @DefaultValue("name")
    String sortKey;

    @QueryParam("name")
    String name;

    @QueryParam("firstname")
    String firstname;

    @QueryParam("biography")
    String biography;
}
/**
 * Gets a page of authors after applying a sort.
 *
 * @param paginationInfo the pagination info represented as a class injected with @BeanParam.
 * @return the page of authors.
 */
@GET
@Path("page")
public Page<Library.Author> getAuthorsPage(@BeanParam PaginationInfo paginationInfo) {
    return Library.demoLibrary.getAuthorsWithFilter(paginationInfo);
}

L’appel suivant de l’API gĂ©nĂšre alĂ©toirement 100 auteurs.

On peut alors demander la page 3 (de taille 4).

L’appel suivant de l’API remet uniquement deux auteurs dans la base de donnĂ©es.