스프링 Unmarshaller 사용시 @XmlRootElement 같은 name 속성이 2개 이상 있을때 발생하는 언마샬링 오류
웹 프로그래밍/Spring 2015. 10. 7. 15:45API 통신을 할 때, 결과값 데이터형식이 XML일 경우 스프링의 Jaxb2Marshaller를 주로 사용하였다.
applicationContext에 빈을 설정하고, org.springframework.oxm패키지내에 있는 Unmarshaller 인터페이스에 해당 빈을 주입하여 unmarshal() 메소드를 통해 언마샬링을 하였다.
문제는 언마샬링을 할 때 대상이 되는 JAXB 모델중 @XmlRootElement에 name 속성이 같은 경우 Unmarshaller에서 어떤 모델로 인식해야 되는지 몰라 오류가 발생하는 것이였다.
소스는 다음과 같다.
[applicationContext.xml]
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="packagesToScan"> <list> <value>kr.co.pshcode.user.jaxb</value> </list> </property> </bean>
[UserBOImpl.java]
import org.springframework.oxm.Unmarshaller; @Slf4j @Service public class UserBOImpl implements UserBO { @Autowired private Unmarshaller unmarshaller; private String getUser(String userId) { User user = null; try { user = (User)unmarshaller.unmarshal(new StreamSource(makeUserApiUrl(userId, "PERSONAL"))); } catch (Exception e) { log.error("user api error : {}\n", userId, e); } return user; } private String getGroupUser(String groupUserId) { GroupUser groupUser = null; try { groupUser = (GroupUser)unmarshaller.unmarshal(new StreamSource(makeUserApiUrl(groupUserId, "GROUP"))); } catch (Exception e) { log.error("group user api error : {}\n", groupUserId, e); } return groupUser; } }
[서비스API XML]
<!-- 일반유저 --> <USER> <COMMON></COMMON> <DETAIL> <ID></ID> <NAME></NAME> <EMAIL></EMAIL> </DETAIL> </USER> <!-- 그룹유저 --> <USER> <COMMON></COMMON> <DETAIL> <GROUP_ID></GROUP_ID> <GROUP_NAME></GROUP_NAME> <GROUP_EMAIL></GROUP_EMAIL> </DETAIL> </USER>
[User.java, GroupUser.java]
@Data @XmlRootElement(name = "USER") @XmlAccessorType(XmlAccessType.FIELD) public class User { @XmlElement(name = "COMMON") private UserCommon common; @XmlElement(name = "DETAIL") private UserDetail detail; } @Data @XmlRootElement(name = "USER") @XmlAccessorType(XmlAccessType.FIELD) public class GroupUser { @XmlElement(name = "COMMON") private GroupUserCommon common; @XmlElement(name = "DETAIL") private GroupUserDetail detail; }
사용자정보를 제공하는 API측에서는 XML의 루트를 <USER>로 일반유저, 그룹유저별로 <COMMON>과 <DETAIL>에 다른 엘리먼트명으로 각각 정보를 제공해주고 있다.
(비슷하지만 다른 느낌? 그런 느낌적인 느낌..)
이럴경우, Unmarshaller에서 정확한 JAXB 모델을 판단할 수 없기 때문에 언마샬링 오류가 발생하게 되는 것이다.
해결을 할 수 있는 간단한 방법으로는 2가지 케이스를 모두 언마샬링 할 수 있는 통합 모델을 만드는 것이다.
UserCommon + GroupUserCommon 합치기
UserDetail + GroupUserDetail 합치기
하지만 이렇게 처리할 경우 데이터를 객체지향적으로 관리할 수 없다.
파싱할 XML 정보가 늘어날때마다 하나의 모델에 계속 추가를 하게 되면 결국 어떤 API에서만 활용되는 정보인지 구분이 안가게 된다. 그래서 이 방법은 Pass!
다른 방법은 Jaxb2Marshaller를 상속받아서 Class를 선택적으로 넘겨받을 수 있는 메소드를 만들어 기능을 확장하는 것이다. (unmarshal() 메소드 오버로딩)
Castor나 Jibx 같은 다른 OXM은 사용 안해봐서 설명하는 방법과 비슷한 방법이 있는지는 잘 모르겠다. ㅎㅎ
암튼 개선한 소스는 다음과 같다.
[개선된 CustomJaxb2Marshaller.java]
public class CustomJaxb2Marshaller extends Jaxb2Marshaller { public Object unmarshal(Source source, Class<?> clazz) throws JAXBException { Unmarshaller unmarshaller = createUnmarshaller(); return unmarshaller.unmarshal(source, clazz).getValue(); } }
Jaxb2Marshaller 클래스에 존재하는 createUnmarshaller() 메소드를 활용해서 javax.xml.bind 패키지의 Unmarshaller를 획득한 후, unmarshal(source, clazz) 메소드를 활용해서 선택적으로 파싱할 JAXB 모델 Class를 넘겨주어 언마샬링을 하는 것이다.
import kr.co.pshcode.oxm.CustomJaxb2Marshaller; @Slf4j @Service public class UserBOImpl implements UserBO { @Autowired private CustomJaxb2Marshaller marshaller; private String getUser(String userId) { User user = null; try { user = (User)marshaller.unmarshal(new StreamSource(makeUserApiUrl(userId, "PERSONAL")), User.class); } catch (Exception e) { log.error("user api error : {}\n", userId, e); } return user; } private String getGroupUser(String groupUserId) { GroupUser groupUser = null; try { groupUser = (GroupUser)marshaller.unmarshal(new StreamSource(makeUserApiUrl(groupUserId, "GROUP")), GroupUser.class); } catch (Exception e) { log.error("group user api error : {}\n", groupUserId, e); } return groupUser; } }
이렇게 처리할 경우, 기존의 Jaxb2Marshaller를 활용하면서 @XmlRootElement에 name속성이 같을 경우도 커버할 수 있어 문제를 해결할 수 있다. ^^;
'웹 프로그래밍 > Spring' 카테고리의 다른 글
스프링 동적리스트 바인딩시 256개 초과하여 IndexOutOfBoundsException오류 해결 (0) | 2015.08.03 |
---|