jaxb2marshaller에서 XMLRootElement을 안쓰는 방법이 있나요?

조회수 3506회

jaxb2marshaller에서 XMLRootElement을 사용 안하고 root element를 지정해줄 수 있는 방법이 있나요?

root element를 컨트롤러 단에서 조건에 따라서 다양하게 주고 싶은데

XMLRootElement을 사용하면 VO에 다른 조건을 주지 못하고 한가지만 나와서..

  • (•́ ✖ •̀)
    알 수 없는 사용자

2 답변

  • XML로 Marshaling/Unmashaling 할 때, Root Element를 판단하는 ObjectFactory를 만들면 됩니다.

    ObjectFactory.java

    // ObjectFactory는 @XmlRegistry로 애노테이션을 달아줘야 합니다.
    // 별도로 상속하거나 하는 것은 없고, createXXX 함수들을 제공하는 것이
    // 목적입니다.
    @XmlRegistry
    public class ObjectFactory {
        // XML 스키마상에서 Root 가 가능하도록 정의된 element의
        // QName을 다음과 같이 정의합니다.
        // 네임스페이스는 편의상 ns://namespace로 했고
        // Root 후보인 두 개의 타입 엘리먼트 이름을 지정했습니다.
        private final static QName someRootAltNum1 = 
            new QName("ns://namespace","some-root-alt-A");
        private final static QName someRootAltNum2 = 
            new QName("ns://namespace","some-root-alt-One");
    
        public ObjectFactory() {
        }
    
        // AltNum1이 @XmlType으로 애노테이션한 것입니다.
        // XML 스키마에서 Type으로 정의된 경우에 해당합니다.
        // 즉, @XmlRootElement 로 정의 되지 않은 것입니다.
        public AltNum1Type createAltNum1() {
           return new AltNum1();
        }
    
        // AltNum2는 또다른 @XmlType입니다.
        // 설명은 AltNum1 과 같습니다.
        public AltNum2Type createAltNum2() {
            return new AltNum2();
        }
    
        // 만약 some-root-alt-A라는 이름의 Root 엘리먼트 후보가
        // AltNum1Type으로 지정된 경우에 다음과 같이 씁니다.
        // @XmlElementDecl을 통해서 엘리먼트의 타입 정보를 선언합니다.
        // 함수의 인자는 some-root-alt-A에 지정된 타입에 대응 하는
        // 자바 클래스(@XmlType을 사용한)를 지정합니다.
        // 이 경우 some-root-alt-A는 별도의 자바 클래스를 갖지 않고, 
        // some-root-alt-A는 해당 XML 타입에 대응하는 자바 클래스와 맵핑됩니다.
        // 
        @XmlElementDecl(namespace = "ns://namespace",  name="some-root-alt-A"
        public JAXBElement<AltNum1Type> createSomeRootAltA(AltNum1Type value) {
            return new JAXBElement<AddressType>(someRootAltNum1 ,AltNum1Type.class,null,value);
        }
    
        // 다른 root 후보 엘리먼트도 모두 작성해줘야 합니다.
        ...
    
    
    

    위의 createSomeRootAltA의 설명에 해당하는 부분이 핵심입니다.

    사실 ObjectFactory는 XML 스키마를 먼저 만든 후에 JAXB 컴파일러(xjc)를 사용해서 생성하면 자동으로 생성되는 것입니다.

    그리고 Class와 XML과 대응되는 패키지에 @XmlSchema를 표시해주는 방법과 그냥 사용하는 방법이 있습니다.

    package-info.java

    @XmlSchema(namespace = "ns://namespace", elementFormDefault = XmlNsForm.QUALIFIED)
    package org.example.xml;
    
    import javax.xml.bind.annotation.*;
    

    위와 같이 된 경우 사용할 때는 다음과 같이 그냥 사용하면 됩니다. XML 스키마의 규칙에 위배되지 않는 경우 다 변환 가능합니다.

    Example.java

     public static void main(String[] args) throws Exception {
            // 다음은 org.example.xml 패키지의 package-info.java에
            // @XmlSchema를 애노테이션 한경우 입니다.
            JAXBContext jc = JAXBContext.newInstance("org.example.xml");
            // 만약 하지 않았다면...
            // ObjectFactory 클래스를 직접 주면 됩니다.
            // JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class)
            Unmarshaller unmarshaller = jc.createUnmarshaller();
    
            // Unmarshal 예시
            File xml= new File("src/org/example/xml/example.xml");
            JAXBElement<AltNum1Type> je = 
                (JAXBElement<AltNum1Type>) 
                    unmarshaller.unmarshal(xml);
            // 위 과정에서 ObjectFactory가 관여합니다.
            // 다 되면 이제 Value를 가져오면 됩니다.
            // 같은 타입으로 된 여러개의 엘리먼트가 root가 되어도 상관없습니다.
            AltNum1Type root = je.getValue();
        }
    

    주의할 것은 위 예에서 본것 처럼 캐스팅은 직접해야 하는 데, 잘못 캐스팅하면 캐스팅 에러가 납니다.

    https://dzone.com/articles/jaxb-and-root-elements 문서를 참고하세요. 보시면 일반 @XmlRootElement와 혼용할 수도 있습니다.

    다만, 가급적이면 xjc 를 써보는 것을 추천합니다.

  • @XMLRootElement대신에 @XmlType을 사용하시고 JAXBElement로 싸서 넘기시면 root element를 동적으로 변경해가며 사용할 수 있습니다.

    JAXBElement로 감싸는 방법은 Company클래스가 있다고 가정하면 이런식으로 해서 사용하시면 됩니다.

    JAXBElement a = new JAXBElement(new QName("new-name"), Company.class, company);
    
    • (•́ ✖ •̀)
      알 수 없는 사용자

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)