Crea un'API REST con Spring e Java Config

REST Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

Questo articolo mostra come configurare REST in primavera : il controller e i codici di risposta HTTP, la configurazione del marshalling del payload e la negoziazione del contenuto.

2. Capire RIPOSO in primavera

Il framework Spring supporta due modi per creare servizi RESTful:

  • utilizzando MVC con ModelAndView
  • utilizzando convertitori di messaggi HTTP

L' approccio ModelAndView è più vecchio e molto meglio documentato, ma anche più dettagliato e pesante di configurazione. Cerca di inserire il paradigma REST nel vecchio modello, che non è privo di problemi. Il team di Spring lo ha capito e ha fornito un supporto REST di prima classe a partire da Spring 3.0.

Il nuovo approccio, basato su HttpMessageConverter e annotazioni, è molto più leggero e facile da implementare. La configurazione è minima e fornisce valori predefiniti ragionevoli per ciò che ti aspetteresti da un servizio RESTful.

3. La configurazione Java

@Configuration @EnableWebMvc public class WebConfig{ // }

La nuova annotazione @EnableWebMvc fa alcune cose utili - in particolare, nel caso di REST, rileva l'esistenza di Jackson e JAXB 2 sul classpath e crea e registra automaticamente convertitori JSON e XML predefiniti. La funzionalità dell'annotazione è equivalente alla versione XML:

Questa è una scorciatoia e, sebbene possa essere utile in molte situazioni, non è perfetta. Quando è necessaria una configurazione più complessa, rimuovere l'annotazione ed estendere direttamente WebMvcConfigurationSupport .

3.1. Utilizzo di Spring Boot

Se stiamo utilizzando l' annotazione @SpringBootApplication e la libreria spring-webmvc si trova sul classpath, l' annotazione @EnableWebMvc viene aggiunta automaticamente con un'autoconfigurazione predefinita.

Possiamo ancora aggiungere la funzionalità MVC a questa configurazione implementando l' interfaccia WebMvcConfigurer su una classe annotata @Configuration . Possiamo anche usare un'istanza WebMvcRegistrationsAdapter per fornire le nostre implementazioni RequestMappingHandlerMapping , RequestMappingHandlerAdapter o ExceptionHandlerExceptionResolver .

Infine, se vogliamo scartare le funzionalità MVC di Spring Boot e dichiarare una configurazione personalizzata, possiamo farlo utilizzando l' annotazione @EnableWebMvc .

4. Testare il contesto primaverile

A partire dalla Spring 3.1, otteniamo un supporto di test di prima classe per le classi @Configuration :

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( classes = {WebConfig.class, PersistenceConfig.class}, loader = AnnotationConfigContextLoader.class) public class SpringContextIntegrationTest { @Test public void contextLoads(){ // When } }

Stiamo specificando le classi di configurazione Java con l' annotazione @ContextConfiguration . Il nuovo AnnotationConfigContextLoader carica le definizioni dei bean dalle classi @Configuration .

Si noti che la classe di configurazione WebConfig non è stata inclusa nel test perché deve essere eseguita in un contesto Servlet, che non è fornito.

4.1. Utilizzo di Spring Boot

Spring Boot fornisce diverse annotazioni per configurare Spring ApplicationContext per i nostri test in modo più intuitivo.

Possiamo caricare solo una particolare fetta della configurazione dell'applicazione, oppure possiamo simulare l'intero processo di avvio del contesto.

Ad esempio, possiamo usare l' annotazione @SpringBootTest se vogliamo che l'intero contesto venga creato senza avviare il server.

Con ciò, possiamo quindi aggiungere @AutoConfigureMockMvc per iniettare un'istanza MockMvc e inviare richieste 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(...); } }

Per evitare di creare l'intero contesto e testare solo i nostri controller MVC, possiamo utilizzare @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(...); } }

Possiamo trovare informazioni dettagliate su questo argomento nel nostro articolo "Test in Spring Boot".

5. Il Titolare

Il @RestController è il manufatto centrale in tutta la Tier Web delle API RESTful. Ai fini di questo post, il controller sta modellando una semplice risorsa 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); } }

Potresti aver notato che sto usando un'utilità RestPreconditions semplice e in stile Guava :

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

L'implementazione del controller non è pubblica, questo perché non è necessario che lo sia.

Di solito, il controller è l'ultimo nella catena di dipendenze. Riceve richieste HTTP dal front controller Spring ( DispatcherServlet ) e le delega semplicemente a un livello di servizio. Se non esiste un caso d'uso in cui il controller deve essere iniettato o manipolato tramite un riferimento diretto, preferisco non dichiararlo pubblico.

Le mappature delle richieste sono semplici. Come con qualsiasi controller, il valore effettivo della mappatura, così come il metodo HTTP, determinano il metodo di destinazione per la richiesta. @ RequestBody legherà i parametri del metodo al corpo della richiesta HTTP, mentre @ResponseBody fa lo stesso per la risposta e il tipo restituito.

Il @RestController è una scorciatoia per includere sia il @ResponseBody ei @Controller annotazioni nella nostra 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

Questo tutorial ha illustrato come implementare e configurare un servizio REST utilizzando Spring e la configurazione basata su Java.

Nei prossimi articoli della serie, mi concentrerò sulla rilevabilità dell'API, sulla negoziazione avanzata dei contenuti e sul lavoro con rappresentazioni aggiuntive di una risorsa.

Tutto il codice di questo articolo è disponibile su Github. Questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.

REST fondo

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO