MTOM Databinding

MTOM은 Message Transmission Optimization Mechanism의 약자로 SOAP 메시지 통신 최적화 메커니즘이다. 웹 서비스를 통해 바이너리 데이터를 효과적으로 편리하게 전송할 수 있도록 하는 표준이다. JAXB Databinding 혹은 Aegis Databinding과 함께 사용될 수 있다.
서비스를 통해 바이너리 데이터를 효과적으로 편리하게 전송할 수 있도록 하는 표준으로 XML 문서의 일부가 아닌 첨부파일(Attachment) 형태로 생성되어 전송되며 Schema Type 혹은 자바 코드에 Annotation으로 설정한다.
Schema Type에 xmime:expectedContentTypes="application/octet-stream" 을 추가 설정하면 base64Binary element를 위한 byte[] array 형태를 생성하지 않고 데이터를 스트림하는데 쓰이는 DataHandler를 생성한다

MTOM의 특징을 살펴보면 다음과 같다.
  • Apache CXF 사용 시 디폴트로 MTOM 기능이 동작하지 않으므로, MTOM 사용을 위한 추가 설정이 필요하다.
  • XML 문서의 일부가 아닌 첨부파일(Attachment) 형태로 생성되어 전송한다.
  • 바이너리 데이터를 나타내는 JavaBeans의 attribute를 javax.activation.DataHandler type으로 정의하고, @XmlMimeType("application/octet-stream") Annotation 설정을 추가해주도록 한다.

다음은 MTOM 기능을 Server와 Client 단에서 어떻게 사용해야 하는지에 대한 사용법이다. 예제는 JAX-WS Frontend, JAXB, MTOM을 함께 사용하는 것으로 구성되어 있다.

Server Configuration

Movie Service를 JAX-WS Frontend를 사용하여 Web Services로 노출시키는 예제로, MTOM을 테스트하기 위해서 추가 작업한 부분을 중심으로 살펴본다.
또한 MTOM을 사용하지 않고 기존에 XML 문서의 일부로 바이너리 데이터를 전송하는 경우와 MTOM을 이용하여 XML 문서 일부가 아닌 첨부파일 형태로 전송하는 경우에 대해서 바이너리 데이터의 전송과 수신이 올바르게 동작하는지 알아본다.

Samples

다음은 Movie Service의 인터페이스 메소드를 통해 클라이언트와 서버 간 주고받는 데이터인 Movie 클래스 정의에 대한 예제이다. 서버 구동을 위해서 Anyframe에서 제공하는 JaxWsServer 클래스를 이용하고 있다.
  • JavaBeans Class
  • 다음은 Movie Service의 인터페이스 메소드를 통해 클라이언트와 서버 간 주고받는 데이터인 Movie 클래스를 작성한 Movie.java의 일부이다.
    public class Movie implements Serializable {
        private String movieId = "";
        private String title = "";
        private String director = "";
    	
        <!-- MTOM을 사용하지 않고 기존에 XML 문서의 일부로 바이너리 데이터를 전송하는 경우 -->
        private byte[] posterImgByteArray = null;
    
        <!-- MTOM을 이용하여 XML 문서 일부가 아닌 첨부파일 형태로 바이너리 데이터를 전송하는 경우 -->
        @XmlMimeType("application/octet-stream")
        private DataHandler posterImgDataHandler = null;
    
        public byte[] getPosterImgByteArray() {
            return posterImgByteArray;
        }
    
        public void setPosterImgByteArray(byte[] posterImgByteArray) {
            this.posterImgByteArray = posterImgByteArray;
        }
    
        public DataHandler getPosterImgMTOM() {
            return posterImgDataHandler;
        }
    
        public void setPosterImgDataHandler(DataHandler posterImgDataHandler) {
            this.posterImgDataHandler = posterImgDataHandler;
        }
            중략...
    
  • Test case
  • 다음은 서버 사이드의 서비스를 Web Services로 노출시키는 서버를 구동하는 코드를 작성한 JaxWsMTOMTest.java의 일부이다. setUp() 메소드 내에서 JaxWsServer를 생성시킨 후, 서버의 정보로 인터페이스 클래스, 구현 클래스의 인스턴스, Web Services 주소를 설정해준다.
    상위 테스트케이스 클래스인 RemotingTestCase의 setUp() 메소드에서 Server의 start() 메소드가 호출되면서 실제로 구동된다.
    여기서 ServerInfo 클래스 생성 시 생성자 메소드의 다섯 번째 파라미터(boolean useMTOMBinding)값을 true로 설정하고 있음에 주의하도록 한다. MTOM Databinding 기능을 사용하고자 한다면 true로 반드시 설정해야 한다.
    public class JaxWsMTOMTest 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();
        }
           중략...
    ServerInfo 클래스의 useMTOMBinding 값을 true로 설정하게 되면, 내부적으로 JaxWsServerFactoryBean 클래스의 API를 호출하여 MTOM Databinding 사용을 가능하게 해준다. 다음은 JaxWsServer.java의 일부 코드 부분이다. 참고하도록 한다.
    public class JaxWsServer implements Server {
    
        private JaxWsServerFactoryBean svrFactory = null;
        public JaxWsServer() {
            svrFactory = new JaxWsServerFactoryBean();
        }
    
        public void setServerInfo(ServerInfo serverInfo) {
            svrFactory.setServiceClass(serverInfo.getInterfaceClass());
            svrFactory.setAddress(serverInfo.getAddress());
            svrFactory.setServiceBean(serverInfo.getImpleClass());
            if (serverInfo.isUseAegisBinding())
                svrFactory.getServiceFactory().setDataBinding(
                    new AegisDatabinding());
            if (serverInfo.isUseMTOMBinding()) {
                Map<String, Object> props = new HashMap<String, Object>();
                props.put(Message.MTOM_ENABLED, "true");
                svrFactory.setProperties(props);
            }
            중략...
    [Optional] Spring Configuration XML Server 설정 이용(JAX-WS Frontend)
    JaxWsMTOMTest 에서는 JAX-WS Frontend API 코드를 사용하고 있으나, Spring Configuration XML 설정을 통해 MTOM Databinding 사용을 정의할 수 있다.
    <!-- JAX-WS Frontend to expose MovieService with MTOM Databinding -->
    <jaxws:endpoint id="movieService" implementor="anyframe.sample.movie.jaxws.impl.MovieServiceImpl" 
                    address="http://localhost:9002/Movie">
         <jaxws:properties>
            <entry key="mtom-enabled" value="true"/>
         </jaxws:properties>
    </jaxws:endpoint>
    중략...
    [참고] Spring Configuration XML Server 설정 이용(Simple Frontend)
    Simple Frontend를 사용하는 경우에도 마찬가지로 Spring Configuration XML 설정을 통해 MTOM Databinding 사용을 정의할 수 있다.
    <!-- Simple Frontend to expose MovieService with MTOM Databinding -->
    <simple:server id="movieService" serviceBean="#anyframe.sample.movie.MovieService" 
        serviceClass="anyframe.sample.movie.MovieService" address="/Movie">
        <simple:dataBinding>
          		<bean class="org.apache.cxf.aegis.databinding.AegisDatabinding" />
       	</simple:dataBinding>
        <simple:properties>
          <entry key="mtom-enabled" value="true"/>
        </simple:properties>   	
    </simple:server>
    중략...

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에 접근하는 클라이언트 코드를 작성한 JaxWsMTOMTest.java의 일부이다. testXXX() 메소드 내에서 JaxWsClient를 생성시킨 후, 클라이언트의 정보로 인터페이스 클래스와 접근하고자 하는 Web Services 주소를 이용하여 Movie Service를 얻어낸다. Movie Service를 얻은 후에는 Movie Service에서 Web Service 메소드로 노출된 메소드를 호출하여 동작이 올바른지 테스트해본다.

    MTOM을 이용하여 XML 문서 일부가 아닌 첨부파일 형태로 바이너리 데이터를 전송하는 경우에 MTOM Databinding 기능이 올바로 동작하여 Movie의 poster image 바이너리 파일을 조회하고 수정이 되는지 테스트한다.

    여기서 ClientInfo 클래스 생성 시 생성자 메소드의 네번째 파라미터(boolean useMTOMBinding)값을 true로 설정하고 있음에 주의하도록 한다. MTOM Databinding 기능을 사용하고자 한다면 true로 반드시 설정해야 한다.
    public class JaxWsMTOMTest extends RemotingTestCase {
        // ==============================================================
        // ====== TestCase methods ======================================
        // ==============================================================
        /**
         * [Flow #-7] Positive Case : MTOM을 적용하지 않고 Movie의 Poster Image를 변경한다.
         * @throws Exception
         *         throws exception which is from service
         */ 
        public void testMoviePosterImgByteArrayNotMTOM() throws Exception {
            Client client = new JaxWsClient();
            MovieService movieService =
              (MovieService)client.getClient(new ClientInfo(MovieService.class,
                            "http://localhost:9002/Movie", false, false));
    
            // 1. update movie poster image
            Movie movie = movieService.findMovie("001");
            URL fileURL =
                this.getClass().getClassLoader().getResource(
                    "webservices/databinding/mtom/soundofmusic.jpg");
            File aFile = new File(new URI(fileURL.toString()));
            long imgSize = aFile.length();
            System.out.println("PosterImg Size using ByteArray is" + imgSize);
            byte[] posterImg = new byte[(int) imgSize];
            InputStream in = fileURL.openStream();
            in.read(posterImg);
    
            movie.setPosterImgByteArray(posterImg);
            movie.setPosterImgDataHandler(null);
            movieService.updateMovie(movie);
    
            // 2. find updated movie
            Movie updatedMovie = movieService.findMovie("001");
    
            // 3. check the movie information
            assertEquals("The Sound Of Music", updatedMovie.getTitle());
            assertEquals("Robert Wise", updatedMovie.getDirector());
            assertEquals((int)imgSize, updatedMovie.getPosterImgByteArray().length);
        }
    
        /**
         * [Flow #-8] Positive Case : MTOM을 적용하여 Movie의 Poster Image를 변경한다.
         * @throws Exception
         *         throws exception which is from service
         */     
        public void testMoviePosterImgDataHandlerMTOM() throws Exception {
            Client client = new JaxWsClient();
            MovieService movieService =
                (MovieService)client.getClient(new ClientInfo(MovieService.class,
                 "http://localhost:9002/Movie", false, true));
    
            // 1. update movie poster image
            Movie movie = movieService.findMovie("001");
            URL fileURL =
                this.getClass().getClassLoader().getResource(
                    "webservices/databinding/mtom/soundofmusic.jpg");
            DataHandler posterImg = new DataHandler(fileURL);
            int imgSize = posterImg.getInputStream().available();
            System.out.println("PosterImg Size using DataHandler is " + imgSize);
    
            movie.setPosterImgDataHandler(posterImg);
            movie.setPosterImgByteArray(null);
            movieService.updateMovie(movie);
    
            // 2. find updated movie
            Movie updatedMovie = movieService.findMovie("001");
    
            // 3. check the movie information
            assertEquals("The Sound Of Music", updatedMovie.getTitle());
            assertEquals("Robert Wise", updatedMovie.getDirector());
            assertEquals(imgSize, updatedMovie.getPosterImgMTOM().getInputStream()
                .available());
        }
            중략...
    
    ClientInfo 클래스의 useMTOMBinding 값을 true로 설정하게 되면, 내부적으로 JaxWsProxyFactoryBean 클래스의 API를 호출하여 MTOM Databinding 사용을 가능하게 해준다. 다음은 JaxWsClient.java의 일부 코드 부분이다. 참고하도록 한다.
    public class JaxWsClient implements Client {
    
        JaxWsProxyFactoryBean factory = null;
        public JaxWsClient() {
            factory = new JaxWsProxyFactoryBean();
        }
    
        public Object getClient(ClientInfo clientInfo) {
            factory.setServiceClass(clientInfo.getInterfaceClass());
            factory.setAddress(clientInfo.getAddress());
            if (clientInfo.isUseAegisBinding())
                factory.getServiceFactory().setDataBinding(new AegisDatabinding());
            if (clientInfo.isUseMTOMBinding()) {
                Map<String, Object> props = new HashMap<String, Object>();
                 props.put(Message.MTOM_ENABLED, "true");
                factory.setProperties(props);
            }
            return factory.create();
        }
            중략...
    [Optional] Spring Configuration XML Client 설정 이용(JAX-WS Frontend)
    JaxWsMTOMTest 에서는 JAX-WS Frontend API 코드를 사용하고 있으나, Spring Configuration XML 설정을 통해 MTOM Databinding 사용을 정의할 수 있다.
    <!-- JAX-WS Frontend to access MovieService with MTOM Databinding -->
    <jaxws:client id="movieService" serviceClass="anyframe.sample.movie.jaxws.MovieService" 
         address="http://localhost:9002/Movie">
         <jaxws:properties>
            <entry key="mtom-enabled" value="true"/>
         </jaxws:properties>
    </jaxws:client>
    중략...
    [참고] Spring Configuration XML Server 설정 이용(Simple Frontend)
    Simple Frontend를 사용하는 경우에도 마찬가지로 Spring Configuration XML 설정을 통해 MTOM Databinding 사용을 정의할 수 있다.
    <!-- Simple Frontend to access MovieService with MTOM Databinding -->
    <simple:client id="movieService" serviceClass="anyframe.sample.movie.MovieService" 
        address="http://localhost:9002/Movie">
    	<simple:dataBinding>
    		<bean class="org.apache.cxf.aegis.databinding.AegisDatabinding" />
    	</simple:dataBinding>
        <simple:properties>
          <entry key="mtom-enabled" value="true"/>
        </simple:properties>
    </simple:client>
    중략...

참고 - MTOM에 관련된 내용

  • Web Services 성능 향상을 위해 W3C에서 제시한 스펙
    • 2005년 1월 W3C(World Wide Web Consortium)는 다음과 같이 3가지의 Web Services 스펙을 제시하였다. 이들은 SOAP 1.2 메시지를 참조하거나 포함하는 바이너리 데이터를 효율적으로 패키징하거나 송신하는 방법을 제공한다.
    • XOP (XML-binary Optimized Packaging), MTOM (SOAP Message Transmission Optimization Mechanism), RRSHB (Resource Representation SOAP Header Block)

  • 바이너리 데이터 전송
    • XML 문서의 일부가 바이너리 데이터인 경우, base64로 인코딩 되어야 하는데 이는 CPU 점유 시간을 증가시키고 Payload 사이즈를 증가시키는 문제가 있다. Web Services의 특징 중 하나는 전송되는 메시지가 XML로 구성되어 이기종간의 통신을 원활하게 한 것이다. 이는 XML이 일종의 텍스트 문서이므로 이기종 사이에서 이식성이 좋기 때문이다. 하지만 텍스트인 XML 문서에 바이너리 데이터를 추가하기 위해서는 base64 방식으로 인코딩해야 하며 이러한 인코딩 과정에서 바이너리 데이터의 용량은 50%이상 증가하고, 인코딩과 디코딩을 처리하기 위해서 발생하는 CPU의 부하도 5~10% 정도 추가된다는 통계가 있다. 즉, 바이너리 데이터를 전송하는데 있어 Web Services를 이용하게 되면 성능이 저하되는 문제가 발생하게 된다.
    • MTOM을 사용하면 XML 문서의 일부가 아닌 첨부파일(SOAP-Attachment) 형태로 생성되어 전송되어지므로 위와 같은 문제를 해소시킬 수 있다.
    • Schema Type에 xmime:expectedContentTypes="application/octet-stream" 을 추가 설정하면 base64Binary element를 위한 byte[] array 형태를 생성하지 않고 데이터를 스트림하는데 쓰이는 DataHandler를 생성한다.

  • 대용량 바이너리 데이터의 송신을 위한 표준적인 수단과 방법 제공
    • XOP와 MTOM을 이용하여 보다 효율적인 SOAP 메시지의 직렬화와 송신이 가능해진다.
    • XOP는 바이너리 데이터를 그대로 통신 패킷에 포함되도록 하는 표준적인 수단을 제공함으로써 작은 데이터 저장공간이나 좁은 데이터 통신 대역에서도 효율적으로 동작하게 하며 XML Information Set(InfoSet)을 이용하여 다양한 방법으로 XML 문서를 직렬화시킨다.
    • XOP를 구현하는 MTOM은 XOP가 제공하는 기능을 활용하여 SOAP 통신을 처리한다. SOAP 바인딩과 송신을 최적화하는 전송 메커니즘을 정의하여 필요한 통신 대역을 축소하고 대용량 바이너리 데이터의 인코딩과 디코딩에 필요한 시간을 단축한다. 또한 다양한 바이너리 데이터 송신 시 MIME 첨부 형식으로 SOAP 통신을 한다.
    • RRSHB(Resource Representation SOAP Header Block)는 SOAP 메시지 수신자가 원격에 위치한 자원의 로컬 캐시에 접근하게 한다. 메시지 수신자는 URI에 의해 식별된 파일과 SOAP 통신으로 함께 보내진 데이터에 대한 로컬 캐시 중 하나를 선택하여 이용할 수 있다. MTOM과 함께 사용됨으로써 메시지 수신자는 원격에 위치한 데이터에 대한 로컬 캐시가 이미 존재하고 있다면 요청 처리 속도는 현격하게 향상될 것이다.

Resources