RMI(Remote Method Invocation)

RMI는 JDK 1.1에서 처음으로 자바에 도입되어 자바 프로그램 사이에 통신할 수 있는 방법을 제공하였다. RMI 이전에는 CORBA를 사용하거나 소켓 프로그래밍을 해야만 했었다. 그러나 RMI 서비스를 개발하거나 접근하는 일은 복잡하여 개발하기가 불편하며 다음과 같은 일을 수행해야한다.
  • java.rmi.Remote를 상속받은 인터페이스 클래스를 작성한다.
  • UnicastRemoteObject를 상속받으며 위의 인터페이스 클래스를 구현하는 구현클래스를 작성한다.
  • RMI 컴파일러를 사용하여 클라이언트 Stub 클래스와 서버 Skeleton 클래스를 생성시킨다. (rmic –d classname)
  • RMI 레지스트리를 구동시키고 서비스를 레지스트리에 바인딩시킨다.
  • 클라이언트 코드를 이용하여 RMI 서비스를 호출하여 사용한다.
Spring의 빈 형태로 서비스를 개발한 후 Spring에서 제공하는 RmiServiceExporter를 사용하면 RMI 객체처럼 서비스 객체의 인터페이스를 쉽게 원격 서비스로 노출할 수 있는 기능을 제공한다. 즉, 위에서 언급한 RMI 서비스 개발 단계에서 수행했던 일들을 RmiServiceExporter에서 수행한다. 개발자는 RMI 서비스와 관련된 개발 작업을 비즈니스 서비스에 반영할 필요가 없으므로 비즈니스 로직에 집중하여 개발할 수 있다. 단, RMI는 통신을 위해 특정 Port를 사용하므로 방화벽을 통과하기 어렵고, 클라이언트와 서버에서 제공되는 서비스 모두 자바로 작성되어야 한다는 제약사항이 존재한다. 다음은 RMI 기능을 Server와 Client 단에서 어떻게 사용해야 하는지에 대한 사용법이다.

Server Configuration

서버 구현 방식은 Spring에서 제공하는 org.springframework.remoting.rmi.RmiServiceExporter 클래스를 이용하여 손쉽게 일반 Spring Bean으로 작성된 서비스를 RMI Service로 노출시킬 수 있다.
Property Name
Description
Required
Default Value
serviceName 서비스 이름은 서비스를 RMI Registry에 바인딩 하기 위해 사용된다.
Y
N/A
service RMI 서비스로 노출시키고 싶은 Spring Bean의 id를 설정한다.
Y
N/A
serviceInterface RMI 서비스로 노출되는 서비스의 인터페이스 클래스를 패키지정보와 함께 작성한다.
Y
N/A
registryPort RMI 등록(registry)을 위한 port를 오버라이딩하기 위해 사용된다. 작성하지 않은 경우 디폴트로 1099 port가 사용된다.
N
1099

Samples

다음은 RMI 서버 구현 속성 설정에 대한 예제이다. 서비스는 일반 Spring Bean 개발과 동일하며 RmiServiceExporter Bean에서 property 설정 정보를 참조하여 RMI 서비스로 노출시키고 있다.
  • Configuration
  • 다음은 RMI 서비스를 지원하는 RmiServiceExporter의 속성을 정의한 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 RMI ServiceExporter -->
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
    
    	<property name="serviceName" value="MovieService" />
    	<property name="service" ref="anyframe.sample.movie.MovieService" />
    	<property name="serviceInterface" value="anyframe.sample.movie.MovieService" />
    	<!-- defaults to 1099 
    	<property name="registryPort" value="1199" />
    	-->
    </bean>
  • Test case
  • RMI 서비스로 노출시키기 위해 Server를 구동시켜야 하며, 이를 위해서 TestCase 내에서 ClassPathXmlApplicationContext를 이용하여 Spring Container를 구동시키고 있다. 다음은 앞서 정의한 속성 설정 파일들을 기반으로 하여 작성된 TestCase인 RMISpringSupportTest.java 코드의 일부이다.
    public class RMISpringSupportTest extends RemotingSpringTestCase {
    	protected String[] getConfigLocations() {
            try {
                onSetUpServer();
    
            } catch (Exception e) {
                e.printStackTrace();
                fail();
            }
            ...중략...
        }
    
        public void onSetUpServer() throws Exception {
            // server spring configuration files
            context = new ClassPathXmlApplicationContext(
                            "/rmi/server/WEB-INF/context-movie-server.xml");
        }    
    
        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
        }
        
        protected void onTearDown() throws Exception {       
            context = null;
        } 중략...

Client Configuration

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

Samples

다음은 RMI 클라이언트 속성 설정에 대한 예제이다. 클라이언트는 RmiProxyFactoryBean에서 property 설정 정보를 참조하여 RMI 서비스에 접근하고 있다.
  • Configuration
  • 다음은 RMI 서비스에 접근하는 RmiProxyFactoryBean의 속성을 정의한 context-movie-client.xml 의 일부이다.
    <!-- Add RMI Client -->
    <bean id="movieServiceClient" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
      
    	<property name="serviceUrl" value="rmi://localhost:1099/MovieService" />
    	<property name="serviceInterface" value="anyframe.sample.movie.MovieService"/>
    </bean>
  • Test Case
  • 다음은 앞서 정의한 속성 설정 파일들을 기반으로 하여 RMI 서비스에 접근하는 TestCase인 RMISpringSupportTest.java 코드의 일부이다. JUnit TestCase 개발 시에는 AbstractDependencyInjectionSpringContextTests 클래스를 상속받은 RemotingSpringTestCase 내에서 Setter Injection 방식으로 서비스를 호출하며 예는 다음과 같다.
    public class RMISpringSupportTest extends RemotingSpringTestCase {
    
        // ==============================================================
        // ====== TestCase 수행에 필요한 사전 작업 정의 ====================
        // ==============================================================
    
        private MovieService movieServiceClient = null;
      
    
        public void setMovieServiceClient(MovieService movieService)
       {
            this.movieServiceClient = movieService;
        }
    
        protected String[] getConfigLocations() {
    		...중략...
            return new String[] {"classpath*:/rmi/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"));
        }
    }

Resources

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

  • 참고자료