Acabo de anunciar el nuevo curso Learn Spring , centrado en los fundamentos de Spring 5 y Spring Boot 2:
>> VER EL CURSO1. Información general
Este artículo muestra cómo configurar REST en Spring : los códigos de respuesta del controlador y HTTP, la configuración de la clasificación de carga útil y la negociación de contenido.
2. Comprensión del REST en Spring
El marco de Spring admite dos formas de crear servicios RESTful:
- usando MVC con ModelAndView
- usando convertidores de mensajes HTTP
El enfoque ModelAndView es más antiguo y está mucho mejor documentado, pero también es más detallado y pesado en la configuración. Intenta calzar el paradigma REST en el modelo antiguo, que no está exento de problemas. El equipo de Spring entendió esto y brindó soporte REST de primera clase a partir de Spring 3.0.
El nuevo enfoque, basado en HttpMessageConverter y anotaciones, es mucho más ligero y fácil de implementar. La configuración es mínima y proporciona valores predeterminados razonables para lo que esperaría de un servicio RESTful.
3. La configuración de Java
@Configuration @EnableWebMvc public class WebConfig{ // }
La nueva anotación @EnableWebMvc hace algunas cosas útiles; específicamente, en el caso de REST, detecta la existencia de Jackson y JAXB 2 en la ruta de clase y crea y registra automáticamente convertidores JSON y XML predeterminados. La funcionalidad de la anotación es equivalente a la versión XML:
Este es un atajo y, aunque puede ser útil en muchas situaciones, no es perfecto. Cuando se necesite una configuración más compleja, elimine la anotación y amplíe WebMvcConfigurationSupport directamente.
3.1. Usando Spring Boot
Si estamos usando la anotación @SpringBootApplication y la biblioteca spring-webmvc está en la ruta de clases, entonces la anotación @EnableWebMvc se agrega automáticamente con una configuración automática predeterminada.
Todavía podemos agregar la funcionalidad MVC a esta configuración implementando la interfaz WebMvcConfigurer en una clase anotada @Configuration . También podemos usar una instancia de WebMvcRegistrationsAdapter para proporcionar nuestras propias implementaciones RequestMappingHandlerMapping , RequestMappingHandlerAdapter o ExceptionHandlerExceptionResolver .
Finalmente, si queremos descartar las características MVC de Spring Boot y declarar una configuración personalizada, podemos hacerlo usando la anotación @EnableWebMvc .
4. Prueba del contexto de primavera
A partir de Spring 3.1, obtenemos soporte de prueba de primera clase para las clases de @Configuration :
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( classes = {WebConfig.class, PersistenceConfig.class}, loader = AnnotationConfigContextLoader.class) public class SpringContextIntegrationTest { @Test public void contextLoads(){ // When } }
Estamos especificando las clases de configuración de Java con la anotación @ContextConfiguration . El nuevo AnnotationConfigContextLoader carga las definiciones de bean de las clases @Configuration .
Tenga en cuenta que la clase de configuración WebConfig no se incluyó en la prueba porque necesita ejecutarse en un contexto de Servlet, que no se proporciona.
4.1. Usando Spring Boot
Spring Boot proporciona varias anotaciones para configurar Spring ApplicationContext para nuestras pruebas de una manera más intuitiva.
Podemos cargar solo una porción particular de la configuración de la aplicación, o podemos simular todo el proceso de inicio del contexto.
Por ejemplo, podemos usar la anotación @SpringBootTest si queremos que se cree todo el contexto sin iniciar el servidor.
Con eso en su lugar, podemos agregar @AutoConfigureMockMvc para inyectar una instancia de MockMvc y enviar solicitudes 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(...); } }
Para evitar crear todo el contexto y probar solo nuestros controladores MVC, podemos usar @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(...); } }
Podemos encontrar información detallada sobre este tema en nuestro artículo 'Pruebas en Spring Boot'.
5. El controlador
El @RestController es el artefacto central en toda la capa web de la API REST. Para el propósito de esta publicación, el controlador está modelando un recurso REST simple : 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); } }
Es posible que haya notado que estoy usando una sencilla utilidad RestPreconditions estilo Guava :
public class RestPreconditions { public static T checkFound(T resource) { if (resource == null) { throw new MyResourceNotFoundException(); } return resource; } }
La implementación del controlador no es pública, esto se debe a que no es necesario.
Por lo general, el controlador es el último en la cadena de dependencias. Recibe solicitudes HTTP del controlador frontal de Spring (el DispatcherServlet ) y simplemente las delega hacia una capa de servicio. Si no hay un caso de uso en el que el controlador deba inyectarse o manipularse a través de una referencia directa, prefiero no declararlo público.
Las asignaciones de solicitudes son sencillas. Al igual que con cualquier controlador, el valor real de la asignación, así como el método HTTP, determinan el método de destino para la solicitud. @ RequestBody vinculará los parámetros del método al cuerpo de la solicitud HTTP, mientras que @ResponseBody hace lo mismo para la respuesta y el tipo de retorno.
El @RestController es una forma abreviada de incluir las anotaciones @ResponseBody y @Controller en nuestra clase .
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
Este tutorial ilustró cómo implementar y configurar un servicio REST utilizando Spring y una configuración basada en Java.
En los próximos artículos de la serie, me centraré en la capacidad de descubrimiento de la API, la negociación de contenido avanzado y el trabajo con representaciones adicionales de un recurso.
Todo el código de este artículo está disponible en Github. Este es un proyecto basado en Maven, por lo que debería ser fácil de importar y ejecutar como está.
DESCANSO inferior