RESTEasy是JBoss的开源项目之一,是一个RESTful Web Services框架。RESTEasy的开发者Bill Burke同时也是JAX-RS的J2EE标准制定者之一。JAX-RS是一个JCP制订的新标准,用于规范基于HTTP的RESTful Web Services的API。
我们已经有SOAP了,为什么需要Restful WebServices?用Bill自己的话来说:"如果是为了构建SOA应用,从技术选型的角度来讲,我相信REST比SOAP更具优势。开发人员会意识到使用传统方式有进行SOA架构有多复杂,更不用提使用这些做出来的接口了。这时他们就会发现Restful Web Services的光明之处。"
说了这么多,我们使用RESTEasy做一个项目玩玩看。首先创造一个maven1的web项目
mvn archetype:create -DgroupId=org.bluedash \ -DartifactId=try-resteasy -DarchetypeArtifactId=maven-archetype-webapp
准备工作完成后,我们就可以开始写代码了,假设我们要撰写一个处理客户信息的Web Service,它包含两个功能:一是添加用户信息;二是通过用户Id,获取某个用户的信息,而交互的方式是标准的WebService形式,数据交换格式为XML。假设一条用户包含两个属性:Id和用户名。那么我们设计交换的XML数据如下:
<user> <id>1</id> <name>liweinan</name> </user>
首先要做的就是把上述格式转换成XSD2,网上有在线工具可以帮助我们完成这一工作3,在此不详细展开。使用工具转换后,生成如下xsd文件:
<?xml version="1.0" encoding="utf-8"?> <xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="user" type="userType" /> <xsd:complexType name="userType"> <xsd:sequence> <xsd:element name="id" type="xsd:int" /> <xsd:element name="name" type="xsd:string" /> </xsd:sequence> </xsd:complexType> </xsd:schema>
有了xsd文件,我们便可以使用JDK自带工具的xjc将xsd转换成为Java的Class。将上述xsd文件存为 user.xsd
,并使用如下命令进行转换:
xjc user.xsd
执行结束后我们会得到一系列的类文件:
Li-Weinans-MacBook-Pro:Desktop liweinan$ xjc user.xsd parsing a schema... compiling a schema... generated/ObjectFactory.java generated/UserType.java
这样,我们的XML格式的交换数据便转化为面向对像的Java类了,是不是感觉有点像Hibernate的ORM理念?没错,将XML映射成成面向对象的数据类,这个过程叫做XML Binding,即XML绑定。这个过程也有J2EE标准,叫做JAXB4。而RESTEasy是全面支持JAXB的。可以说RESTEasy所支持的JAX-RS标准,当与JAXB标准结合在一起使用时,就可以发挥出最大优势,让程序员少写一堆一堆的代码。有关JAXB标准,会在 独立的篇章中 详细讨论,在此先不展开。总之我们将生成的Java类放进项目中等候使用。我们可以看一下UserType类的内容:
package org.bluedash.resteasy; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "userType", propOrder = { "id", "name" }) public class UserType { protected int id; @XmlElement(required = true) protected String name; /** * Gets the value of the id property. * */ public int getId() { return id; } /** * Sets the value of the id property. * */ public void setId(int value) { this.id = value; } /** * Gets the value of the name property. * * @return * possible object is * {@link String } * */ public String getName() { return name; } /** * Sets the value of the name property. * * @param value * allowed object is * {@link String } * */ public void setName(String value) { this.name = value; } }
可以看到,XML格式就是通过一些JAXB的标记被映射成了Java类。我们没写什么代码,已经把数据模型定义清楚了。接下来我们撰写最核心的WebService API。我们的WebService包含两个接口:一个是添加用户接口createUser,另一个是获取用户接口getUser:
package org.bluedash.resteasy; import java.net.URI; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; @Path("/users") public class UserServlet { private Map<Integer, UserType> userStore = new ConcurrentHashMap<Integer, UserType>(); private AtomicInteger idGenerator = new AtomicInteger(); @POST @Consumes("application/xml") public Response createUser(UserType user) { user.setId(idGenerator.incrementAndGet()); userStore.put(user.getId(), user); System.out.println(user.getName() + " created: " + user.getId()); return Response.created(URI.create("/users/" + user.getId())).build(); } @GET @Path("{id}") @Produces("application/xml") public UserType getUser(@PathParam("id") int id) { UserType u = userStore.get(id); if (u == null) { throw new WebApplicationException( Response.Status.NOT_FOUND); } return u; } }
用几个简单的JAX-RS标记,便把普通的函数变成了WebService接口。而这些标记将由RESTEasy支持生效。接下来我们将要进行RESTEasy的配置工作。RESTEasy的配置方法有多种多样,可以和Spring等容器集成,也可以独立运行,因为我们用的Servlet的形式使RESTEasy进行工作,这也是最主流的方式,因此在这里使用web容器来加载它,首先定义一个配置类:
package org.bluedash.resteasy; import java.util.HashSet; import java.util.Set; import javax.ws.rs.core.Application; public class BluedashResteasyApplication extends Application { private Set<Object> singletons = new HashSet<Object>(); private Set<Class<?>> classes = new HashSet<Class<?>>(); public BluedashResteasyApplication() { // classes.add(UserServlet.class); singletons.add(new UserServlet()); } @Override public Set<Class<?>> getClasses() { return classes; } @Override public Set<Object> getSingletons() { return singletons; } }
这个类扩展JAX-RS的Application接口,用于封装我们的WebService API方法。我们可以看到JAX-RS支持两种封装方法,一种是classes封装,由容器管理WebServices类的实例化和销毁等动作,一个线程一个实例,开发者不需要关心线程安全问题。但这种方法可能比较浪费资源。如果开发者想自己管理线程安全,共线程共用一个WebServices实例,那么就用singletons封装。我们在这里用的singletons封装,这也就解释了为什么我们在 UserServlet中使用了ConcurrentHashMap和AtomicInteger这些保障线程安全的类。接下来就是在web.xml中启动RESTEasy:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>javax.ws.rs.core.Application</param-name> <param-value>org.bluedash.resteasy. BluedashResteasyApplication</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server. servlet.ResteasyBootstrap</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet. HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
没错,就是这么简单,这样,我们的WebService就完成了!还差点什么呢?嗯,还差一个Test Case来使用我们的WebService接口,并验证它的正确性,让我们来写一个TestUserAPI
package org.bluedash.resteasy.test.integration.test; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import junit.framework.TestCase; public class TestUserAPI extends TestCase { public static final String USER_API = "http://127.0.0.1:8080/try-resteasy/users"; public void testCreateUserAndGetUser() throws IOException { URL url = new URL(USER_API); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/xml"); connection.setDoOutput(true); connection.setInstanceFollowRedirects(false); connection.setConnectTimeout(1000); String userXML = "<user><name>liweinan</name></user>"; OutputStream os = connection.getOutputStream(); os.write(userXML.getBytes()); os.flush(); assertEquals(HttpURLConnection.HTTP_CREATED, connection .getResponseCode()); connection.disconnect(); } }
一切都已经准备就绪,最后我们要配置一下Maven,让它下载所需的RESTEasy等库,然后配置Maven使用Jetty Web服务器,来把我们的服务和测试跑起来:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.bluedash</groupId> <artifactId>try-resteasy</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>try-resteasy Maven Webapp</name> <url>http://maven.apache.org</url> <repositories> <repository> <id>JBossMavenRepo</id> <name>JBoss Maven2 repo</name> <url>http://repository.jboss.org/maven2</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>1.2.RC1</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> <version>1.2.RC1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> </dependency> </dependencies> <build> <finalName>try-resteasy</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <excludes> <exclude>**/integration/**</exclude> </excludes> </configuration> <executions> <execution> <id>integration-tests</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <skip>false</skip> <excludes> <exclude>none</exclude> </excludes> <includes> <include>**/integration/**</include> </includes> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.15</version> <configuration> <scanIntervalSeconds>5</scanIntervalSeconds> <stopKey>foo</stopKey> <stopPort>9999</stopPort> </configuration> <executions> <execution> <id>start-jetty</id> <phase>pre-integration-test</phase> <goals> <goal>run</goal> </goals> <configuration> <scanIntervalSeconds>5</scanIntervalSeconds> <daemon>true</daemon> </configuration> </execution> <execution> <id>stop-jetty</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
有关Maven的配置就不详细展开了。配置完成后我们便可以运行单元测试,看看WebServices是否正确运行。执行下述命令
mvn integration-test
执行结果如下:
------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.bluedash.resteasy.test.integration.test.TestUserAPI liweinan created: 1 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.372 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
可以看到,我们的测试按预期执行成功了。这篇文章中,我简单向大家介绍了RESTEasy的初步使用方法,希望对大家在架构SOA应用时,有所帮助。JAX-RS标准做为J2EE家庭中相对较新的一员,其应用前景是十分广阔的。