嵌入式RESTEasy

RESTEasy除了能够运行于标准Web服务器(如Tomcat),或是与J2EE容器(如JBoss AS)整合之外,还可以嵌入式的方式运行,本文通过一些代码实例来对这些功能和RESTEasy中相关的classes进行说明。

TJWS
首先,我们需要理解,RESTEasy使用TJWS作为嵌入式RESTFul WebService服务的容器。TJWS是一个开源的Servlet容器,它相当轻便小巧,占用资源少,启动速度快,因此做为嵌入式容器非常适合。在RESTEasy中,已经自包含了一个tjws服务器,如果签出RESTEasy的代码则可以看到它位于代码中的tjws目录:

liweinan@smart:~/projs/resteasy/jaxrs$ ls -l | grep tjws
drwxr-xr-x   7 liweinan  staff    238 Sep  5 22:37 tjws


基于TJWS这个容器,RESTEasy为我们提供了TJWSEmbeddedJaxrsServer,开发人员可以很方便地使用它来启动一个内嵌于代码之中的JAX-RS服务。下面我们动手实际做一个例子试试看:

HelloWorldService
首先我们来创建一个最简单的RESTFul WebService:

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/helloworld")
public interface HelloWorldService {

    @GET
    @Produces("text/plain")
    public String printHelloWorld();
}


以下是它的实现类:

public class HelloWorldServiceImpl implements HelloWorldService {
    public static final String HELLO_WORLD = "Hello, world!";

    public String printHelloWorld() {
        return HELLO_WORLD;
    }
}


很简单的一个WebService,不需做太多说明。接下来的重点是如何使用TJWSEmbeddedJaxrsServer来将这个WebService启动起来:

TJWSEmbeddedJaxrsServer
我们可以通过JUnit单元测试的形式来实现这个例子:

import org.jboss.resteasy.plugins.server.tjws.TJWSEmbeddedJaxrsServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import static org.junit.Assert.assertEquals;

public class TestTJWSEmbeddedJAXRSServer {

    private static TJWSEmbeddedJaxrsServer tjws;

    @Before
    public void setUp() {
        tjws = new TJWSEmbeddedJaxrsServer();
        tjws.setPort(8081);
        tjws.getDeployment().getActualResourceClasses().add(HelloWorldServiceImpl.class);
        tjws.start();
    }

    @After
    public void tearDown() {
        tjws.stop();
    }

    @Test
    public void testEmbeddedJAXRSServer() throws IOException {
        URL url = new URL("http://127.0.0.1:8081/helloworld");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Content-Type", "text/plain");
        connection.setDoOutput(true);
        connection.setInstanceFollowRedirects(false);
        connection.setConnectTimeout(1000);

        BufferedReader ir = new BufferedReader(new InputStreamReader(connection.getInputStream()));

        assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
        assertEquals("Hello, world!", ir.readLine());
        ir.close();
        connection.disconnect();

    }

}


从上面的代码中,我们可以看到,在setUp()方法中,我们将HelloWorldServiceImpl这个WebService添加进了TJWSEmbeddedJaxrsServer当中并启动了这个server,同时设定的服务端口为8081。值得说明的是这行代码:

tjws.getDeployment().getActualResourceClasses().add(HelloWorldServiceImpl.class);


我们是以perRequestResource的形式将HelloWorldServiceImpl服务进行注册,这一点从org.jboss.resteasy.spi.ResteasyDeployment的源代码中可以看出来:

...
registry.addPerRequestResource(actualResourceClass);
...


因此,我们的HelloWorldServiceImpl,将在每次客户端请求服务时被RESTEasy进行实例化,服务处理完成后销毁。如果我们想以singleton方式来添加我们的WebService,可以使用getResourceFactories()方法:

tjws.getDeployment().getResourceFactories().add(HelloWorldServiceImpl.class);


这种情况下我们必须自己来保证HelloWorldServiceImpl是线程安全的。如果你不了解我这里说的singleton模式,可参考蓝点上这篇 "RESTEasy的入门文章":http://bluedash.net/spaces/RESTEasy ,理解了RESTEasyr的基本使用方法后,再回来看本篇文章。

我们继续看这个单元测试的源代码,在setUp()方法中,我们进行了服务注册及参数的配置之后,便启动了这个内嵌的WebService服务:

tjws.start();


接下来,在主测试程序中,我们通过使用最基本的HTTP协议来调用WebService服务,并查看返回值与期望是否相附,代码很明白,没有需要特别说明的地方。

最后,在tearDown() 方法中,我们关闭了WebService服务:

tjws.stop();


这是测试完成后必要的清理工作。

InMemoryClientExecutor
如果我们不需要对外提供WebService服务,而只是会在自己的项目代码中,以编码的形式来调用自己的WebService,RESTEasy还提供了更为轻量级的InMemoryClientExecutor, 下面是一个例子:

import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.core.executors.InMemoryClientExecutor;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class TestInMemoryClientExecutor {

    @Test
    public void testHelloWorld() throws Exception {
        InMemoryClientExecutor executor = new InMemoryClientExecutor();
        executor.getDispatcher().getRegistry().addPerRequestResource(HelloWorldServiceImpl.class);
        ClientRequest request = new ClientRequest("/helloworld", executor);
        assertEquals(HelloWorldServiceImpl.HELLO_WORLD, request.getTarget(String.class));
    }
}


一般是在RESTEasy自身的测试代码中多见这个InMemoryClientExecutor的使用,在一般的应用编码情况下,可能InMemoryClientExecutor用到的场合并不会太多。

TJWSServletContainer
在RESTEasy的单元测试代码中,提供了一个很方便的TJWSServletContainer类,它对TJWSEmbeddedJaxrsServer进行了封装,我们也可以使用这个来实现我们的嵌入式WebService服务:

import org.jboss.resteasy.client.ProxyFactory;
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.test.TJWSServletContainer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class TestTJWSServletContainer {

    @BeforeClass
    public static void setUp() throws Exception {

        ResteasyDeployment deployment = TJWSServletContainer.start();
        deployment.getRegistry().addPerRequestResource(HelloWorldServiceImpl.class);
    }

    @AfterClass
    public static void stop() throws Exception {
        TJWSServletContainer.stop();
    }


    @Test
    public void testIt() throws Exception {
        RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
        HelloWorldService client = ProxyFactory.create(HelloWorldService.class, "http://127.0.0.1:8081");
        assertEquals("Hello, world!", client.printHelloWorld());

    }
}


这个例子在本质上和直接使用TJWSEmbeddedJaxrsServer没有任何区别(实际上是有一点区别,有兴趣的读者可以参考下一节深入讨论的内容)。另外,在这个单元测试的主测试功能testIt当中,我们并没有使用最直接的HTTP的调用方式,而是利用了RESTEasy客户端的方式来调用了WebService,可以看到,和第一个例子中那个单元测试相比,少写了多少代码,所以使用RESTEasy客户端来调用RESTEasy的WebService应该是最省事的。

深入讨论
如果对RESTEasy的源代码实现没兴趣,本节内容可以无视。我们要讨论第一个例子(针对TJWSEmbeddedJaxrsServer)和第三个例子(针对TJWSServletContainer)中的一处代码上的区别。在第一个例子中,我们注册服务的代码为:

tjws.getDeployment().getActualResourceClasses().add(HelloWorldServiceImpl.class);


而在第三个例子中,我们使用的方法为:

deployment.getRegistry().addPerRequestResource(HelloWorldServiceImpl.class);


实际上我们同为使用ResteasyDeploy这个类来注册我们的Service,为什么两个代码中使用的方法不同呢?

仔细看下我们会发现,两个方法所处的上下文环境不同。再看下第一个:

...
tjws.getDeployment().getActualResourceClasses().add(HelloWorldServiceImpl.class);
tjws.start();
...


我们是在start()之前来注册服务。而第三个例子中:

...
ResteasyDeployment deployment = TJWSServletContainer.start();
deployment.getRegistry().addPerRequestResource(HelloWorldServiceImpl.class);
...


我们是在start()之后来注册服务。这就是两者使用不同方法的重要区别,看下ResteasyDeployment这个RESTEasy中的核心类,我们会发现,它是在start()中进行registry的加载工作,并调用registration()方法将我们在getActualResourceClasses中添加的服务注册进Registry的:

package org.jboss.resteasy.spi;
...
public class ResteasyDeployment {
   public void start() {
      registry = dispatcher.getRegistry();
      ...
      registration();
      ...
   }

   ...
   public void registration() {
      ...
      if (resourceClasses != null)
      {
         for (String resource : resourceClasses)
         {
            Class clazz = null;
            try
            {
               clazz = Thread.currentThread().getContextClassLoader().loadClass(resource.trim());
            }
            catch (ClassNotFoundException e)
            {
               throw new RuntimeException(e);
            }
            registry.addPerRequestResource(clazz);
         }
      }
   }
...
}



了解了细节之后,我们便知道第一个例子和第三个例子当中这一差异的原因了。在这里要特别说明一下,我们在使用RESTEasy的时候,应该使用第一种方式,因为这是写在RESTEasy官方文档中的标准使用及调用方法。第三个例子中介绍的先start(),再添加服务的调用方法,并没有写在RESTEasy的文档里,是一种"非标准"的使用,了解即可。

有关样例代码
我将本文中使用的代码放在了github上面,有兴趣的读者可以自己下载代码玩玩看:

https://github.com/liweinan/try-resteasy


使用这个git命令签出代码:

git clone git://github.com/liweinan/try-resteasy.git


代码签出后可以用maven命令来执行本文中的代码:

mvn test


所有的本文源代码都以JUnit单元测试的形式存于项目当中。

你可能感兴趣的:(测试,resteasy,tjws)