Hessian

Hessian과 Burlap은 HTTP를 통해 경량의 원격 서비스를 가능하게 하는 Caucho Technology 가 제공하는 솔루션이다.
  • RMI의 방화벽 문제 해결
  • 메모리와 저장 공간이 제한된 환경에서 사용 적합(애플릿/무선 단말기)
  • 제약 사항 - 자바 표준 직렬화 매커니즘이 아닌 자체 직렬화 매커니즘 사용으로 복합 데이터 모델 불충분
Spring을 사용하지 않는 경우에도 Hessian 서비스를 작성하는 것은 매우 쉽다. 서비스 클래스가 com.caucho.hessian.server.HessianServlet을 확장하도록 하고 노출 대상 메소드의 지시자를 public으로 설정하면 된다. Spring 기반으로 Hessian 서비스를 작성하는 경우 Dependency Injection, Spring AOP 등의 Spring의 기능을 모두 이용할 수 있으므로 Spring Remoting 기능으로 제공하고 있다.
RMI 서비스 작성 시 Spring 설정 파일에 RmiServiceExporter 빈을 설정한 것과 마찬가지로 Hessian 서비스 작성시에는 HessianServiceExporter 빈을 사용한다. Property 설정이 거의 유사하지만 RmiServiceExporter빈에서 설정했던 serviceName, registryPort Property 설정은 지정하지 않는다. Hessian 서비스는 RMI 레지스트리를 갖고 있지 않으므로 서비스 명과 Port 번호 설정이 필요하지 않다.
다음은 Hessian 기능을 Server와 Client 단에서 어떻게 사용해야 하는지에 대한 사용법이다. 다음은 Hessian과 Burlap의 차이점을 정리한 내용이다.

Server Configuration

서버 구현 방식은 일반 서비스 빈 개발 방식과 같으며 HessianServiceExporter 클래스를 이용하여 손쉽게 일반 Spring Bean으로 작성된 서비스를 Hessian Service로 노출시킬 수 있다. 이때 모든 public 메소드는 서비스 메소드로 노출된다.
Property Name
Description
Required
Default Value
service Hessian 서비스로 노출시키고 싶은 Spring Bean의 id를 설정한다.
Y
N/A
serviceInterface Hessian 서비스로 노출되는 서비스의 인터페이스 클래스를 패키지정보와 함께 작성한다.
Y
N/A

Samples

다음은 Hessian 서버 구현 속성 설정에 대한 예제이다. 서비스는 일반 Spring Bean 개발과 동일하며 HessianServiceExporter Bean에서 property 설정 정보를 참조하여 Hessian 서비스로 노출시키고 있다.
  • Configuration
  • 다음은 Hessian 서비스를 지원하는 HessianServiceExporter의 속성을 정의한 context-movie-server.xml 의 일부이다.
    <!-- MovieService -->
    <bean id="anyframe.sample.movie.MovieService"
      class="anyframe.sample.movie.impl.MovieServiceImpl">
    	<property name="movieDAO">
    		<ref bean="movieDAO" />
    	</property>
    </bean>
    
    <bean id="movieDAO" class="anyframe.sample.movie.impl.MovieDAODefaultImpl" />
    	
    <!-- Add Hessian ServiceExporter -->
    <bean id="hessianMovieService" class="org.springframework.remoting.caucho.HessianServiceExporter">
    
    	<property name="service" ref="anyframe.sample.movie.MovieService" />
    	<property name="serviceInterface" value="anyframe.sample.movie.MovieService" />
    </bean>
    여기서 HessianServiceExporter 빈은 Spring MVC의 컨트롤러로 작성되어 있으므로 Spring MVC의 DispatcherServlet을 web.xml 파일에 설정해야 한다. HTTP로 서비스를 제공하기 위해서 웹 어플리케이션으로 서비스를 배포하여 제공하고 있다. 다음은 서버 사이드 웹 어플리케이션의 web.xml 의 일부이다.
    <web-app>
    	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>WEB-INF/context-movie-server.xml</param-value>
    	</context-param>
    
    	<listener>
    		<listener-class>
    			org.springframework.web.context.ContextLoaderListener
    		</listener-class>
    	</listener>
    	<servlet>
    		<servlet-name>remoting</servlet-name>
    		<servlet-class>
    			org.springframework.web.servlet.DispatcherServlet
    		</servlet-class>
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>/WEB-INF/hessian-servlet.xml</param-value>
    		</init-param>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>remoting</servlet-name>
    		<url-pattern>/*</url-pattern>
    	</servlet-mapping>
    </web-app>
    여기서 Spring MVC의 URL Mapping 기능을 사용하여 HTTP 기반의 Hessian 컨트롤러를 호출할 수 있도록 한다. 다음은 서버 사이드 웹 어플리케이션의 hessian-servlet.xml 의 일부이다. /* 패턴의 모든 URL에 대한 요청이 DispatcherServlet으로 전달된 후 urlMapping 정보에 의해 hessianMovieService가 호출되어 Hessian 서비스가 제공된다/
    <bean id="urlMappingUser" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    	<property name="mappings">
    		<props>
    			<prop key="/MovieService">hessianMovieService</prop>
    		</props>
    	</property>
    </bean>
  • Test case
  • Hessian 서비스로 노출시키기 위해 Server를 구동시켜야 하며, 이를 위해서 TestCase 내에서 JettyServer를 이용하여 Spring Container를 구동시키고 있다. 다음은 앞서 정의한 속성 설정 파일들을 기반으로 하여 작성된 TestCase인 HessianSpringSupportTest.java 코드의 일부이다.
    public class HessianSpringSupportTest extends RemotingSpringTestCase {
    	protected String[] getConfigLocations() {
            try {
                onSetUpServer();
    
            } catch (Exception e) {
                e.printStackTrace();
                fail();
            }
            ...중략...
        }
    
        public void onSetUpServer() throws Exception {
            this.setServer(new JettyServer());
            ServerInfo serverInfo = new ServerInfo();
            serverInfo.setPort(9002);
            serverInfo.setWarpath("src/test/resources/hessian/server");
            this.getServer().setServerInfo(serverInfo);
            super.onSetUp();
        }    
    
        public void onSetUp() throws Exception {
            // do nothing because getConfigLocations() method is invoked 
            // before onSetUp() method is called
            // we have to start server before creating client spring context
        } 중략...

Client Configuration

클라이언트는 Spring에서 제공하는 org.springframework.remoting.caucho.HessianProxyFactoryBean 클래스를 사용하여 Hessian Service에 접근할 수 있다.
Property Name
Description
Required
Default Value
serviceUrl Hessian 서비스 접근 URL 정보이다. "http://" + 서버ip + ":" + port 번호 + "/" + 서비스 명 (ex.http://localhost:9002/MovieService)
Y
N/A
serviceInterface Hessian 서비스로 노출되는 서비스의 인터페이스 클래스를 패키지정보와 함께 작성한다.
Y
N/A

Samples

다음은 Hessian 클라이언트 속성 설정에 대한 예제이다. 클라이언트는 HessianProxyFactoryBean에서 property 설정 정보를 참조하여 Hessian 서비스에 접근하고 있다.
  • Configuration
  • 다음은 Hessian 서비스에 접근하는 HessianProxyFactoryBean의 속성을 정의한 context-movie-client.xml 의 일부이다.
    <!-- Add Hessian Client -->
    <bean id="movieServiceClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
      
    	<property name="serviceUrl" value="http://localhost:9002/MovieService" />
    	<property name="serviceInterface" value="anyframe.sample.movie.MovieService"/>
    </bean>
    RMI 서비스에 접근하는 클라이언트와 마찬가지로 serviceInterface Property에는 서비스가 구현하는 인터페이스 클래스를 설정하고. serviceUrl Property에는 서비스 URL을 작성하는데 Hessian은 HTTP 기반으로 제공되므로 HTTP URL을 작성하도록 한다.
  • Test Case
  • 다음은 앞서 정의한 속성 설정 파일들을 기반으로 하여 Hessian 서비스에 접근하는 TestCase인 HessianSpringSupportTest.java 코드의 일부이다. JUnit TestCase 개발 시에는 AbstractDependencyInjectionSpringContextTests 클래스를 상속받은 RemotingSpringTestCase 내에서 Setter Injection 방식으로 서비스를 호출하며 예는 다음과 같다.
    public class HessianSpringSupportTest extends RemotingSpringTestCase {
    
        // ==============================================================
        // ====== TestCase 수행에 필요한 사전 작업 정의 ====================
        // ==============================================================
    
        private MovieService movieServiceClient = null;
      
    
        public void setMovieServiceClient(MovieService movieService)
       {
            this.movieServiceClient = movieService;
        }
    
        protected String[] getConfigLocations() {
    		...중략...
            return new String[] {"classpath*:/hessian/client/context-movie-client.xml" };
    
        }
        // ==============================================================
        // ====== TestCase methods ======================================
        // ==============================================================
        
        /**
         * [Flow #-1] Positive Case : List 형태로 전체 목록을 조회한다.
         * @throws Exception
         *         throws exception which is from service
         */          
        public void testFindMovieListAll() throws Exception {
            // 1. find movie list all
            List<Movie> movieList = movieServiceClient.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 {
            // 1. find movie map all
            Map<String, Movie> movieMap = movieServiceClient.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 {
            // 1. find movie
            Movie movie = movieServiceClient.findMovie("001");
    
    
            // 2. check the movie information
            assertEquals("The Sound Of Music", movie.getTitle());
            assertEquals("Robert Wise", movie.getDirector());
        }
    
        /**
         * [Flow #-4] Positive Case : Movie Id가 "003"인 신규 Movie를 생성한다.
         * @throws Exception
         *         throws exception which is from service
         */        
        public void testCreateMovie() throws Exception {
            // 1. check the existing movie list count
            assertEquals(2, movieServiceClient.findMovieListAll().size());
    
            // 2. create new movie
            movieServiceClient.createMovie(new Movie("003", "Life Is Beautiful", "Roberto Benigni"));
    
    
            // 3. check the new movie list count
            assertEquals(3, movieServiceClient.findMovieListAll().size());
    
            // 4. check the new movie information
            Movie movie = movieServiceClient.findMovie("003");
            assertEquals("Life Is Beautiful", movie.getTitle());
            assertEquals("Roberto Benigni", movie.getDirector());
        }
    
        /**
         * [Flow #-5] Positive Case : Movie Id가 "002"인 기존 Movie 정보를 변경한다.
         * @throws Exception
         *         throws exception which is from service
         */        
        public void testUpdateMovie() throws Exception {
            // 1. update movie
           movieServiceClient.updateMovie(new Movie("002", "Life Is Wonderful", "Roberto"));
    
    
            // 2. find updated movie
            Movie movie = movieServiceClient.findMovie("002");
    
            // 3. check the movie information
            assertEquals("Life Is Wonderful", movie.getTitle());
            assertEquals("Roberto", movie.getDirector());
        }
    
        /**
         * [Flow #-6] Positive Case : Movie Id가 "002"인 기존 Movie를 삭제한다.
         * @throws Exception
         *         throws exception which is from service
         */        
        public void testRemoveMovie() throws Exception {
            // 1. set movie id to remove
            Movie movie = new Movie();
            movie.setMovieId("002");
    
            // 2. remove the movie
            movieServiceClient.removeMovie(movie);
    
    
            // 3. check the removed movie info
            assertNull(movieServiceClient.findMovie("002"));
        }
    }

Hessian과 Burlap의 차이점

  • Hessian의 경우 RMI와 같이 클라이언트와 원격 시스템의 서비스 간 통신을 할때 바이너리 데이터를 사용한다.
  • Burlap의 경우 XML 기반으로 통신한다. Hessian의 바이너리 데이터에 비해 Human-readable하다. 그러나 SOAP 메시지 기반의 Remoting과 달리 Burlap의 XML 메시지는 WSDL이나 IDL과 같은 별도의 Definition Language가 없는 간단한 구조로 구성되어 있다.
  • 바이너리 데이터를 사용하는 Hessian이 네트워크 환경에서의 데이터 전송 면에서 더 효율적이다.
  • 그러나 메시지 가독성이 중요한 경우 혹은 Hessian 구현이 존재하지 않는 타 Language와 통신해야 하는 경우에는 Burlap을 사용해야 한다.

Resources

  • 다운로드
  • 샘플 테스트 코드를 포함하고 있는 anyframe-remotingtest-src.zip 파일을 다운받은 후, 테스트 환경 설정 을 참조하여 위에서 제시한 예제 코드를 실행해 볼 수 있다. 이때 해당 프로젝트 내의 src/test/java 소스폴더 하위의 anyframe.core.remoting.hessian.HessianSpringSupportTest 클래스를 JUnit Test Framework을 이용하여 수행시키도록 한다.
    Name
    Download
    anyframe-remotingtest-src.zip
    Download

  • 참고자료