En este post técnico veremos como responder XML usando JAXB en la implementación de Jakarta EE. La Java arquitectura para XML Binding (JAXB) provee una API y herramientas para poder convertir entre documentos XML y objetos Java. NOTA: Si quieres saber más acerca de como crear una aplicación Spring Webflux por favor visita mi previo post empezando con Spring Webflux aquí. Entonces, crea una aplicación Spring Boot con Lombok y Webflux como dependencias:
spring init --dependencies=webflux,lombok --build=gradle --language=java spring-webflux-jaxb
Aquí está el build.gradle
generado:
plugins {
id 'org.springframework.boot' version '2.2.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.jos.dem.spring.webflux.jaxb'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
}
test {
useJUnitPlatform()
}
Después, agrega la dependencia de JAXB
implementation "javax.xml.bind:jaxb-api"
implementation "org.glassfish.jaxb:jaxb-runtime"
Empezaremos creando un controlador que regrese XML
package com.jos.dem.spring.webflux.jaxb.controller;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import com.jos.dem.spring.webflux.jaxb.model.Person;
import com.jos.dem.spring.webflux.jaxb.repository.PersonRepository;
@RestController
public class PersonController {
@Autowired
private PersonRepository personRepository;
private Logger log = LoggerFactory.getLogger(this.getClass());
@GetMapping(value = "/", produces = APPLICATION_XML_VALUE)
public Mono<Person> index() {
log.info("Getting Person");
return personRepository.findOne("josdem");
}
}
Así es, agregando APPLICATION_XML_VALUE
como MediaType en la anotación @GetMapping
hará la magia :). Aquí está nuestro modelo Person
:
package com.jos.dem.spring.webflux.jaxb.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
@Getter
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Person {
@XmlAttribute
private String nickname;
@XmlAttribute
private String firstName;
@XmlAttribute
private String lastName;
@XmlAttribute
private String address;
@XmlElement
private Device device;
}
Dónde:
@XmlRootElement
Esta anotación asocia un documento XML con la clasePerson
.@XmlAttribute
Mapeará a un atributo XML.@XmlElement
Mapeará a un elemento XML.
Aquí está nuestro modelo Device
:
package com.jos.dem.spring.webflux.jaxb.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAttribute;
@Getter
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Device {
@XmlAttribute
private String name;
@XmlAttribute
private String os;
@XmlAttribute
private String model;
}
Finalmente, crearemos un test para nuestro controlador usando WebTestClient
package com.jos.dem.spring.webflux.jaxb;
import com.jos.dem.spring.webflux.jaxb.model.Person;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
@SpringBootTest(classes = DemoApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoApplicationTests {
@Autowired
private WebTestClient webClient;
@Test
@DisplayName("Should Get Person")
void shouldGetPerson() {
webClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(APPLICATION_XML_VALUE)
.expectBody(Person.class)
.value(person -> person.getFirstName(), equalTo("Jose"))
.value(person -> person.getLastName(), equalTo("Morales"))
.value(person -> person.getAddress(), equalTo("30 Frank Lloyd, Ann Arbor MI 48105"))
.value(person -> person.getDevice().getName(), equalTo("Pixel 3"))
.value(person -> person.getDevice().getOs(), equalTo("Android"))
.value(person -> person.getDevice().getModel(), equalTo("9 Pie"));
}
}
Si quieres testear éste proyecto desde lìnea de comando, siempre puedes usar CURL ;)
curl -v http://localhost:8080 \
-H 'Content-Type: application/xml'
Entonces deberìas ver una salida similar a ésta:
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/xml
>
< HTTP/1.1 200 OK
< Content-Type: application/xml
< Content-Length: 222
<
* Connection #0 to host localhost left intact
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><person nickname="josdem" firstName="Jose" lastName="Morales" address="30 Frank Lloyd, Ann Arbor MI 48105"><device name="Pixel 3" os="Android" model="9 Pie"/></person>
Para explorar el proyecto, por favor ve aquí, para descargar el proyecto:
git clone git@github.com:josdem/spring-webflux-jaxb.git
Para correr el proyecto:
gradle bootRun
Para testear el proyecto:
gradle test