BackEnd/Java

[JAVA] JAXB(Marshalling, UnMarshalling) 사용하기(Feat. XML 파싱)

Wonol 2023. 8. 16. 13:37
반응형

1. JAXB(Java Architecture for XML Binding)

- JAXB는 Java Object 를 XML 로 직렬화 해주거나, XML 을 Java Object 로 역직렬화 해주는 Java API 입니다.

  • 마샬링(Marshalling) : Java Object -> XML
  • 언마샬링(UnMarshalling) : XML -> Java Object

- 기본적으로 JDK6 ~ 9 버전에는 JAXB 가 내장되어 있어 라이브러리를 따로 추가할 필요가 없습니다.(JDK 11 부터는 Dependency 추가가 필요합니다)

- 최근 Object 는 관련해서는 대부분 JSON(JavaScript Object Notation)을 많이 사용하지만, XML 도 종종 사용되고 있습니다.

- JSON 을 다룰 때 사용되는 Jackson 라이브러리를 통해서도 XML 파싱은 가능하지만 해당 글에서는 JAXB 를 사용해보도록 하겠습니다.

2. JAXB Annotation

  • @XmlRootElement
    - XML 의 Root Element 명을 정의.
    - ex) @XmlRootElement(name = "header")
    - 마샬링 시에는 Element 이름을 정의하고, 언마샬링 시에는 이름에 해당하는 Element 속성 또는 Element 를 저장.
  • @XmlElement
    - XML 의 Element 명을 정의.
    -ex) @XmlElement(name = "body")
    - 마샬링 시에는 Element 이름을 정의하고, 언마샬링 시에는 이름에 해당하는 Element 속성 또는 Element 를 저장.
  • @XmlType
    - XML Schema 이름과 Namespace 를 정의.
    - propOrder 속성을 사용하여 XML 표현 시 요소들의 표현 순서 정의 가능.
    - ex) @XmlType(propOrder = {"header", "body"}
  • @XmlElementWrapper
    - 다른 XML 요소들을 감싸는 역할.
    - List 같은 컬렉션 객체들을 XML 변활할 때 사용.
  • @XmlTransient
    - XML에 포함하지 않은 필드에 사용.
  • @XmlAttribute
    - ID 필드가 요소 대신 속성으로 매핑되도록 정의.

3. JAXB 사용하기

- 예를 들기 위해 아래와 같은 XML 형식의 데이터가 있다고 가정하고 진행하겠습니다.

<?xml version="1.0" encoding="UTF-8"?>
<User>
	<Header>
		<TimeStamp>20230816105312</TimeStamp>
	</Header>
	<Body>
		<UserName>황재원</UserName>
		<UserJob>Developer</UserJob>
		<UserBlog>http://dev-jwblog.tistory.com</UserBlog>
	</Body>
</User>

3-0. JAXB 사용 준비하기

- 위에서 말씀드렸듯이 JDK6 ~ 9 버전에는 라이브러리가 내장되어 있어서 따로 추가할 필요가 없습니다.


JDK 11 을 사용하신다면 아래 Dependency 를 추가가 필요합니다.
//	build.gradle
implementation 'javax.xml.bind:jaxb-api'
runtimeOnly 'com.sun.xml.bind:jaxb-impl:3.0.0'
implementation 'org.glassfish.jaxb:jaxb-runtime'
implementation 'javax.activation:activation:1.1.1'

- 위와 예제와 같은 형태의 XML 이 있다면 동일하게 객체를 생성합니다.

  • UserXml.class
@XmlRootElement(name = "User")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "UserType", propOrder = {
        "header",
        "body"
})
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserXml {

    @XmlElement(name = "Header", required = true, nillable = true)
    private Header header;
    @XmlElement(name = "Body", required = true, nillable = true)
    private Body body;
}
  • Header.class
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "header", propOrder = {
        "timeStamp"
})
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Header {

    @XmlElement(name = "TimeStamp", required = true, nillable = true)
    private String timeStamp;
}
  • Body.class
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "body", propOrder = {
        "userName",
        "userJob",
        "userBlog"
})
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Body {

    @XmlElement(name = "UserName", required = true, nillable = true)
    private String userName;
    @XmlElement(name = "UserJob", required = true, nillable = true)
    private String userJob;
    @XmlElement(name = "UserBlog", required = true, nillable = true)
    private String userBlog;
}

3-1. 마샬링(JavaObject -> XML)


해당 객체를 생성하는 방법은 다양하지만, 해당 글에서는 Builder 를 사용하여 생성합니다.

- 아래는 마샬링 테스트 입니다.

  • Marshalling Test(JavaObject -> XML)
public class UserXmlTest {

    @Test
    void test() {
        Header header = Header.builder()
                .timeStamp("20230816105312")
                .build();

        Body body = Body.builder()
                .userName("황재원")
                .userJob("Developer")
                .userBlog("https://dev-jwblog.tistory.com")
                .build();

        UserXml userXml = UserXml.builder()
                .header(header)
                .body(body)
                .build();

        try {
            //	XML 변환[S]
            //	XML 파싱에 필요한 정보를 생성
            JAXBContext context = JAXBContext.newInstance(UserXml.class);

            //	마샬링에 필요한 객체 생성
            Marshaller marshaller = context.createMarshaller();
            //	마샬링 옵션을 통해 XML 결과를 보기 좋게 출력
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

            StringWriter stringWriter = new StringWriter();
            marshaller.marshal(userXml, stringWriter);
            //	XML 변환[E]

            System.err.println(stringWriter.toString());

        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }

    }
}

- 테스트 결과는 아래와 같습니다.

3-2. 언마샬링(XML -> JavaObject)

- 반대로 위 예제 XML 데이터를 JavaObject 로 변환해보도록 하겠습니다.


테스트를 위해서 XML 파일을 읽어서 변환하도록 하지만, 만약 API 로 하여 String 으로 전달받아도 방법은 동일하고, unmarshaller.unmarshal() 메소드에서 받고자 하는 형태로 변환하시면 됩니다.

- 객체는 위와 동일하고 마샬링 대신 언마샬링을 사용합니다.

  • UnMarshalling Test(XML -> JavaObject)
public class UserXmlTest {

    @Test
    void unMarshalling_test() {

        try {
            //	XML 데이터
            FileInputStream fileInputStream = new FileInputStream("C:\\marshall\\UserXml.xml");

            //	JavaObject 변환[S]
            //	XML 파싱에 필요한 정보를 생성
            JAXBContext context = JAXBContext.newInstance(UserXml.class);

            //	언마샬링에 필요한 객체 생성
            Unmarshaller unmarshaller = context.createUnmarshaller();

            UserXml userXml = (UserXml) unmarshaller.unmarshal(fileInputStream);
            //	JavaObject 변환[E]

            System.err.println(userXml.toString();

        } catch (Exception e) {

        }

    }
}

- 테스트 결과는 아래와 같습니다.

4. 번외

- JAXB API 를 이용하면 간단하게 XML 파싱이 가능합니다.

- 그러나, JaxbContext 를 초기화(생성)하고 마샬러, 언마샬러를 생성하는 부분에서 JaxbContext 를 1회 생성 후 재사용을 권장하고 있습니다.(Thread-Safe)

- 생성 비용이 적지 않기 때문에 마샬/언마샬을 수행될 때마다 매번 생성하는 것보다 한 번 생성하고 재사용하는 편이 좋습니다.

- 다만, 마샬러와 언마샬의 경우에는 스레드에 안전하지 않기 때문에 반대로 수행될 때마다 다시 생성하는 것을 권장하고 있습니다.


참고

- https://velog.io/@huk00j/JAXB-marshal%EB%A7%88%EC%83%AC-unmarshal%EC%96%B8%EB%A7%88%EC%83%AC

- https://tychejin.tistory.com/135

- https://madplay.github.io/post/jaxb-marshal-unmarshal

반응형