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
http://weli.iteye.com/blog/1299497