Hace bastante tiempo publiqué un post en el que explicaba como documentar un servicio REST montado con Spring, con Swagger. Desde entonces, la librería usada para Spring ha cambiado bastante (incluso de nombre y de dueños…). En este post veremos como hacerlo con la nueva librería, SpringFox.
Concepto
Desde hace tiempo la antigua librería ‘swagger-springmvc‘, que servia para integrar swagger fácilmente en un proyecto Spring, ha pasado a llamarse SpringFox, y ha cambiado un poco. En sus últimas versiones soporta Swagger 2.0. Ya hablé un poco sobre Swagger en el post que comentaba antes, pero tenéis más info sobre swagger y sobre SpringFox en estos links:
http://swagger.io/
https://github.com/swagger-api
http://springfox.github.io/springfox/
https://github.com/springfox/springfox
En este post voy a documentar un servicio REST Spring con Swagger 2.0, usando SpringFox. Para ello usare como base un proyecto SpringBoot con un servicio REST ‘Infos’ que ya use en otros posts anteriores:
https://anotherdayanotherbug.wordpress.com/2015/03/16/tests-de-integracion-para-un-servicio-rest-con-spring/
https://anotherdayanotherbug.wordpress.com/2015/05/25/tests-de-integracion-usando-rest-assured/
Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 15.0.2 Ultimate version
Pasos
1. Creamos nuestro proyecto SpringBoot como siempre , con la versión 1.3. Lo principal que necesitamos es el pom.xml de Maven, la clase ‘main’ de SpringBoot … y listo.
2. Implementamos nuestro sencillo servicio REST. Un controller, que usa un Service, él cual usa un Repository (mockeado este último). Nuestra entity y el controller son estos:
public class Info {
private Long id;
private String infoText;
private LocalDateTime creationDateTime;
// getters and setters
}
@RestController
@RequestMapping("/api/infos")
public class InfoController {
@Autowired
private InfoService infoService;
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public List<Info> getAllInfos() {
return infoService.findAll();
}
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET, value = "{id}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Info getInfo(@PathVariable Long id) {
return infoService.findOne(id);
}
@RequestMapping(method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Info> createInfo(@RequestBody Info info) {
Info infoCreated = infoService.save(info);
return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
HttpStatus.CREATED);
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.PUT, value = "{id}")
public void updateInfo(@PathVariable Long id, @RequestBody Info info) {
infoService.update(info.setId(id));
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.DELETE, value = "{id}")
public void deleteInfo(@PathVariable Long id) {
infoService.delete(id);
}
private HttpHeaders createHeadersWithLocation(Info info) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(
ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(info.getId())
.toUri());
return httpHeaders;
}
}
3. Vamos a empezar a integrarle Swagger 2 con SpringFox. Primero añadimos las librerías necesarias a nuestro pom.xml:
...
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.3.0</version>
</dependency>
...
La principal que necesitamos es ‘springfox-swagger2‘.
La de ui es más bien un ‘webjar’ con la ui de swagger de siempre. Lo bueno es que esta todo en el jar, y no tenemos que bajarnos toda la ui en una subcarpeta del proyecto, como hice en el post anterior.
4. Creamos una clase de configuración de Spring, con la anotación @EnableSwagger2 y con el bean básico que necesita Swagger 2 (Docket):
package com.edwise.pocs.swagger2.config;
// imports...
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket newsApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("api-infos")
.apiInfo(apiInfo())
.directModelSubstitute(LocalDateTime.class, Date.class)
.select()
.paths(regex("/api.*"))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Infos REST api")
.description("PoC of a REST api, Infos")
.termsOfServiceUrl("http://en.wikipedia.org/wiki/Terms_of_service")
.contact("edwise.null@gmail.com")
.license("Apache License Version 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("2.0")
.build();
}
}
Esto es similar a la anterior configuración con la librería swagger-springmvc, pero aquí en lugar de un SwaggerSpringMvcPlugin, tenemos un bean llamado Docket, pero es similar. Tiene alguna mejora interesante. Por ejemplo, aquí estamos diciéndole que, a la hora de mostrar los tipos de dato LocalDateTime en la documentación de la API salgan como String (si no, te sale todos los objetos que contiene LocalDateTime, es un guarreo…)
5. Ya solo falta documentar nuestro api REST. Para eso, documentaremos primero la entity ‘Info’ así:
package com.edwise.pocs.swagger2.entity;
// imports...
@ApiModel(value = "Info entity", description = "Complete data of a entity Info")
public class Info {
@ApiModelProperty(value = "The id of the info", required = false)
private Long id;
@ApiModelProperty(value = "The text of the info", required = true)
private String infoText;
@ApiModelProperty(value = "The date of the info", required = true)
private LocalDateTime creationDateTime;
// getters and settters
}
Apenas hay cambios respecto a la versión anterior. Usamos las anotaciones @ApiModel y @ApiModelProperty para documentar nuestro pojo.
6. Y ahora documentamos nuestro controller:
package com.edwise.pocs.swagger2.controller;
// imports...
@RestController
@RequestMapping("/api/infos")
@Api(value = "infos", description = "Infos API", produces = "application/json")
public class InfoController {
@Autowired
private InfoService infoService;
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get Infos", notes = "Returns all infos")
@ApiResponses({
@ApiResponse(code = 200, message = "Exits one info at least")
})
public List<Info> getAllInfos() {
return infoService.findAll();
}
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET, value = "{id}",
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get one Info", notes = "Returns one info")
@ApiResponses({
@ApiResponse(code = 200, message = "Exists this info")
})
public Info getInfo(@ApiParam(defaultValue = "1", value = "The id of the info to return")
@PathVariable Long id) {
return infoService.findOne(id);
}
@RequestMapping(method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Create Info", notes = "Create a info")
@ApiResponses({
@ApiResponse(code = 201, message = "Successful create of a info")
})
public ResponseEntity<Info> createInfo(@RequestBody Info info) {
Info infoCreated = infoService.save(info);
return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
HttpStatus.CREATED);
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.PUT, value = "{id}")
@ApiOperation(value = "Update Info", notes = "Update a info")
@ApiResponses({
@ApiResponse(code = 204, message = "Successful update of a info")
})
public void updateInfo(@ApiParam(defaultValue = "1", value = "The id of the info to update")
@PathVariable Long id,
@RequestBody Info info) {
infoService.update(info.setId(id));
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.DELETE, value = "{id}")
@ApiOperation(value = "Delete Info", notes = "Delete a info")
@ApiResponses({
@ApiResponse(code = 204, message = "Successful delete of a info")
})
public void deleteInfo(@ApiParam(defaultValue = "1", value = "The id of the info to delete")
@PathVariable Long id) {
infoService.delete(id);
}
private HttpHeaders createHeadersWithLocation(Info info) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(
ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(info.getId())
.toUri());
return httpHeaders;
}
}
Tampoco cambia nada, seguimos usando las mismas anotaciones para los controlers: @Api, @ApiOperation, @ApiResponses, @ApiParam…
En el anterior post sobre Swagger las explico un poco por encima, aunque creo que viendo el código es bastante sencillo entender como funcionan.
7. Arrancamos nuestro proyecto SpringBoot (mvn spring-boot:run). Si accedemos a http://localhost:8080/swagger-ui.html podemos ver la interfaz de Swagger y juguetear con nuestro API.
Si queremos acceder directamente a la info (en formato json) de Swagger, accedemos a http://localhost:8080/v2/api-docs?group=api-infos. El formato ha cambiado bastante respecto a la versión anterior, echadle un vistazo.
El código de este post está en mi github, proyecto springfox-swagger2-example.