Spring MVC 를 사용중이라면 XML 을 java 내에서 파싱해야 되는 경우가 종종 있습니다.
그때 사용할 수 있는 선택지는 크게 2가지입니다.
이 게시물에서는 JAXB API
에 대해 제가 공부했던 내용들을 기록해보려 합니다.
먼저 사용을 위해서 maven ( 또는 다른 gradle ) 을 사용해서
라이브러리 의존성을 추가해줘야 합니다.
참고로 jdk 17 을 사용 중입니다.
jdk 버전에 따라서 다르게 dependency 를 받아야 할 수도 있습니다.
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.2</version>
</dependency>
여기서는 코드나 예제가 부분적으로 잘라서 게시된 것입니다.
모든 코드를 보고 싶다면 깃허브에서 확인하실 수 있습니다.
<?xml version="1.0" encoding="UTF-8"?>
<RESPONSE>
<HEADER>
<CODE>200</CODE>
<MESSAGE>GET BUILDING INFO LIST - SUCCESS</MESSAGE>
</HEADER>
<BODY>
<BLDG_LIST>
<BLDG_INFO>
<ZIPCODE>111-222</ZIPCODE>
<BLDG_NM>White Bread Luxury Hotel</BLDG_NM>
</BLDG_INFO>
<BLDG_INFO>
<ZIPCODE>333-444</ZIPCODE>
<BLDG_NM>Toast Bread Luxury Hotel</BLDG_NM>
</BLDG_INFO>
</BLDG_LIST>
</BODY>
</RESPONSE>
저는 DTO 에 JAXB 애노테이션을 붙일 때 아래와 같은 패턴을 정해서 작성합니다.
이렇게 패턴을 고정하고 작성하면 머리가 덜 아프더군요 😋
@XMLRootElement(name = {태그명})
@XmlAccessorType(XmlAccessType.FIELD)
getter
, setter
생성 (저는 롬복 사용)@XmlElement(name = {태그명})
@XmlElementWrapper(name = {리스트 태그명})
@XmlElement(name = {리스트 요소 태그명})
@XmlRootElement(name = "RESPONSE")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
private static class Response {
@XmlElement(name = "HEADER")
private Header header;
@XmlElement(name = "BODY")
private Body body;
}
@XmlRootElement(name = "HEADER")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
private static class Header {
@XmlElement(name = "CODE")
private String code;
@XmlElement(name = "MESSAGE")
private String message;
}
@XmlRootElement(name = "BODY")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
private static class Body {
@XmlElementWrapper(name = "BLDG_LIST")
@XmlElement(name = "BLDG_INFO")
private List<BldgInfo> bldgList;
}
@XmlRootElement(name = "BODY")
@XmlAccessorType(XmlAccessType.FIELD)
@Data @NoArgsConstructor @AllArgsConstructor
private static class BldgInfo {
@XmlElement(name = "ZIPCODE")
private String zipcode;
@XmlElement(name = "BLDG_NM")
private String bldgNm;
}
marshalling(마셜링)
, unmarshalling(언마셜링)
의 의미는 다음과 같습니다.
marshalling
: JAVA -> XML
변환unmarshalling
: XML -> JAVA
변환지금부터 JAXB API 에서 제공하는 JAXBContext 를 사용해서
이러한 마셜링, 언마셜링을 직접 실습해보겠습니다.
3-1. 마셜링
@Test
@DisplayName("converting pojo to xml string or file (= marshalling)")
void convertPojoToXmlTest() {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class);
Marshaller marshaller = jaxbContext.createMarshaller();
Response response = new Response();
Header header = new Header();
Body body = new Body();
List<BldgInfo> bldgInfos = List.of(
new BldgInfo("111-222", "luxury hotel"),
new BldgInfo("222-333", "luxury motel")
);
body.setBldgList(bldgInfos);
header.setCode("200");
header.setMessage("GET BUILDING INFO LIST - SUCCESS");
response.setHeader(header);
response.setBody(body);
// The following code adds a new line to improve readability.
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// read xml string from marshaller
ByteArrayOutputStream os = new ByteArrayOutputStream();
marshaller.marshal(response, os);
String xmlString = os.toString(StandardCharsets.UTF_8);
// remark: if your jdk version is lower than 10, use code below
// ==> String s = new String(os.toByteArray(), StandardCharsets.UTF_8);
log.debug("\n{}", xmlString);
// 혹시라도 "<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"
// 에서 standalone="yes" 를 빼고 싶으신 분들은 아래 코드를 참고해주세요.
// marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
// StringWriter stringWriter = new StringWriter();
// stringWriter.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
// marshaller.marshal(response, stringWriter);
// System.out.println(stringWriter.toString());
} catch (JAXBException e) {
fail(e.getMessage());
}
}
마셜링 테스트 코드 실행결과:
3-2. 언마셜링
@Test
@DisplayName("converting complicating xml file to pojo (= unmarshalling)")
void complicateXmlToPojoConvertingTest() {
try (InputStream is = this.getClass().getResourceAsStream("./complicate.xml")) {
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Response buildingInfoResponse = (Response) unmarshaller.unmarshal(is);
log.debug("header info = {}", buildingInfoResponse.getHeader());
log.debug("body info = {}", buildingInfoResponse.getBody());
log.debug("building list info = {}", buildingInfoResponse.getBody().getBldgList());
} catch (IOException | JAXBException e) {
fail(e.getMessage());
}
}
마셜링 테스트 코드 실행결과:
Spring MVC 에서 제공하는 RestTemplate
을 사용하면 아주 간편하게 언마셜링이 가능합니다.
Controller Endpoint 코드를 기준으로 예시를 작성하겠다.
@GetMapping("/buldingDong")
@ResponseBody
public ResponseDTO bldgDongList(@RequestParam MultiValueMap<String, String> params) {
URI uri = UriComponentsBuilder.fromUriString("http://example.com")
.queryParams(params)
.build.toUri();
XmlResponseDTO responseDTO
= new RestTemplate().getForObject(uri, Response.class);
return responseDTO;
}