Construiți un API REST cu Spring și Java Config

REST Top

Tocmai am anunțat noul curs Learn Spring , axat pe fundamentele Spring 5 și Spring Boot 2:

>> VERIFICAȚI CURSUL

1. Prezentare generală

Acest articol arată cum să configurați REST în primăvară - controlerul și codurile de răspuns HTTP, configurația marshallingului sarcinii utile și negocierea conținutului.

2. Înțelegerea REST în primăvară

Cadrul de primăvară acceptă două moduri de a crea servicii RESTful:

  • folosind MVC cu ModelAndView
  • folosind convertoare de mesaje HTTP

Abordarea ModelAndView este mai veche și mult mai bine documentată, dar și mai detaliată și mai configurată. Încearcă să adauge paradigma REST în vechiul model, care nu este lipsit de probleme. Echipa Spring a înțeles acest lucru și a oferit suport REST de primă clasă începând cu Spring 3.0.

Noua abordare, bazată pe HttpMessageConverter și adnotări, este mult mai ușoară și ușor de implementat. Configurarea este minimă și oferă valori implicite sensibile pentru ceea ce v-ați aștepta de la un serviciu RESTful.

3. Configurarea Java

@Configuration @EnableWebMvc public class WebConfig{ // }

Noua adnotare @EnableWebMvc face câteva lucruri utile - în mod specific, în cazul REST, detectează existența lui Jackson și JAXB 2 pe calea de clasă și creează și înregistrează automat convertoarele JSON și XML implicite. Funcționalitatea adnotării este echivalentă cu versiunea XML:

Aceasta este o comandă rapidă și, deși poate fi utilă în multe situații, nu este perfectă. Când este necesară o configurație mai complexă, eliminați adnotarea și extindeți direct WebMvcConfigurationSupport .

3.1. Folosind Spring Boot

Dacă folosim adnotarea @SpringBootApplication și biblioteca spring-webmvc se află pe clasa, atunci adnotarea @EnableWebMvc este adăugată automat cu o configurare automată implicită.

Putem adăuga în continuare funcționalitate MVC la această configurație prin implementarea interfeței WebMvcConfigurer pe o clasă adnotată @Configuration . De asemenea, putem folosi o instanță WebMvcRegistrationsAdapter pentru a furniza propriile noastre aplicații RequestMappingHandlerMapping , RequestMappingHandlerAdapter sau ExceptionHandlerExceptionResolver .

În cele din urmă, dacă dorim să renunțăm la caracteristicile MVC ale Spring Boot și să declarăm o configurație personalizată, o putem face folosind adnotarea @EnableWebMvc .

4. Testarea contextului de primăvară

Începând cu primăvara 3.1, primim asistență de testare de primă clasă pentru clasele @Configuration :

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

Specificăm clasele de configurare Java cu adnotarea @ContextConfiguration . Noul AnnotationConfigContextLoader încarcă definițiile bean din clasele @Configuration .

Observați că clasa de configurare WebConfig nu a fost inclusă în test, deoarece trebuie să ruleze într-un context Servlet, care nu este furnizat.

4.1. Folosind Spring Boot

Spring Boot oferă mai multe adnotări pentru a configura Spring ApplicationContext pentru testele noastre într-un mod mai intuitiv.

Putem încărca doar o anumită porțiune din configurația aplicației sau putem simula întregul proces de pornire a contextului.

De exemplu, putem folosi adnotarea @SpringBootTest dacă dorim să fie creat întregul context fără a porni serverul.

Cu acest lucru în loc, putem adăuga apoi @AutoConfigureMockMvc pentru a injecta o instanță MockMvc și a trimite cereri 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(...); } }

Pentru a evita crearea întregului context și a testa doar controlerele noastre MVC, putem folosi @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(...); } }

Putem găsi informații detaliate cu privire la acest subiect în articolul nostru „Testare în boot de primăvară”.

5. Controlerul

@RestController este artefact central în întreaga Tier web a API - ului REST. În scopul acestei postări, controlerul modelează o resursă simplă 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); } }

Este posibil să fi observat că folosesc un utilitar RestPreconditions în stil Guava :

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

Implementarea controlerului nu este publică - acest lucru se datorează faptului că nu trebuie să fie.

De obicei, controlerul este ultimul din lanțul de dependențe. Acesta primește solicitări HTTP de la controlerul frontal Spring ( DispatcherServlet ) și le delegă pur și simplu către un strat de serviciu. Dacă nu există un caz de utilizare în care controlerul trebuie să fie injectat sau manipulat printr-o referință directă, atunci prefer să nu-l declar public.

Asocierile solicitării sunt simple. Ca și în cazul oricărui controler, valoarea reală a mapării, precum și metoda HTTP, determină metoda țintă pentru cerere. @ RequestBody va lega parametrii metodei de corpul cererii HTTP, în timp ce @ResponseBody face același lucru pentru tipul de răspuns și returnare.

@RestController este o prescurtare pentru a include atât @ResponseBody și @Controller adnotările din clasa noastră .

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

Acest tutorial a ilustrat cum să implementați și să configurați un serviciu REST utilizând configurația bazată pe Spring și Java.

În următoarele articole ale seriei, mă voi concentra pe descoperibilitatea API-ului, negocierea avansată a conținutului și lucrul cu reprezentări suplimentare ale unei resurse.

Tot codul acestui articol este disponibil pe Github. Acesta este un proiect bazat pe Maven, deci ar trebui să fie ușor de importat și rulat așa cum este.

REST de jos

Tocmai am anunțat noul curs Learn Spring , axat pe fundamentele Spring 5 și Spring Boot 2:

>> VERIFICAȚI CURSUL