Jersey搭建Rest web服务

Jersey搭建Rest web服务

本文由大关整理所得,转载请标明出处

       上篇文章我们简述了使用SpringMVC搭建Rest服务。SpringMVC尽管使用了Rest的思想,但SpringMVC使用的不是java 规范。这次我们将简单介绍使用jersey搭建Rest服务的方法。Jersey是有apache组织提供的java JAX-RS接口的实现。使用Jersey实现Rest服务应该更加清晰和合理。(SpringMVC也是基于Rest服务实现的,但是,SpringMVC提供了更多的功能,因此也更加繁杂,有关SpringMVC的更多信息,请参考我上篇文档)。本文主要介绍如何基于Spring框架,使用Jersey实现的Rest服务,和JAX-RS接口搭建一个Rest web服务。在这个Rest web服务中,最后将会使用Spring 的RestTemplate完成客户端的测试。本文主要通过一个项目实现的流程简略的介绍了一种Rest Web服务的搭建方法,通过本文的学习,您能够基本掌握使用Spring、JAX-RS、Jersey搭建一个Rest Web服务的全过程,如果想要知道更多关于Rest和JAX-RS的信息,请查阅:Restful Java with JAX-RS(这是一本英文教材)。

1.     实现功能描述

本文以实现一个简易的学生管理系统为例,讲解如何搭建web服务,在这个学生管理系统中,Rest服务端将会为客户端提供对学生的添加、修改、查询和删除功能。客户端通过Spring 的RestTemplate访问Rest服务端,完成对学生信息的操控。

2.     使用的技术

简易学生管理系统服务端使用Rest方式搭建,使用标准的JAX-RS接口和apache的Jersey实现。此外服务端使用Spring做为基础注入容器,Jetty作为嵌入式的web服务器(即Http服务器)。使用JAXB将对象转换成XML文档,通过Rest消息体传输。如果您对上述的某些概念不熟悉,请查看我的其他文档。

客户端使用Spring的RestTemplate完成访问。

3.     创建model

我们这里的model是学生数据的原型,在这个学生管理系统中,我们主要定义两个数据模型,第一个是Student,用来表示一个学生的信息的实体类,另外一个是StudentLists类,用来表示一个学生的集合。(由于使用JAXB无法直接将集合转换为XML文档,我们就采用了一种迂回的方式)。在建立Rest服务的时候,定义通信两端数据类型,往往是通过定义xsd(即schema)文档开始的。因为对于通信两端来说,schema就是通信签订的协议。这里我们的studentLists.xsd定义如下:

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:complexType name="student">

       <xs:sequence>

           <xs:element name="name" type="xs:string"/>

           <xs:element name="age" type="xs:int"/>

       </xs:sequence>

       <xs:attribute name="id" type="xs:string"/>

    </xs:complexType>

   

    <xs:element name="studentList">

       <xs:complexType>

           <xs:sequence>

              <xs:element name="student" type="student" minOccurs="0" maxOccurs="unbounded"/>

           </xs:sequence>

       </xs:complexType>

    </xs:element>

</xs:schema>

当数据确定下来之后,我们需要使用java工具,将xsd转换成java对应的实体类。

使用的命令如下:

I:\programs\Rest\RestJersey\src>xjc -p org.example.model studentLists.xsd

上述的命令可以通过cmd窗口执行,首先要到达我们项目定义xsd的目录下,之后使用xjc(xml java compiler)将xsd文档转换成java类。-p 后面的第一个参数是转换后java类的包名字,第二个参数是xsd文档的名字。

此时,我们刷新项目,可以发现,系统已经为我们创建了两个实体类,两个类的大致内容如下:

package org.example.model;

 

import javax.xml.bind.annotation.XmlAccessType;

import javax.xml.bind.annotation.XmlAccessorType;

import javax.xml.bind.annotation.XmlAttribute;

import javax.xml.bind.annotation.XmlElement;

import javax.xml.bind.annotation.XmlRootElement;

import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)

@XmlType(name = "student", propOrder = {

    "name",

    "age"

})

@XmlRootElement(name = "student")

public class Student {

 

    @XmlElement(required = true)

    protected String name;

    protected int age;

    @XmlAttribute

    protected String id;

 

   

    public Student() {

    }

   

    public Student(String name, String id, int age) {

    this.name = name;

    this.id = id;

    this.age = age;

    }

   

  

    public String getName() {

        return name;

    }

 

 

    public void setName(String value) {

        this.name = value;

    }

 

  

    public int getAge() {

        return age;

    }

 

  

    public void setAge(int value) {

        this.age = value;

    }

 

    public String getId() {

        return id;

    }

 

 

    public void setId(String value) {

        this.id = value;

    }

 

}

 

package org.example.model;

 

import java.util.ArrayList;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;

import javax.xml.bind.annotation.XmlAccessorType;

import javax.xml.bind.annotation.XmlRootElement;

import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)

@XmlType(name = "", propOrder = {

    "student"

})

@XmlRootElement(name = "studentList")

public class StudentList {

 

    protected List<Student> student;

    public List<Student> getStudent() {

        if (student == null) {

            student = new ArrayList<Student>();

        }

        return this.student;

    }

 

}

上面提供的两个类是同过xjc生成的,因此这些类已经添加了JAXB需要的标记。(注意,我们在Student类中略加修改,添加了一个XmlRootElement标签,因为我们希望,Student类可以作为一个独立的单位进行传输,更多细节可以参考我的关于JAXB2.0使用方法的文章)。

4.     创建DAO

在实际应用中,model是需要与实际数据库中的表相对应的,但是为了简化考虑,我们没有引进对数据库的操作(如果使用数据库,可以考虑使用Hibernate)。在服务端,仅用一个线程安全的Map来存储数据。DAO用来完成对数据的存取和更新操作。我们提供的DAO内容如下:

package org.example.model;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.stereotype.Component;

@Component

public class StudentDao {

    private Map<String, Student> students = new ConcurrentHashMap<String, Student>();

   

    public synchronized void addstudent(Student student)

    {

       students.put(student.id, student);

    }

   

    public synchronized Student getStudentById(String id){

       return students.get(id);

    }

   

    public synchronized void updateStudent(Student student){

       students.remove(student.getId());

       students.put(student.getId(), student);

    }

   

    public synchronized void deleteStudent(String id){

       students.remove(id);

    }

   

    public synchronized StudentList getAllStudent(){

       StudentList sl = new StudentList();

       List<Student> list = sl.getStudent();

       for(Map.Entry<String, Student> s:students.entrySet()){

           list.add(s.getValue());

       }  

       return sl;

    }

}

实际上,students集合的操作换成数据库的操作并不是很难,读者可以自己去实现。

 

5.     创建资源访问器(resource)

这里的资源访问器,是指服务端对外部提供的访问服务端资源的接口。客户端通过调用这些接口,完成对服务端的具体操作。定义如下:

package org.example.resource;

 

import javax.ws.rs.Consumes;

import javax.ws.rs.DELETE;

import javax.ws.rs.GET;

import javax.ws.rs.POST;

import javax.ws.rs.PUT;

import javax.ws.rs.Path;

import javax.ws.rs.PathParam;

import javax.ws.rs.Produces;

 

import org.example.model.Student;

import org.example.model.StudentDao;

import org.example.model.StudentList;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Scope;

import org.springframework.stereotype.Component;

 

@Path("/student/")

@Component

@Scope("prototype")

public class StudentResource {

   

    private StudentDao studentDao;

   

   

    @POST

    @Consumes("application/xml")

    public void createStudent(Student student)

    {

       studentDao.addstudent(student);

    }

 

    @GET

    @Path("{id}")

    @Produces("application/xml")

    public Student getStudent(@PathParam("id") String id){

       return studentDao.getStudentById(id);

    }

   

    @GET

    @Produces("application/xml")

    public StudentList getAllStudent(){      

       return studentDao.getAllStudent();

    }

   

    @PUT

    @Consumes("application/xml")

    public void updateStudent(Student student){

       studentDao.updateStudent(student);

    }

 

    @DELETE

    @Path("{id}")

    public void deleteStudent(@PathParam("id")String id){

       studentDao.deleteStudent(id);

    }

   

    @Autowired

    public void setStudentDao(StudentDao studentDao) {

       this.studentDao = studentDao;

    }

 

}

资源访问接口主要使用了JAX-RS接口,向外提供服务访问。

6.     创建jetty web服务容器

我们使用嵌入式的jetty容器,作为http服务容器。Jetty的创建和开启很简单,如下:

package org.example.server;

 

import java.io.File;

 

import org.eclipse.jetty.server.Connector;

import org.eclipse.jetty.server.Server;

import org.eclipse.jetty.server.nio.SelectChannelConnector;

import org.eclipse.jetty.webapp.WebAppContext;

 

public class StartServer {

    public static void main(String[] args) {

       String userDir = System.getProperty("user.dir");

      

       Server server = new Server();

       server.setStopAtShutdown(true);//在退出程序是关闭服务

      

       Connector connector = new SelectChannelConnector();//创建一个连接

       connector.setPort(12122);//连接的端口号,(tomcat下)一般是8080,这里根据服务需求进行设置

       connector.setHost("202.194.158.128");//ip地址

       server.addConnector(connector);//添加连接

      

       Connector connectorLocal = new SelectChannelConnector();

       connectorLocal.setPort(12123);

       connectorLocal.setHost("localhost");

       server.addConnector(connectorLocal);

      

       WebAppContext context = new WebAppContext();//创建服务上下文

       context.setContextPath("/RestJersey");//设置访问路径,这是访问服务http://{ip}:12123/RestJersey

       context.setConfigurationDiscovered(true);

       context.setDescriptor(userDir+File.separator+"resources"+File.separator+"web.xml");//指明服务描述文件,就是web.xml

       context.setResourceBase(userDir);//指定服务的资源根目录,资源寻找与根目录有关

       server.setHandler(context);//添加处理

      

       try {

           server.start();

           server.join();

       } catch (Exception e) {

           e.printStackTrace();

           System.exit(1);

       }

    }

}

注意,在创建和开启jetty服务时,提供了jetty容器的监听端口号12123,Rest服务的访问目录名称/RestJersey,以及配置文件web.xml的具体位置(即当前工作目录下的resources目录下)。

 

7.     Web服务配置

我们知道,使用web应用程序都需要配置一个web.xml文件,web容器可以同过web.xml文件来获取服务器的具体配置和使用信息。这里我们需要在web.xml文件中提供Spring监听器,并将Spring监听器中监听到的所有resources为前缀的消息投递给Jersey监听器。还要配置Spring的一些基本信息。

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5"

    xmlns="http://java.sun.com/xml/ns/javaee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <context-param>

       <param-name>contextConfigLocation</param-name>

       <param-value>/resources/spring/spring-servlet.xml</param-value>

    </context-param>

   

    <listener>

       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

   

    <servlet>

       <servlet-name>Jersey Web Application</servlet-name>

        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>

    </servlet>

   

    <servlet-mapping>

       <servlet-name>Jersey Web Application</servlet-name>

       <url-pattern>/resources/*</url-pattern>

    </servlet-mapping>

   

</web-app>

在web.xml配置文档中,先指明了spring配置文档的位置,之后指明了一个jersey服务接收的消息模式。上面的配置说明,Jetty服务器接收到请求,先投递给Spring,之后Spring会将接收到的,以resources为前缀的请求提交给Jersey执行。

8.     Spring配置文档

Spring配置文档需要扫描所有内部的标记,其中包括Spring IOC的标记、JAXB的标记、JAX-RS的标记。之后根据标记创建相应的具体实例。配置文档很简单,如下所示:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:context="http://www.springframework.org/schema/context"

       xmlns:tx="http://www.springframework.org/schema/tx"

       xmlns:aop="http://www.springframework.org/schema/aop"

      

       xsi:schemaLocation="http://www.springframework.org/schema/beans

           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

           http://www.springframework.org/schema/aop

           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd

           http://www.springframework.org/schema/tx

           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

           http://www.springframework.org/schema/context

           http://www.springframework.org/schema/context/spring-context-2.5.xsd">

           <context:annotation-config/>

           <context:component-scan base-package="org"/>                

</beans>

9.     Log4j的配置

最后我们提供一个关于log4j的配置,这个配置主要用于输出log内容(jetty等使用的是log4j日志)。

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.Target=System.out

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

#

log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS}:: %-4r [%t] %-5p %c %x %n%-5p %m%n

log4j.rootLogger=info, stdout, R

 

log4j.appender.R=org.apache.log4j.RollingFileAppender

 

log4j.appender.R.file=gridServer.log

 

log4j.appender.R.MaxFileSize=100KB

 

log4j.appender.R.MaxBackupIndex=1

 

log4j.appender.R.layout=org.apache.log4j.PatternLayout

 

log4j.appender.R.layout.ConversionPattern=%d{yyyy-mm-dd HH:mm:ss.SSS} ::%-4r [%t] %-5p %c %x %n%-5p %m%n

 

10.   开启Web服务

现在,执行第六步中的jetty服务的main方法,可以得到如下输出:

2011-07-08 19:05:31.656:: 0    [main] INFO  org.eclipse.jetty.util.log 

INFO  jetty-7.3.0.v20110203

2011-07-08 19:05:31.843:: 187  [main] INFO  org.eclipse.jetty.util.log 

INFO  NO JSP Support for /RestJersey, did not find org.apache.jasper.servlet.JspServlet

2011-07-08 19:05:32.031:: 375  [main] INFO  /RestJersey 

INFO  Initializing Spring root WebApplicationContext

2011-7-8 19:05:32 org.springframework.web.context.ContextLoader initWebApplicationContext

信息: Root WebApplicationContext: initialization started

2011-7-8 19:05:32 org.springframework.context.support.AbstractApplicationContext prepareRefresh

信息: Refreshing Root WebApplicationContext: startup date [Fri Jul 08 19:05:32 CST 2011]; root of context hierarchy

2011-7-8 19:05:32 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions

信息: Loading XML bean definitions from ServletContext resource [/resources/spring/spring-servlet.xml]

2011-7-8 19:05:33 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons

信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@b45130: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,studentDao,studentResource]; root of factory hierarchy

2011-7-8 19:05:33 org.springframework.web.context.ContextLoader initWebApplicationContext

信息: Root WebApplicationContext: initialization completed in 1875 ms

2011-07-08 19:05:33.921:: 2265 [main] INFO  org.eclipse.jetty.util.log 

INFO  started o.e.j.w.WebAppContext{/RestJersey,file:/I:/programs/Rest/RestJersey/}

2011-07-08 19:05:33.984:: 2328 [main] INFO  org.eclipse.jetty.util.log 

INFO  Started SelectChannelConnector@202.194.158.128:12122

2011-07-08 19:05:33.984:: 2328 [main] INFO  org.eclipse.jetty.util.log 

INFO  Started SelectChannelConnector@localhost:12123

11.   编写测试代码

最后,我们提供一个客户端,用来访问服务端,并对服务端进行操作。

package org.example.client;

 

import java.net.URI;

import java.net.URISyntaxException;

 

import org.example.model.Student;

import org.example.model.StudentList;

import org.springframework.web.client.RestClientException;

import org.springframework.web.client.RestTemplate;

 

public class Client {

    private static String url = "http://202.194.158.128:12122/RestJersey/resources/student/";

    public static void main(String[] args) throws RestClientException, URISyntaxException {

       RestTemplate restTemplate = new RestTemplate();

       Student student = new Student("大关", "S09080458", 22);

       restTemplate.postForLocation(new URI(url), student);

       student = new Student("李雷","S09080707",33);

       restTemplate.postForLocation(new URI(url), student);

       student = new Student("Lucy","S09080808",23);

       restTemplate.postForLocation(new URI(url), student);

      

       StudentList students = restTemplate.getForObject(new URI(url), StudentList.class);

      

       for(Student s:students.getStudent())

       {

           System.out.println(s.getId());

           System.out.println(s.getName());

           System.out.println(s.getAge());

           System.out.println();

       }

    }

}

 

测试结果:

S09080707

李雷

33

 

S09080808

Lucy

23

 

S09080458

大关

22

12.   小结

通过上面提供的例子,我们可以掌握了使用JAX-RS编写Rest服务的全过程,这里需要注意的是,在服务端与客户端传递java对象的时候(不是指基础对象,String、int等),使用的是JAXB,而JAXB 可以通过Spring创建其JAXBContent,因此无需单独提供JAXB的上下文设置。

本文使用jetty、Spring、JAX-RS、Jersey、JAXB2.0、Log4j等技术搭建Rest服务,并使用Spring的RestTemplate编写客户端,如果读者想要继续学习Rest服务,可以参考我的其他文章。

13.   参考文献

Spring官方文档(在Spring 下载后的jar包中)

JAXB 使用文档(http://jaxb.java.net/tutorial/index.html

JAX-RS教材(Restful Java with JAX-RS)

Rest 服务论文(Architectural Style and the Design of Network-base Software Architectures)

Jersey 官方网站:http://jersey.java.net/

SpringMVC与JAX-RS:http://www.infoq.com/articles/springmvc_jsx-rs

14.   目录结构:

 
I:\programs\Rest\RestJersey>tree /F
文件夹 PATH 列表
卷序列号为 801B-4CB2
I:.
├─lib
│      asm-3.1.jar
│      commons-io-1.3.2.jar
│      commons-logging-api-1.1.1.jar
│      dom4j-1.6.1.jar
│      jersey-core-1.8.jar
│      jersey-server-1.8.jar
│      jersey-spring-1.8.jar
│      jetty-annotations-7.3.0.v20110203.jar
│      jetty-client-7.3.0.v20110203.jar
│      jetty-continuation-7.3.0.v20110203.jar
│      jetty-deploy-7.3.0.v20110203.jar
│      jetty-http-7.3.0.v20110203.jar
│      jetty-io-7.3.0.v20110203.jar
│      jetty-security-7.3.0.v20110203.jar
│      jetty-server-7.3.0.v20110203.jar
│      jetty-servlet-7.3.0.v20110203.jar
│      jetty-servlets-7.3.0.v20110203.jar
│      jetty-util-7.3.0.v20110203.jar
│      jetty-webapp-7.3.0.v20110203.jar
│      jetty-websocket-7.3.0.v20110203.jar
│      jetty-xml-7.3.0.v20110203.jar
│      jsr311-api-1.1.1.jar
│      junit-4.7.jar
│      log4j-1.2.16.jar
│      org.springframework.aop-3.0.3.RELEASE.jar
│      org.springframework.asm-3.0.3.RELEASE.jar
│      org.springframework.aspects-3.0.3.RELEASE.jar
│      org.springframework.beans-3.0.3.RELEASE.jar
│      org.springframework.context-3.0.3.RELEASE.jar
│      org.springframework.context.support-3.0.3.RELEASE.jar
│      org.springframework.core-3.0.3.RELEASE.jar
│      org.springframework.expression-3.0.3.RELEASE.jar
│      org.springframework.instrument-3.0.3.RELEASE.jar
│      org.springframework.instrument.tomcat-3.0.3.RELEASE.jar
│      org.springframework.oxm-3.0.3.RELEASE.jar
│      org.springframework.web-3.0.3.RELEASE.jar
│      org.springframework.web.servlet-3.0.3.RELEASE.jar
│      servlet-api-2.5.jar
│      slf4j-api-1.5.8.jar
│      slf4j-log4j12-1.5.8.jar

├─resources
│  │  web.xml
│  │
│  └─spring
│          spring-servlet.xml

└─src
    │  log4j.properties
    │  studentLists.xsd
    │
    └─org
        └─example
            ├─client
            │      Client.java
            │
            ├─model
            │      Student.java
            │      StudentDao.java
            │      StudentList.java
            │
            ├─resource
            │      StudentResource.java
            │
            └─server
                    StartServer.java


I:\programs\Rest\RestJersey>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值