들어가며
XML 데이터를 DTO로 변환하고 그 반대의 과정도 수행해야하는 업무를 진행하는 과정에서 Java의 JAXB 라이브러리를 알게 되었고, 마샬링과 언마샬링 개념까지 언급되었다.들어보기만 했었던 개념이라 자세하게 짚고 넘어가는 게 좋을 거 같아서 이를 정리해보려 한다!
마샬링, 언마샬링이란?
마샬링이란 객체나 특정 형태의 데이터를 저장 및 전송 가능한 데이터 형태로 변환하는 과정이다.
언마샬링은 마샬링의 반대 개념으로, 변환했던 데이터를 원래대로 복구하는 과정이다.
개념만 봤을 때 직렬화, 역직렬화와 동일한 개념이 아닌가? 라는 생각이 들었다. 그래서 비교 해보기로 했다.
우선 직렬화, 역직렬화의 개념부터 확인해보자.
직렬화란 객체나 데이터 구조를 연속적인 바이트 스트림으로 변환하는 과정이다. 웹에서는 이 개념이 확장되어 JSON 데이터를 객체로 변환하거나, 객체를 JSON 데이터로 변환하는 것을 의미한다. 역직렬화는 이 반대의 개념으로 볼 수 있다.
마샬링/언마샬링, 직렬화/역직렬화는 비슷한 개념을 갖고 있지만 마샬링은 특정 통신 프로토콜이나 파일 포맷에 맞게 데이터를 변환하는 것에 초점을 두고 있기 때문에 마샬링이 직렬화보다 더 큰 범위의 과정을 의미하게 되는 것이다.
깊게 들어가면 두 가지의 차이점은 더욱 크게 드러난다. 해당 차이점에 대해서는 기회가 된다면 다뤄보도록 하겠다!
Spring의 마샬링과 언마샬링
@RequestBody, @ResponseBody
컨트롤러를 만들면 request는 @RequstBody를 통해 Json 데이터를 객체로 변환하여 받아오고, response는 @ResponseBody를 통해 객체 형태의 데이터를 Json으로 변환하여 보내준다.
이러한 어노테이션들은 내부적으로 MappingJacson2HttpMessageConverter에 의해 객체와 Json간 변환 과정을 거치게 된다. 웹에서는 이러한 변환 과정을 직렬화, 역직렬화라고 하며 이는 마샬링, 언마샬링이라고도 할 수 있다.
(다시 한 번 언급하자면 직렬화, 역직렬화는 마샬링, 언마샬링에 포함되는 개념이기 때문임 !!)
Mybatis의 마샬링과 언마샬링
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<sqlMap>
<select id="findById" resultType="com.ssonzm.Member">
SELECT id, nickname, email, phone FROM MEMBER;
</select>
</sqlMap>
Mybatis를 사용하면 SQL 쿼리를 xml 파일로 관리한다. 어떻게 xml에 정의한 쿼리를 가져와서 실행할 수 있을까?
바로 Mybatis를 초기화 할 때 xml 설정 파일들을 로드하고 데이터를 파싱한 다음 최종적으로 자바 객체로 변환하는 언마샬링 과정을 거치기 때문이다. 이렇게 생성된 객체들을 기반으로 하여 JDBC를 통해 연결된 DB와 통신하여 쿼리를 실행하는 것이다.
XML과 자바 객체의 마샬링, 언마샬링
<result>
<msgHeader>
<id></id>
<password></password>
</msgHeader>
<msgBody>
<itemDetail>
<data1></data1>
<data2></data2>
<data3></data3>
</itemDetail>
<itemDetail>
<data1></data1>
<data2></data2>
<data3></data3>
</itemDetail>
</msgBody>
</result>
위 예시와 같은 형태의 XML 파일을 받아와서 자바 객체로 변환하는 마샬링 과정을 어떻게 구현했는지 자세하게 알아보겠다.
우선 각 DTO를 먼저 만들었다. 각 태그를 하나의 DTO로 보면 쉽게 이해가 될 것이다.
그렇다면 result, msgHeader, msgBody, itemDetail, data1, data2, data3 총 7개의 DTO를 생성해야하는 것이다.
1. Result.java
@Getter
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "result")
public class Result
@XmlElement(name = "msgHeader")
private MsgHeader msgHeader;
@XmlElement(name = "msgBody")
private MsgBody msgBody;
}
가장 최상단의 태그 Result 클래스 이다. 두번째 depth에 존재하는 태그 MsgHeader, MsgBody를 갖고 있다.
2. MsgHeader.java
@Getter
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "msgHeader")
public class MsgHeader{
@XmlElement(name="id")
private String id;
@XmlElement(name="password")
private String password;
}
MsgHeader태그 안에 id, password 값이 들어있는데 이는 xmlElement로 지정해주었다.
3. MsgBody.java
@Getter
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "msgBody")
public class MsgBody {
@XmlElements({
@XmlElement(name = "itemDetail", type = ItemDetail1.class),
@XmlElement(name = "itemDetail", type = ItemDetail2.class)
})
private List<ItemDetail> itemDetail;
}
MsgBody에는 List 형태로 itemDetail 객체를 가지고 있다. 바로 아래에서 확인할 수 있듯이 ItemDetail은 인터페이스이다. Jaxb 라이브러리에서 인터페이스 마샬링, 언마샬링은 지원하지 않기 때문에 위와 같이 @XmlElements로 구현 클래스를 모두 지정해줘야했다.
4. ItemDetail.java
public interface ItemDetail {
}
5. ItemDetail1.java
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ItemDetail1 implements ItemDetail{
@XmlElement(name="data1")
private String data1;
@XmlElement(name="data2")
private String data2;
@XmlElement(name="data3")
private String data3;
@XmlElement(name="data4")
private String data4;
}
ItemDetail1 구현 클래스이다. 이 외에 ItemDetail2 클래스로 있지만 이는 생략하도록 하겠다. 내부에 data1 ~ 4를 xmlElement로 가지고 있다.
6. getResult 메서드 (DTO 생성)
public Result getResult() {
MsgHeader msgHeader = new MsgHeader(
"id",
"password"
);
ItemDetail itemDetail1 = new ItemDetail1(
"data1",
"data2",
"data3",
"data4"
);
ItemDetail itemDetail2 = new ItemDetail1(
"data1",
"data2",
"data3",
"data4"
);
List<ItemDetail> itemDetails = new ArrayList<>();
itemDetails.add(itemDetail1);
itemDetails.add(itemDetail2);
MsgBody msgBody = new MsgBody(itemDetails);
return new Result(msgHeader, msgData);
}
7. JAXB 마샬링
public static String marshalRequest(Result result) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Result.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
marshaller.marshal(result, writer);
return writer.toString();
} catch (Exception e) {
log.debug(e.getMessage(), e);
return null;
}
}
8. 컨트롤러에서 request body에 xml 데이터 추가 후 send
public ResponseEntity<String> sendData() {
Result result = service.getResult();
String xmlRequest = XmlConvertUtil.marshalRequest(result); // XML 요청 본문 생성 (dto to xml)
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<String> requestEntity = new HttpEntity<>(xmlRequest, headers); // 요청 엔티티 생성
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(URL, requestEntity, String.class); // 데이터 전송
return response;
}
9. ResponseDto
@Getter
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "response")
public class ResponseDto {
@XmlElement(name = "resultCode")
private String resultCode;
@XmlElement(name = "resultMsg")
private String resultMsg;
}
10. JAXB 언마샬링
public static ResponseDto unmarshalResponse(String xmlResponse) {
try {
JAXBContext context = JAXBContext.newInstance(ResponseDto.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
// XML 문자열을 ResponseDto 객체로 변환
StringReader reader = new StringReader(xmlResponse);
ResponseDto response = (ResponseDto) unmarshaller.unmarshal(reader);
log.info("resultCode={}", response.getResultCode());
log.info("resultMsg={}", response.getResultMsg());
return response;
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
11. xml에서 ResponseDto 데이터 추출
8. sendData() 내부에서 진행 가능하다.
ResponseEntity<String> response = restTemplate.postForEntity(LOCAL_URL, requestEntity, String.class); // 데이터 전송
이렇게 받아온 response를 활용한다.
ResponseDto responseDto = XmlConvertUtil.unmarshalResponse(response.getBody()); // xml 응답 파싱 (xml to dto)
이렇게 언마샬링 코드를 통해 ResponseDto로 추출 가능하다.
마치며
이렇게 마샬링, 언마샬링이 무엇인지 알아보았다. 그리고 직렬화와의 차이가 무엇인지에 대해서도 간단하게 알아보았고, 실제로 xml to java object, java object to xml 과정을 코드로 알아보았다. 제대로 알지 못했던 개념을 이렇게 알아과는 과정이 생각보다 재밌었다.
잘못된 부분이나 궁금한 점은 댓글로 알려주시면 감사드리겠습니다!
:wq 💟
댓글