JAX-RS 활용한 RESTful 서비스 구현

JAX-RS(JSR-311) Spec. 을 이용하여 RESTful 서비스를 구현하는데, Apache CXF에서 Spec.에 대한 구현체를 제공하여 표준 방식으로 RESTful 서비스를 작성할 수 있게 한다. JAX-RS에서 제공하는 Annotation 설정을 이용한다.

JAX-RS의 특징 을 살펴보면 다음과 같다.
  • Web Service로 노출시킬 서비스 인터페이스 클래스의 각 method별로 JAX-RS에서 제공하는 Annotation을 설정하여 RESTful Web Service를 구현한다.
  • 인터페이스 클래스와 메소드에 @Path Annotation을 설정한다. 클래스의 Path 속성 정보가 RESTful Web Service로 접근하는 상위 path 정보가 된다.
  • 인터페이스 클래스에 @ProduceMime Annotation을 설정한다. ProduceMime 설정을 안한 경우, 클라이언트에서 addRequestHeader("Accept" , "application/xml") 메소드를 호출해주는 부가 작업이 필요하다.
다음은 JAX-RS를 활용하여 RESTful 서비스 구현 시 Server와 Client 단에서 어떻게 사용해야 하는지에 대한 사용법이다.

Server Configuration

Movie Service를 JAX-WS Frontend나 Simple Frontend를 사용하지 않고 JAX-RS를 지원하는 API를 사용하여 RESTful Web Services로 노출시켜보도록 한다.

Samples

다음은 Movie Service의 인터페이스 클래스 정의에 대한 예제이다. 서버 구동을 위해서 Anyframe에서 제공하는 JaxRsServer 클래스를 이용하고 있다.
  • Interface Class
  • 다음은 Movie Service의 인터페이스 클래스를 작성한 MovieService.java 의 일부이다. Annotation 설정에 유의하도록 한다.
    import javax.ws.rs.Path;
    import javax.ws.rs.ProduceMime;
    
    @Path("/movieservice/")
    @ProduceMime("application/xml") public interface MovieService { @GET
    @Path("/movies/") public Movies findMovieListAll() throws Exception; @GET
    @Path("/movies/{movieId}/") public Movie findMovie(@PathParam("movieId") String movieId) throws Exception; @POST
    @Path("/movies/") public Response createMovie(Movie movie) throws Exception; @PUT
    @Path("/movies/") public Response updateMovie(Movie movie) throws Exception; @DELETE
    @Path("/movies/{movieId}/") public Response removeMovie(@PathParam("movieId") String movieId) throws Exception; 중략...
  • Test case
  • 다음은 서버 사이드의 서비스를 Web Services로 노출시키는 서버를 구동하는 코드를 작성한 JaxRsServerFactoryTest.java 의 일부이다. setUp() 메소드 내에서 JaxRsServer 를 생성시킨 후, 서버의 정보로 인터페이스 클래스, 구현 클래스의 인스턴스, Web Services 주소를 설정해준다.
    상위 테스트케이스 클래스인 RemotingTestCase 의 setUp() 메소드에서 Server 의 start() 메소드가 호출되면서 실제로 구동된다.
    public class JaxRsServerFactoryTest extends RemotingTestCase {
    
        // ==============================================================
        // ====== TestCase 수행에 필요한 사전 작업 정의 ====================
        // ==============================================================
        
        public void setUp() throws Exception {
            this.setServer(new JaxRsServer());
            this.getServer().setServerInfo(
                new ServerInfo(MovieService.class, new MovieServiceImpl(),
                    "http://localhost:9002/"));
            super.setUp();
        }
           중략...
    [참고] JaxRsServer
    Anyframe에서 제공하는 JaxRsServer 클래스는 내부적으로 Apache CXF에서 제공하는 JAXRSServerFactoryBean 클래스의 API를 이용하고 있다. 다음은 JaxRsServer.java 의 일부이다.
    public class JaxRsServer implements Server {
        private JAXRSServerFactoryBean svrFactory = null;
        public JaxRsServer() {
            svrFactory = new JAXRSServerFactoryBean();
        }
        public void setServerInfo(ServerInfo serverInfo) {
            svrFactory.setResourceClasses(serverInfo.getInterfaceClass());
            svrFactory.setResourceProvider(serverInfo.getInterfaceClass(),
                new SingletonResourceProvider(serverInfo.getImpleClass()));
            svrFactory.setAddress(serverInfo.getAddress());
        }
        public void start() throws Exception {
            svrFactory.create();
            Server.LOGGER.info("Server ready...");
        }
        public void stop() throws Exception {
            svrFactory.getBus().shutdown(true);
        }
           중략...
    [참고] Spring Configuration XML Server 설정 이용
    JAX-RS를 이용하여 RESTful 서비스를 구현할 때 Spring 설정 파일을 이용하여 서버를 구동시킬 수 있다. 다음은 Spring Configuration XML 예시이다.
    <jaxrs:server id="jaxrsMovieService" address="/">
        <jaxrs:serviceBeans>
          <ref bean="movieService" />
        </jaxrs:serviceBeans>
    </jaxrs:server>
    중략...

Client Configuration

Web Services에 접근하기 위한 클라이언트를 작성한다. HttpClient 혹은 URL 클래스를 이용하여 RESTful WebService에 접근하도록 한다.

Samples

다음은 org.apache.commons.httpclient.HttpClient를 이용하여 RESTful Web Services로 노출된 Movie Service에 접근하는 예제이다.
이때, RESTful WebService의 결과 값이 XML 형태로 리턴되기 때문에 실제 클라이언트 코드에서 JavaBeans 객체로 변경하여 사용해야 하는 작업이 추가된다. XML을 JavaBeans 객체로 변경하는 일을 JAXB를 통해서 수행하고 있다.
  • JAXB 활용
  • JAXB를 활용하여 서비스 결과 XML을 JavaBeans로 변경시키기 위해서는 다음과 같이 각 JavaBeans 객체에 Annotation을 설정하고 jaxb.index 파일을 추가 생성시켜줘야 한다.

    다음은 Movie Service의 인터페이스 클래스에서 파라미터와 리턴 값으로 사용하는 Movie.java 의 일부이다.
    Movie 클래스 정의 시 작성한 @XmlRootElement(name = "Movie") Annotation 설정에 유의하도록 한다. JAXB를 이용하여 서비스 결과 XML을 JavaBeans 객체로 변환 시 이 Annotation 정보를 이용한다.
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlRootElement(name = "Movie")
    public class Movie implements Serializable {
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
            중략...
    
    다음은 Movie Service의 인터페이스 클래스에서 목록조회 시 리턴 값으로 사용하는 Movies.java 의 일부이다. Movies 클래스 정의 시 작성한 @XmlRootElement(name = "Movies") Annotation 설정에 유의하도록 한다. JAXB를 이용하여 서비스 결과 XML을 JavaBeans 객체로 변환 시 이 Annotation 정보를 이용한다. 특히 목록 조회 결과 시 사용되는 리턴 값은 내부 멤버 변수로 Collection 객체를 정의하여 새로운 Movies 객체를 작성함에 유의하도록 한다.
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlRootElement(name = "Movies")
    public class Movies {
        private Collection<Movie> movies;
    
        public Collection<Movie> getMovie() {
            return movies;
        }
    
        public void setMovie(Collection<Movie> movies) {
            this.movies = movies;
        }
            중략...
    
    다음은 JAXB를 사용하기 위해서 필요한 jaxb.index 파일이다. Movie와 Movies 클래스를 정의해 놓는다.
    Movie
    Movies
    
  • Test Case
  • 다음은 HttpClient 클래스를 사용하여 RESTful Web Services로 노출된 Movie Service에 접근하는 클라이언트 코드를 작성한 JaxRsServerFactoryTest.java 의 일부이다.
    testXXX() 메소드 내에서 Get/Post/Put/Delete Method 중 어느 것을 사용할 것인지 정하여 접근하고자 하는 Web Services 주소와 함께 정의하고, HttpClient 객체를 생성하여 위에서 정의한 Method를 실행시킨다. 이때 클라이언트는 서버의 Movie Service 인터페이스 클래스를 이용하지 않고 URL 정보를 이용하여 접근한다.
    RESTful WebService 실행 결과로 받은 XML 데이터를 JavaBeans 객체로 변경하여 메소드 동작이 올바른지 테스트해본다.
    public class JaxRsServerFactoryTest extends RemotingTestCase {
        // ==============================================================
        // ====== TestCase methods ======================================
        // ==============================================================    
        
        /**
         * [Flow #-1] Positive Case : Get method로 Movie Service의 전체 목록 조회 기능을 호출하여
         *            XML data를 리턴받고  JAXB를 사용하여 Movies 객체로 전환하여 사용한다.  
         *                            
         *            @GET, @Path annotation을 이용하여 findMovieListAll method를 
         *            RESTful한 Web Service로 노출하여 Client가 호출가능하도록 한다.
         *            (ex. @GET
         *            @Path("/movies/")
         *            public Movies findMovieListAll() throws Exception;
         *            )
         * @throws Exception
         *         throws exception which is from service                            
         */      
        public void testFindMovieListAll() throws Exception {
            // 1. find movie        
            GetMethod get =
                new GetMethod("http://localhost:9002/movieservice/movies");
            HttpClient httpclient = new HttpClient();
            String response = "";
            try {
                assertEquals(200, httpclient.executeMethod(get));
                response = get.getResponseBodyAsString();
            } catch (Exception e) {
                fail();
            } finally {
                get.releaseConnection();
            }
    
            JAXBContext jaxbContext =
                JAXBContext.newInstance("anyframe.sample.movie.restful.jaxrs");
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            Movies movies =
                (Movies) unmarshaller.unmarshal(new InputSource(new StringReader(
                    response)));
    
            // 2. check the movie information
            assertEquals(2, movies.getMovie().size());
        }
    
        /**
         * [Flow #-3] Positive Case : Get method로 Movie Id가 "001"인 Movie를 조회하는 기능을  
         *            호출하여 XML data를 리턴받고  JAXB를 사용하여 Movie 객체로 전환하여 사용한다.  
         *                            
         *            @GET, @Path annotation을 이용하여 findMovie method를 
         *            RESTful한 Web Service로 노출하여 Client가 호출가능하도록 한다.
         *            (ex. @GET
         *                 @Path("/movies/{movieId}/")
         *                 public Movie findMovie(@PathParam("movieId") String movieId)...
         *            )
         *            이때, @Path 에 작성한 {movieId}와 @PathParam 의 "movieId"를 동일하게 해야 한다.
         * @throws Exception
         *         throws exception which is from service                            
         */    
        public void testFindMovie() throws Exception {
            // 1. find movie
            GetMethod get =
                new GetMethod("http://localhost:9002/movieservice/movies/001");
            HttpClient httpclient = new HttpClient();
            String response = "";
            try {
                assertEquals(200, httpclient.executeMethod(get));
                response = get.getResponseBodyAsString();
            } catch (Exception e) {
                fail();
            } finally {
                get.releaseConnection();
            }
    
            JAXBContext jaxbContext =
                JAXBContext.newInstance("anyframe.sample.movie.restful.jaxrs");
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            Movie movie =
                (Movie) unmarshaller.unmarshal(new InputSource(new StringReader(
                    response)));
    
            // 2. check the movie information
            assertEquals("The Sound Of Music", movie.getTitle());
            assertEquals("Robert Wise", movie.getDirector());
        }
    
        /**
         * [Flow #-4] Positive Case : Post method로 Movie Id가 "003"인 신규 Movie 생성 기능을 호출한다.
         *            @POST, @Path annotation을 이용하여 createMovie method를 
         *            RESTful한 Web Service로 노출하여 Client가 호출가능하도록 한다.
         *            (ex. @POST
         *                 @Path("/movies/")
         *                 public Response createMovie(Movie movie) throws Exception;
         *            )
         * @throws Exception
         *         throws exception which is from service                            
         */         
        public void testCreateMovie() throws Exception {
            // 1. create movie
            String inputFile =
                this.getClass().getClassLoader().getResource(
                    "webservices/restful/jaxrs/create_movie.txt").getFile();
            File input = new File(inputFile);
            PostMethod post =
                new PostMethod("http://localhost:9002/movieservice/movies");
            RequestEntity entity =
                new FileRequestEntity(input, "text/xml; charset=ISO-8859-1");
            post.setRequestEntity(entity);
            HttpClient httpclient = new HttpClient();
            String response = "";
    
            try {
                assertEquals(200, httpclient.executeMethod(post));
                response = post.getResponseBodyAsString();
                System.out.println("create: " + response);
            } catch (Exception e) {
                fail();
            } finally {
                post.releaseConnection();
            }
    
            JAXBContext jaxbContext =
                JAXBContext.newInstance("anyframe.sample.movie.restful.jaxrs");
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            Movie movie =
                (Movie) unmarshaller.unmarshal(new InputSource(new StringReader(
                    response)));
    
            // 2. check the new movie information
            assertEquals("Life Is Beautiful", movie.getTitle());
            assertEquals("Roberto Benigni", movie.getDirector());
        }
            중략...
    

Resources