JAXB Databinding

JAXB는 Java Architecture for XML Binding의 약자로 XML Schema로부터 클래스 데이터를 바인딩하여 XML로부터 객체를 Unmarshalling 하거나, 반대로 객체를 XML로 저장하는 Marshalling을 수행할 수 있도록 해 준다.

JAXB의 특징 을 살펴보면 다음과 같다.
  • Apache CXF 사용 시 디폴트 Databinding 방식이 JAXB이므로, JAXB 사용을 위한 추가 설정이 필요없다.
  • JavaBeans 코드에 @XmlElement와 같은 Annotation을 설정하여 element 명을 변경시킬 수 있다.
  • JavaBeans 코드에 Annotation 설정 없이도 JAXB를 사용할 수 있다.
  • List 형태의 타입은 지원하나 Map 형태의 타입에 대해서는 Databinding을 기본적으로 지원하지 않으므로 Map 형태의 타입을 이용하여 Databinding 하기 위해서는 XmlAdapter 클래스를 추가로 작성해줘야 한다.
  • JAXB Databinding와 Simple frontend를 함께 사용 시 문제가 발생하는 경우가 있기 때문에, JAXB Databinding은 JAX-WS frontend와 함께 사용하도록 한다.
JavaBeans 객체와 XML Schema 간의 매핑 관계 를 보면 다음과 같다.

JAXB의 역할 수행 모습을 그림을 통해 살펴보면 다음과 같다.

다음은 JAXB 기능을 Server와 Client 단에서 어떻게 사용해야 하는지에 대한 사용법이다.

Server Configuration

Movie Service를 JAX-WS Frontend를 사용하여 Web Services로 노출시킬 때 인터페이스 메소드의 파라인터 인자와 리턴 값의 타입을 다양한 타입으로 선언하여 테스트해보도록 한다.

Samples

다음은 Movie Service의 인터페이스 클래스 정의와 Map 타입 사용을 위한 XmlAdapter 클래스 정의에 대한 예제이다. 서버 구동을 위해서 Anyframe에서 제공하는 JaxWsServer 클래스를 이용하고 있다.
  • Interface Class
  • 다음은 Movie Service의 인터페이스 클래스를 작성한 MovieService.java 의 일부이다.
    import javax.jws.WebService;
    
    @WebService
    public interface MovieService {
    
        public List<Movie> findMovieListAll() throws Exception;
    
        @XmlJavaTypeAdapter(StringMovieMapAdapter.class)
        public Map<String, Movie> findMovieMapAll() throws Exception;
    
        public Movie findMovie(String movieId) throws Exception;
    
        public void createMovie(Movie movie) throws Exception;
    
        public void updateMovie(Movie movie) throws Exception;
    
        public void removeMovie(Movie movie) throws Exception;
    
        // ================= method for Type tests
        public int testMovieIntVal(int val);
    
        public long testMovieLongVal(long val);
    
        public short testMovieShortVal(short val);
    
        public float testMovieFloatVal(float val);
    
        public double testMovieDoubleVal(double val);
    
        public BigDecimal testMovieBigDecimalVal(BigDecimal val);
    
        public boolean testMovieBooleanVal(boolean val);
    
        public Character testMovieCharacterVal(Character val);
    
        public char testMovieCharVal(char val);
            중략...
    
  • XmlAdapter Class
  • Movie Service의 인터페이스 메소드 중 리턴 타입이 Map인 경우, XmlAdapter 클래스가 추가 작성되어야 한다. Map 타입을 사용하지 않는 경우 작성할 필요 없다. 다음은 XmlAdapter 클래스를 작성한 StringMovieMapAdapter.java 의 일부이다.
    public class StringMovieMapAdapter extends
            XmlAdapter<StringMovieMap, Map<String, Movie>> {
        public StringMovieMap marshal(Map<String, Movie> v) throws Exception {
            StringMovieMap map = new StringMovieMap();
            for (Map.Entry<String, Movie> e : v.entrySet()) {
                StringMovieMap.StringMovieEntry iue =
                    new StringMovieMap.StringMovieEntry();
                iue.setMovie(e.getValue());
                iue.setMovieId(e.getKey());
                map.getEntries().add(iue);
            }
            return map;
        }
    
        public Map<String, Movie> unmarshal(StringMovieMap v) throws Exception {
            Map<String, Movie> map = new LinkedHashMap<String, Movie>();
            for (StringMovieMap.StringMovieEntry e : v.getEntries()) {
                map.put(e.getMovieId(), e.getMovie());
            }
            return map;
        }
            중략...
    
  • XmlAdapter Class에서 사용하는 부가 클래스
  • 위에서 작성한 XmlAdapter 클래스 내에서 marshalling/unmarshalling 작업 시 사용되는 StringMovieMap 클래스 또한 추가 작성되어야 한다. Map 타입을 사용하지 않는 경우 작성할 필요 없다. 다음은 StringMovieMap 클래스를 작성한 StringMovieMap.java 의 일부이다.
    @XmlType(name = "StringMovieMap")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class StringMovieMap {
        @XmlElement(nillable = false, name = "entry")
        List<StringMovieEntry> entries = new ArrayList<StringMovieEntry>();
    
        public List<StringMovieEntry> getEntries() {
            return entries;
        }
    
        @XmlAccessorType(XmlAccessType.FIELD)
        @XmlType(name = "IdentifiedMovie")
        static class StringMovieEntry {
            // Map keys cannot be null
            @XmlElement(required = true, nillable = false)
            String movieId;
    
            Movie movie;
    
            public void setMovieId(String k) {
                movieId = k;
            }
    
            public String getMovieId() {
                return movieId;
            }
    
            public void setMovie(Movie m) {
                movie = m;
            }
    
            public Movie getMovie() {
                return movie;
            }
        }
            중략...
    
  • Test case
  • 다음은 서버 사이드의 서비스를 Web Services로 노출시키는 서버를 구동하는 코드를 작성한 JaxWsJAXBTest.java 의 일부이다. setUp() 메소드 내에서 JaxWsServer 를 생성시킨 후, 서버의 정보로 인터페이스 클래스, 구현 클래스의 인스턴스, Web Services 주소를 설정해준다.
    상위 테스트케이스 클래스인 RemotingTestCase 의 setUp() 메소드에서 Server 의 start() 메소드가 호출되면서 실제로 구동된다.
    public class JaxWsJAXBTest extends RemotingTestCase {
    
        // ==============================================================
        // ====== TestCase 수행에 필요한 사전 작업 정의 ====================
        // ==============================================================
        
        public void setUp() throws Exception {
            this.setServer(new JaxWsServer());
            this.getServer().setServerInfo(
                new ServerInfo(MovieService.class, new MovieServiceImpl(),
                    "http://localhost:9002/Movie", false, true));
            super.setUp();
        }
           중략...

Client Configuration

Web Services에 접근하기 위한 클라이언트 작성 방식 중 Anyframe에서 제공하는 API인 JaxWsClient 를 사용하면 더욱 간편하고 편리하게 사용할 수 있다.(JaxWsClient 내부적으로 JAX-WS Frontend API 코드를 호출하고 있다.)

Samples

다음은 Anyframe에서 제공하는 JaxWsClient를 이용하여 Web Services로 노출된 Movie Service에 접근하는 예제이다.
  • Test Case
  • 다음은 Anyframe에서 제공하는 JaxWsClient 클래스를 사용하여 Web Services로 노출된 Movie Service에 접근하는 클라이언트 코드를 작성한 JaxWsJAXBTest.java 의 일부이다. testXXX() 메소드 내에서 JaxWsClient 를 생성시킨 후, 클라이언트의 정보로 인터페이스 클래스와 접근하고자 하는 Web Services 주소를 이용하여 Movie Service를 얻어낸다. Movie Service를 얻은 후에는 Movie Service에서 Web Service 메소드로 노출된 메소드를 호출하여 동작이 올바른지 테스트해본다.

    즉, 다양한 데이터 타입에 대해서 Databinding이 올바로 동작하여 파라미터 값과 리턴 값이 의도된 대로 동작하는지 테스트한다.
    public class JaxWsJAXBTest extends RemotingTestCase {
    
        // ==============================================================
        // ====== TestCase methods ======================================
        // ==============================================================
            
        /**
         * [Flow #-1] Positive Case : List 형태로 전체 목록을 조회한다.
         * @throws Exception
         *         throws exception which is from service
         */          
        public void testFindMovieListAll() throws Exception {
            Client client = new JaxWsClient();
            MovieService movieService =
                (MovieService) client.getClient(new ClientInfo(MovieService.class,
                    "http://localhost:9002/Movie", false, true));
    
            // 1. find movie list all
            List<Movie> movieList = movieService.findMovieListAll();
    
            // 2. check the movie list count
            assertEquals(2, movieList.size());
        }
    
        /**
         * [Flow #-2] Positive Case : Map 형태로 전체 목록을 조회한다.
         * @throws Exception
         *         throws exception which is from service
         */      
        public void testFindMovieMapAll() throws Exception {
            Client client = new JaxWsClient();
            MovieService movieService =
                (MovieService) client.getClient(new ClientInfo(MovieService.class,
                    "http://localhost:9002/Movie", false));
    
            // 1. find movie map all
            Map<String, Movie> movieMap = movieService.findMovieMapAll();
    
            // 2. check the movie map count
            assertEquals(2, movieMap.size());
        }
    
        /**
         * [Flow #-3] Positive Case : Movie Id가 "001"인 Movie를 조회한다.
         * @throws Exception
         *         throws exception which is from service
         */     
        public void testFindMovie() throws Exception {
            Client client = new JaxWsClient();
            MovieService movieService =
                (MovieService) client.getClient(new ClientInfo(MovieService.class,
                    "http://localhost:9002/Movie", false));
    
            // 1. find movie
            Movie movie = movieService.findMovie("001");
    
            // 2. check the movie information
            assertEquals("The Sound Of Music", movie.getTitle());
            assertEquals("Robert Wise", movie.getDirector());
        }
            중략...
    
        /**
         * [Flow #-7] Positive Case : Type Test - input, output parameter로 int value를 
         *                            사용하여 호출한다.
         */  
        public void testMovieIntVal() {
            Client client = new JaxWsClient();
            MovieService movieService =
                (MovieService) client.getClient(new ClientInfo(MovieService.class,
                    "http://localhost:9002/Movie", false));
    
            assertEquals(1, movieService.testMovieIntVal(1));
        }
    
        /**
         * [Flow #-8] Positive Case : Type Test - input, output parameter로 long value를 
         *                            사용하여 호출한다.
         */      
        public void testMovieLongVal() {
            Client client = new JaxWsClient();
            MovieService movieService =
                (MovieService) client.getClient(new ClientInfo(MovieService.class,
                    "http://localhost:9002/Movie", false));
    
            assertEquals(1, movieService.testMovieLongVal(1));
        }
            중략...
    

유의 사항

JAXB Databinding 방식을 사용하여 Web Services로 노출된 메소드에 접근하여 사용 시 유의해야 하는 사항이다.

SEI 클래스에서 정의되지 않은 Java Type 클래스가 Runtime시 Databinding되어야 하는 경우

SEI 클래스에 정의된 메소드의 입력값이나 리턴값으로 참조되지 않은 Java Type 클래스의 경우, Runtime 시 Databinding이 일어날 때 다음과 같은 에러 메시지가 나오게 됩니다.

org.apache.cxf.interceptor.Fault: Marshalling Error: class dcc.sample.EmpTestDVO nor any of its super class is known to this context.
아래 방법을 통해서 해결하도록 한다.
  • Interface Class
  • 다음은 테스트 코드로 작성된 EmpTest 인터페이스 클래스의 일부이다.

    즉, SEI(Service Endpoint Interface) 클래스의 메소드에는 List 형태의 결과값을 리턴하는 메소드가 존재하고 해당 List내에 저장된 Java Type 클래스에 대해서는 참조하는 메소드가 없는 경우, 클라이언트가 해당 메소드를 호출할 때 Runtime시에 JAXB Databinding을 시도하려고 하면서 Marshalling Error가 발생하게 된다. 이를 방지하기 위해서 @XmlSeeAlso Annotation 혹은 JDK1.5의 Generic Type을 설정하도록 한다.

    @XmlSeeAlso 사용 예제
    @WebService
    @XmlSeeAlso({EmpTestDVO.class})
    public interface MovieService {
    
    	public List selectEmp(HeaderVo hVO, EmpSVO empSVO);
      중략...
    
    JDK 1.5의 Generic Type 사용 예제
    @WebService
    public interface MovieService {
    
    	public List<EmpTestDVO> selectEmp(HeaderVo hVO, EmpSVO empSVO);
      중략...
    

Resources