Créer une API REST avec Spring et Java Config

Haut REST

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS

1. Vue d'ensemble

Cet article montre comment configurer REST au printemps - les codes de réponse Controller et HTTP, la configuration du marshalling de la charge utile et la négociation de contenu.

2. Comprendre REST au printemps

Le framework Spring prend en charge deux façons de créer des services RESTful:

  • utilisation de MVC avec ModelAndView
  • utilisation de convertisseurs de messages HTTP

L' approche ModelAndView est plus ancienne et beaucoup mieux documentée, mais aussi plus détaillée et lourde en configuration. Il essaie d'intégrer le paradigme REST dans l'ancien modèle, ce qui n'est pas sans problèmes. L'équipe Spring l'a compris et a fourni un support REST de première classe à partir de Spring 3.0.

La nouvelle approche, basée sur HttpMessageConverter et les annotations, est beaucoup plus légère et facile à mettre en œuvre. La configuration 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 même s'il peut être utile dans de nombreuses situations, il n'est pas parfait. Lorsqu'une configuration plus complexe est nécessaire, supprimez l'annotation et étendez directement WebMvcConfigurationSupport .

3.1. Utilisation de Spring Boot

Si nous utilisons l' annotation @SpringBootApplication et que la bibliothèque spring-webmvc se trouve sur le chemin de classe, l' annotation @EnableWebMvc est ajoutée automatiquement avec une configuration automatique par défaut.

Nous pouvons toujours ajouter des fonctionnalités MVC à cette configuration en implémentant l' interface WebMvcConfigurer sur une classe annotée @Configuration . Nous pouvons également utiliser une instance WebMvcRegistrationsAdapter pour fournir nos propres implémentations RequestMappingHandlerMapping , RequestMappingHandlerAdapter ou ExceptionHandlerExceptionResolver .

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. Test du 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 nouveau AnnotationConfigContextLoader charge les définitions de bean à partir des classes @Configuration .

Notez que la classe de configuration WebConfig 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 Spring Boot

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

Nous ne pouvons charger qu'une tranche particulière de la configuration de l'application, ou nous pouvons simuler l'ensemble du processus de démarrage du contexte.

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

Une fois cela en place, nous pouvons ensuite ajouter @AutoConfigureMockMvc pour injecter une instance MockMvc 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 dans notre article «Test dans Spring Boot».

5. Le contrôleur

Le @RestController est l'artefact central dans tout le niveau Web de l'API RESTful. 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 RestPreconditions simple de style Guava :

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 - c'est parce qu'elle n'a pas besoin de l'être.

Habituellement, le contrôleur est le dernier de la chaîne de dépendances. Il reçoit les requêtes HTTP du contrôleur frontal Spring (le DispatcherServlet ) 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 comme public.

Les mappages de demandes sont simples. Comme pour tout contrôleur, la valeur réelle du mappage, ainsi que la méthode HTTP, déterminent la méthode cible de la requête. @ 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 un raccourci pour inclure à la fois les annotations @ResponseBody et @Controller dans notre classe .

They also ensure that the resource will be marshalled and unmarshalled using the correct HTTP converter. Content negotiation will take place to choose which one of the active converters will be used, based mostly on the Accept header, although other HTTP headers may be used to determine the representation as well.

6. Mapping the HTTP Response Codes

The status codes of the HTTP response are one of the most important parts of the REST service, and the subject can quickly become very complicated. Getting these right can be what makes or breaks the service.

6.1. Unmapped Requests

If Spring MVC receives a request which doesn't have a mapping, it considers the request not to be allowed and returns a 405 METHOD NOT ALLOWED back to the client.

It's also a good practice to include the Allow HTTP header when returning a 405 to the client, to specify which operations are allowed. This is the standard behavior of Spring MVC and doesn't require any additional configuration.

6.2. Valid Mapped Requests

For any request that does have a mapping, Spring MVC considers the request valid and responds with 200 OK if no other status code is specified otherwise.

It's because of this that the controller declares different @ResponseStatus for the create, update and delete actions but not for get, which should indeed return the default 200 OK.

6.3. Client Error

In the case of a client error, custom exceptions are defined and mapped to the appropriate error codes.

Simply throwing these exceptions from any of the layers of the web tier will ensure Spring maps the corresponding status code on the HTTP response:

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

These exceptions are part of the REST API and, as such, should only be used in the appropriate layers corresponding to REST; if for instance, a DAO/DAL layer exists, it should not use the exceptions directly.

Note also that these are not checked exceptions but runtime exceptions – in line with Spring practices and idioms.

6.4. Using @ExceptionHandler

Another option to map custom exceptions on specific status codes is to use the @ExceptionHandler annotation in the controller. The problem with that approach is that the annotation only applies to the controller in which it's defined. This means that we need to declares in each controller individually.

Of course, there are more ways to handle errors in both Spring and Spring Boot that offer more flexibility.

7. Additional Maven Dependencies

In addition to the spring-webmvc dependency required for the standard web application, we'll need to set up content marshalling and unmarshalling for the REST API:

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

These are the libraries used to convert the representation of the REST resource to either JSON or XML.

7.1. Using Spring Boot

If we want to retrieve JSON-formatted resources, Spring Boot provides support for different libraries, namely Jackson, Gson and JSON-B.

Auto-configuration is carried out by just including any of the mapping libraries in the classpath.

Usually, if we're developing a web application, 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 uses Jackson by default.

If we want to serialize our resources in an XML format, we'll have to add the Jackson XML extension (jackson-dataformat-xml) to our dependencies, or fallback to the JAXB implementation (provided by default in the JDK) by using the @XmlRootElement annotation on our resource.

8. Conclusion

Ce didacticiel a illustré 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 sur la découvrabilité de l'API, la négociation de contenu avancée et le travail avec des représentations supplémentaires d'une ressource.

Tout le code de cet article est disponible à l'adresse over sur Github. Il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.

REST bas

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS