Construire une API REST avec Spring et Java Config

Construire une API REST avec Spring et Java Config

1. Vue d'ensemble

Cet article montre commentset up REST in Spring - les codes de réponse du contrôleur et HTTP, la configuration du marshalling de la charge utile et la négociation de contenu.

Lectures complémentaires:

Utilisation de Spring @ResponseStatus pour définir le code d'état HTTP

Examinez l'annotation @ResponseStatus et son utilisation pour définir le code d'état de la réponse.

Read more

Les annotations Spring @Controller et @RestController

Découvrez les différences entre les annotations @Controller et @RestController dans Spring MVC.

Read more

2. Comprendre REST au printemps

La structure Spring prend en charge deux manières de créer des services RESTful:

  • en utilisant MVC avecModelAndView

  • en utilisant des convertisseurs de messages HTTP

L'approcheModelAndView est plus ancienne et bien mieux documentée, mais aussi plus verbeuse et lourde en configuration. Il tente d'incorporer le paradigme REST dans l'ancien modèle, ce qui n'est pas sans problèmes. L’équipe de Spring a compris cela et a fourni une assistance REST de première classe à partir de Spring 3.0.

La configuration deThe new approach, based on HttpMessageConverter and annotations, is much more lightweight and easy to implement. est minimale et fournit des valeurs par défaut raisonnables pour ce que vous attendez d'un service RESTful.

3. La configuration Java

@Configuration
@EnableWebMvc
public class WebConfig{
   //
}

La nouvelle annotation@EnableWebMvc fait des choses utiles - en particulier, dans le cas de REST, elle détecte l'existence de Jackson et JAXB 2 sur le chemin de classe et crée et enregistre automatiquement les convertisseurs JSON et XML par défaut. La fonctionnalité de l'annotation est équivalente à la version XML:

Il s’agit d’un raccourci, et bien qu’il puisse être utile dans de nombreuses situations, il n’est pas parfait. Lorsqu'une configuration plus complexe est nécessaire, supprimez l'annotation et étendez directementWebMvcConfigurationSupport.

3.1. Utilisation de la botte de printemps

Si nous utilisons l'annotation@SpringBootApplication et que la slibraryspring-webmvc est sur le chemin de classe, alors l'annotation@EnableWebMvc est ajoutée automatiquement aveca default autoconfiguration.

Nous pouvons toujours ajouter des fonctionnalités MVC à cette configuration en implémentant l'interfaceWebMvcConfigurer sur une classe sannotée@Configuration . Nous pouvons également utiliser une instanceWebMvcRegistrationsAdapter pour fournir nos propresRequestMappingHandlerMapping,RequestMappingHandlerAdapter ouExceptionHandlerExceptionResolver implementations.

Enfin, si nous voulons abandonner les fonctionnalités MVC de Spring Boot et déclarer une configuration personnalisée, nous pouvons le faire en utilisant l'annotation@EnableWebMvc.

4. Tester le contexte Spring

À partir de Spring 3.1, nous obtenons un support de test de première classe pour les classes@Configuration:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  classes = {WebConfig.class, PersistenceConfig.class},
  loader = AnnotationConfigContextLoader.class)
public class SpringContextIntegrationTest {

   @Test
   public void contextLoads(){
      // When
   }
}

Nous spécifions les classes de configuration Java avec l'annotation@ContextConfiguration. Le nouveauAnnotationConfigContextLoader charge les définitions de bean à partir des classes@Configuration.

Notez que la classe de configurationWebConfig n'a pas été incluse dans le test car elle doit s'exécuter dans un contexte de servlet, qui n'est pas fourni.

4.1. Utilisation de la botte de printemps

Spring Boot fournit plusieurs annotations pour configurer les SpringApplicationContext pour nos tests de manière plus intuitive.

Nous pouvons charger uniquement une tranche particulière de la configuration de l'application ou simuler le processus de démarrage du contexte complet.

Par exemple, nous pouvons utiliser l'annotation@SpringBootTest si nous voulons que tout le contexte soit créé sans démarrer le serveur.

Avec cela en place, nous pouvons ensuite ajouter les@AutoConfigureMockMvc pour injecter une instanceMockMvc et envoyer des requêtes HTTP:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class FooControllerAppIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void whenTestApp_thenEmptyResponse() throws Exception {
        this.mockMvc.perform(get("/foos")
            .andExpect(status().isOk())
            .andExpect(...);
    }

}

Pour éviter de créer tout le contexte et tester uniquement nos contrôleurs MVC, nous pouvons utiliser@WebMvcTest:

@RunWith(SpringRunner.class)
@WebMvcTest(FooController.class)
public class FooControllerWebLayerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private IFooService service;

    @Test()
    public void whenTestMvcController_thenRetrieveExpectedResult() throws Exception {
        // ...

        this.mockMvc.perform(get("/foos")
            .andExpect(...);
    }
}

Nous pouvons trouver des informations détaillées à ce sujet surour ‘Testing in Spring Boot' article.

5. Le controlle

The @RestController is the central artifact in the entire Web Tier of the RESTful API. Pour les besoins de cet article, le contrôleur modélise une simple ressource REST -Foo:

@RestController
@RequestMapping("/foos")
class FooController {

    @Autowired
    private IFooService service;

    @GetMapping
    public List findAll() {
        return service.findAll();
    }

    @GetMapping(value = "/{id}")
    public Foo findById(@PathVariable("id") Long id) {
        return RestPreconditions.checkFound(service.findById(id));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Long create(@RequestBody Foo resource) {
        Preconditions.checkNotNull(resource);
        return service.create(resource);
    }

    @PutMapping(value = "/{id}")
    @ResponseStatus(HttpStatus.OK)
    public void update(@PathVariable( "id" ) Long id, @RequestBody Foo resource) {
        Preconditions.checkNotNull(resource);
        RestPreconditions.checkNotNull(service.getById(resource.getId()));
        service.update(resource);
    }

    @DeleteMapping(value = "/{id}")
    @ResponseStatus(HttpStatus.OK)
    public void delete(@PathVariable("id") Long id) {
        service.deleteById(id);
    }

}

Vous avez peut-être remarqué que j'utilise un utilitaire simple de style GuavaRestPreconditions:

public class RestPreconditions {
    public static  T checkFound(T resource) {
        if (resource == null) {
            throw new MyResourceNotFoundException();
        }
        return resource;
    }
}

L’implémentation du contrôleur n’est pas publique, car elle n’a pas besoin de l’être.

Généralement, le contrôleur est le dernier membre de la chaîne de dépendances. Il reçoit les requêtes HTTP du contrôleur frontal Spring (lesDispatcherServlet) et les délègue simplement à une couche de service. S'il n'y a pas de cas d'utilisation où le contrôleur doit être injecté ou manipulé via une référence directe, je préfère ne pas le déclarer public.

Les mappages de demandes sont simples. As with any controller, the actual value of the mapping, as well as the HTTP method, determine the target method for the request. @RequestBody liera les paramètres de la méthode au corps de la requête HTTP, tandis que@ResponseBody fait de même pour la réponse et le type de retour.

Le@RestController  est unshorthand pour inclure à la fois les annotations@ResponseBody et @Controller dans notre classe.

Ils veillent également à ce que la ressource soit organisée et conservée à l'aide du convertisseur HTTP approprié. La négociation de contenu aura lieu pour choisir lequel des convertisseurs actifs sera utilisé, principalement sur la base de l'en-têteAccept, bien que d'autres en-têtes HTTP puissent également être utilisés pour déterminer la représentation.

6. Mappage des codes de réponse HTTP

Les codes de statut de la réponse HTTP sont l’une des parties les plus importantes du service REST et le sujet peut rapidement devenir très compliqué. Cela peut être ce qui fait ou défait le service.

6.1. Demandes non mappées

Si Spring MVC reçoit une demande qui n'a pas de mappage, il considère que la demande n'est pas autorisée et renvoie une METHODE 405 NON AUTORISEE au client.

Il est également recommandé d’inclure l’en-tête HTTPAllow lors du renvoi d’un405 au client, pour spécifier les opérations autorisées. Il s'agit du comportement standard de Spring MVC et ne nécessite aucune configuration supplémentaire.

6.2. Demandes mappées valides

Pour toute demande qui a un mappage, Spring MVC considère la demande comme valide et répond avec 200 OK si aucun autre code d'état n'est spécifié autrement.

C'est pour cette raison que le contrôleur déclare des@ResponseStatus différents pour les actionscreate,update etdelete mais pas pourget, ce qui devrait en effet renvoyer la valeur par défaut 200 D'ACCORD.

6.3. Erreur client

En cas d'erreur client, des exceptions personnalisées sont définies et mappées aux codes d'erreur appropriés.

Le simple fait de lancer ces exceptions depuis l'une des couches du niveau Web garantira que Spring mappe le code d'état correspondant sur la réponse HTTP:

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
   //
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
   //
}

Ces exceptions font partie de l'API REST et, en tant que telles, ne doivent être utilisées que dans les couches appropriées correspondant à REST. Si, par exemple, une couche DAO / DAL existe, les exceptions ne doivent pas être utilisées directement.

Notez également qu'il ne s'agit pas d'exceptions vérifiées, mais d'exceptions d'exécution, conformément aux pratiques et aux idiomes de Spring.

6.4. Utilisation de@ExceptionHandler

Une autre option pour mapper des exceptions personnalisées sur des codes d'état spécifiques consiste à utiliser l'annotation@ExceptionHandler dans le contrôleur. Le problème avec cette approche est que l’annotation ne s’applique qu’au contrôleur dans lequel elle est définie. Cela signifie que nous devons déclarer chaque contrôleur individuellement.

Bien sûr, il y a plus deways to handle errorsdans Spring Boot et Spring Boot qui offrent plus de flexibilité.

7. Additional Maven Dependencies **

En plus de la dépendancespring-webmvcrequired for the standard web application, nous devrons configurer le marshalling et le démarshalling de contenu pour l'API REST:


   
      com.fasterxml.jackson.core
      jackson-databind
      2.9.8
   
   
      javax.xml.bind
      jaxb-api
      2.3.1
      runtime
   

Ce sont les bibliothèques utilisées pour convertir la représentation de la ressource REST en JSON ou XML.

7.1. Utilisation de la botte de printemps

Si nous voulons récupérer des ressources au format JSON, Spring Boot prend en charge différentes bibliothèques, à savoir Jackson, Gson et JSON-B.

La configuration automatique est effectuée en incluant simplement l'une des bibliothèques de mappage dans le chemin d'accès aux classes.

Généralement, si nous développons une application Web,we’ll just add the spring-boot-starter-web dependency and rely on it to include all the necessary artifacts to our project:


    org.springframework.boot
    spring-boot-starter-web
    2.1.2.RELEASE

Spring Boot utilise Jackson par défaut.

Si nous voulons sérialiser nos ressources au format XML, nous devrons ajouter l'extension Jackson XML (jackson-dataformat-xml) à nos dépendances, ou revenir à l'implémentation JAXB (fournie par défaut dans le JDK) en utilisant le Annotation@XmlRootElement sur notre ressource.

8. Conclusion

Ce didacticiel explique comment implémenter et configurer un service REST à l'aide de la configuration Spring et Java.

Dans les prochains articles de la série, je me concentrerai surDiscoverability of the API, la négociation de contenu avancée et le travail avec des représentations supplémentaires d'unResource.

Tout le code de cet article est disponibleover on Github. Ceci est un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.