使用Spring MVC 搭建Rest服务

使用Spring MVC 搭建Rest服务

(2011-06-24 21:48:28)

使用Spring MVC 搭建Rest服务

本文由大关总结整理所得,不保证内容的正确性,转载请标明出处!

   Rest(Representational StateTransfer,表述状态转移),是一种基于Http协议的,能够快速开发网络服务程序,并且提高网络服务系统伸展性的设计和开发方式。Rest的两端可以是不同构的编程和程序运行环境,Rest通过Http协议将通信的两端进行连接,在服务两端通过对Http协议的使用,最终完成数据的翻译和转换。

   Rest有几个重要的特点,使得Rest能够在服务搭建上占有一定的优势。首先,Rest以一切皆资源的方式来看待所有的web提供的服务(包括web服务本身,以及web服务中某个具体的服务应用),所有的资源都采用URI进行定位。其次,Rest通过对Http中相应字段属性设置,确定客户端和服务端的缓存策略,在一定程度上,可以缓解服务器和客户端的压力(Rest服务被认为是无状态的web服务),最后,Rest通过对Http消息体中(MIME)Content-Type的内容不同,可以采用不同处理策略对消息分解,获取需要的信息,此外Rest还有很多优势,具体可以参考:ArchitecturalStyles and the Design of Network-based SoftwareArchitectures这篇论文,文中详细描述了Rest服务的推导过程。

   要想使用Rest必须要了解Http服务中定义的几个关键的方法。GET,用于从服务端获取信息,但并不更改服务端的任何状态;POST,用于提交创建的信息,一般更改服务端的数据状态,并且返回新创建对象的ID(一般来说,为了保证ID的唯一性,ID的值需要由服务端来确定,因此在创建结束后会将新分配的ID反馈给客户端);PUT,用于更改服务端某个对象的状态(一般被认为是Update),服务端状态受到影响,不需要返回影响后的结果;DELETE,删除服务端的对象,服务端的状态发生改变,可以返回删除后的对象,也可以不返回。(Http还有其他的方法,但在Rest中不是很常用,可以查看RFC2616)。这些方法仅是意义上这样说,但是并不是一定要这用,具体的方法对应的动作完全是服务两端确定的。

   在JAVA中搭建Rest服务有很多种,其中JAX-RS是JAVA定义的Rest服务的API,可以使用Apache实现的jersy搭建Rest服务。在SpringMVC中也提供了搭建Rest服务的方法,下文主要探讨如何使用Spring MVC搭建Rest服务。

   下面的内容需要您对如下内容有所了解:

(1)  会使用Spring框架,理解依赖注入原理,理解Spring的基本配置。

(2) 使用过Java相关的Web容器(例如,Tomcat或Apache等,这里使用的是Jetty,原理相同)

(3)  会使用一款OXM框架(例如:Castor或JAXB,这里使用的是JAXB2)

(4)  对什么是Http服务和什么是Rest服务略有了解

如果您完全满足上面的条件,那么理解下面的内容就根本不成问题,如果您对有些内容不是很清楚,可以先看整个服务的流程,具体细节可以略看。

1.  Rest功能说明

下面我们将开始使用SpringMVC的方法搭建一个Rest服务,在这个服务中,服务器端维护了一个关于Student的集合,客户端可以通过连接到服务端,向服务端添加、删除、查找和更新学生信息等操作。

2.  学生数据结构(bean)

通过对Rest功能的分析,可以看出,服务端有两个关键的数据结构,一个是用来表示学生信息的Student类,另外一个是用来表示学生集合的StudentList类。下面我们通过schema定义这两个数据结构。

StudentLists.xsd

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

<xsd:schemaxmlns:xsd="http://www.w3.org/2001/XMLSchema">

   <xsd:complexTypename="student">

      <xsd:sequence>

             <xsd:element name="name"type="xsd:string" minOccurs="1"/>

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

          </xsd:sequence>

          <xsd:attribute name="id"type="xsd:string" use="required"/>

   </xsd:complexType>

 

 

   <xsd:elementname="studentList">

      <xsd:complexType>

          <xsd:sequence>

             <xsd:element name="students"type="student" maxOccurs="unbounded"/>

          </xsd:sequence>

      </xsd:complexType>

   </xsd:element>

</xsd:schema>

可以看出,schema中定义了两个结构,一个是学生类型,一个是学生集合类型。学生类型包括两个元素(分别是name和age)和一个属性(id)。学生集合类型中仅包含一个元素(students,是一个学生类型的集合)。

通过使用xjc生成java类,xjc命令如下:

I:\programs\eclipse\SpringMVCRestTest\src>xjc -pcom.upc.upcgrid.guan.springMvcR

estTest.bean.student StudentLists.xsd

xjc(xml to javacompiler),可以将schema转换成java类,-p后面的两个参数分别是转换后的包名,以及需要转换的schema文件名字。此时,你刷新你的服务,可以看到xjc已经生成了Student类和StudentLists类,此外还生成了一个ObjectFactory类,这里我们不需要ObjectFactory类,因此直接将这个类删除,之后在Student类的声明前增加一条语句,使得Student类声明部分如下:

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

    "name",

    "age"

})

@XmlRootElement(name="student")

public class Student {

3.  学生管理类

学生的基本结构已经搭建完成,现在需要提供一个管理类,用于来管理学生集合(注意,正常情况下,应当将学生数据存入数据库,这里为了简化,仅将这些信息出入一个Map中进行管理)。学生管理类维护这学生集合,以及基于集合之上的操作。

StudentManager.java

 

@Component

public class StudentManager {

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

   

   public synchronized void addStudent(Studentstudent){

      if(students.containsKey(student.getId()))

          return;

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

    }

   

   public synchronized void deleteStudent(Stringid){

      if(students.containsKey(id))

          students.remove(id);

    }

   

   public synchronized void updateStudent(Stringid,Student student){

      deleteStudent(id);

      if(student.getId()==null ||student.getId().equals(""))

          student.setId(id);

      addStudent(student);

    }

   

   public synchronized Student getStudent(Stringid){

      return students.get(id);

    }

 

   public synchronized StudentList getStudent() {

      StudentList sl = new StudentList();

      for(String key : students.keySet())

      {

          sl.getStudents().add(students.get(key));

      }

      return sl;

    }

   

}

可以看出,我们将StudentManager标记成component,以便Spring能够将这个类的实例注入到使用它的类中(注意,这个类必须是一个单例模式,因为这个类中维护着学生的集合)。此外,StudentManager中的所有方法都标记成了同步方法,并且Map集合也是一个同步集合。StudentManager提供了对学生集合Map的基本操作。

4.  Rest服务实现

下面提供了使用Spring MVC实现Rest服务的方法。

 

@Controller

@RequestMapping(value="/students")

public class StudentServer {

   private StudentManager manager;

      

   @RequestMapping(method=RequestMethod.POST)

   @ResponseBody

   public String createStudent(@RequestBody Studentstudent){

      manager.addStudent(student);

      return student.getId();

    }

   

   @RequestMapping(value="/{id}",method=RequestMethod.GET)

   @ResponseBody

   public Student getStudent(@PathVariable String id){

      return manager.getStudent(id);

    }

   

   @RequestMapping(method=RequestMethod.GET)

   @ResponseBody

   public StudentList getStudent(){

      return manager.getStudent();

    }

   

   @RequestMapping(value="/{id}",method=RequestMethod.PUT)

   @ResponseBody

   public void updateStudent(@RequestBody Studentstudent,@PathVariable String id){

      manager.updateStudent(id, student);

    }

   

   @RequestMapping(value="/{id}",method=RequestMethod.DELETE)

   @ResponseBody

   public void deleteStudent(@PathVariable Stringid){

      manager.deleteStudent(id);

    }

 

   @Autowired

   public void setManager(StudentManager manager) {

      this.manager = manager;

    }

}

用Control标记,Spring会认为这是一个Web服务,使用RequestMapping指明了服务映射的URI内容和映射的Http请求的方法。使用PathVariable可以从URI中获取参数,使用RequestBody,Spring会将Http消息体内部的数据使用配置策略转换成一个对象(参考5.Spring的配置),使用ResponseBody,Spring会将返回值转换成消息体需要的格式(参考5.Spring的配置)。

5.  Spring的配置

现在,我们需要对Spring进行基本的配置,以便Spring能够使用正确的方式对接收到(发送出)的Http请求(相应)使用正确的方法处理(生成)消息体。我们的基本策略是,如果接收到的是XML文档(即Content-type是***/xml类型),则使用JAXB2将消息体转换成对象,否则将消息体作为普通文本进行处理;如果要发送的消息是复杂对象(不是简单类型,例如:String、Integer、void等),则将消息使用JAXB2进行编组,否则仅生成文本消息。

Spring的配置如下:

SpringConfig.java

@Configuration

public class SpringConfig {

   private Jaxb2Marshaller marshaller;

   

   public @Bean Jaxb2Marshallerjaxb2Marshaller()//配置JAXB2Context

    

      Jaxb2Marshaller marshaller = newJaxb2Marshaller();//创建JAXB上下文环境

      marshaller.setClassesToBeBound(Student.class,StudentList.class);//映射的xml类放入JAXB环境中    

      this.marshaller = marshaller;

      return marshaller;

    }

   

   public @Bean AnnotationMethodHandlerAdapterannotationMethodHandlerAdapter()

    {

      AnnotationMethodHandlerAdapter adapter = newAnnotationMethodHandlerAdapter();//创建消息体转换器

      HttpMessageConverter<?>[] converters= newHttpMessageConverter<?>[2];//创建转换数组

      StringHttpMessageConverter stringConverter = newStringHttpMessageConverter();//创建字符转换器

      MarshallingHttpMessageConverter marshallerConverter = newMarshallingHttpMessageConverter();//创建xom转换器

      marshallerConverter.setMarshaller(marshaller);//设置marshaller

      marshallerConverter.setUnmarshaller(marshaller);//设置unmarshaller

      //将两个转换器放入列表

      converters[0] = (stringConverter);

      converters[1] = (marshallerConverter);

      //将转换器列表赋值给消息体转换器

      adapter.setMessageConverters(converters);

      return adapter;  //返回消息体转换器  

    }

}

SpringConfig中先配置了JAXB2,在JAXB2中需要指定在2学生数据结构中生成的两个类,因此JAXB环境会知道如何将Student和StudentList编组和解组。

SpringConfig中还配置了一个Adapter,这个转换器是Spring用来转换消息体时使用的,在这里,我们为Adapter配置了两个Converter,一个使用来处理XML的Marshaller的Converter,一个是用来处理普通类型String的Converter。因此Spring会根据情况,选择正确的Converter处理消息体。(注意,只要你提供了一个Adapter,Spring就会使用你提供的这个Adapter处理消息体,如果你没有提供,Spring将会使用默认的Adapter处理消息体,由于对于XML类型(或者JSON类型)的数据必须手动提供Converter,所以这里不能使用默认的Converter)。

之后是Spring的配置文件:

rest-servlet.xml

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

<beansxmlns="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-scanbase-package="com.upc.upcgrid.guan"/>                

</beans>

Spring的配置文档中,仅让Spring环境扫描标有标记类的包。

web.xml

<?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">

   

   <servlet>

      <servlet-name>rest</servlet-name>

      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

      <init-param>

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

          <param-value>/conf/rest-servlet.xml</param-value>

      </init-param>

      <load-on-startup>1</load-on-startup>

   </servlet>

   

   <servlet-mapping>

      <servlet-name>rest</servlet-name>

      <url-pattern>/rest/*</url-pattern>

   </servlet-mapping>  

</web-app>

Spring使用DispatcherServlet完成URI任务的匹配和分发,在创建DispatcherServlet的时候,提供了Spring配置文件的路径。

6.  Jetty服务器的配置和启动

我们这里使用较轻量级的Jetty服务器,以嵌入式的方式启动Spring服务。Jetty服务要比Tomcat小的多,并且无需安装,可以嵌入到程序中执行。下面给出Jetty的启动过程。

JettyServerStart.java

public class JettyServerStart {

   public static void main(String[] args) {

      

       Server server= newServer();//创建jetty web容器(这与tomcat容器类似)

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

      

      //创建连接器,每个连接器都是由IP地址和端口号组成,连接到连接器的连接将会被jetty处理

      //第一个连接器的连接方式为http://202.194.158.128:8586

      Connector connector = newSelectChannelConnector();//创建一个连接器

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

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

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

      

      //创建本地连接器,连接方式为http://localhost:8585

      Connector connectorLocal = new SelectChannelConnector();

      connectorLocal.setPort(8585);

      connectorLocal.setHost("localhost");

      server.addConnector(connectorLocal);

      

      //配置rest服务

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

      context.setContextPath("/SpringMVCRestTest");//访问服务路径http://{ip}:8568/SpringMVCRestTest

      context.setConfigurationDiscovered(true);

   context.setDescriptor(System.getProperty("user.dir")+File.separator+"conf"+File.separator+"web.xml");//指明服务描述文件,就是web.xml

      context.setResourceBase(System.getProperty("user.dir"));//指定服务的资源根路径,配置文件的相对路径与服务根路径有关

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

      

      try {

          server.start();//开启服务

          server.join();

      } catch (Exception e) {

          e.printStackTrace();

          System.exit(1);

      }//开启服务

    }

现在,我们执行main方法,会的到如下输出:

2011-06-2421:22:07.609:INFO::jetty-7.3.0.v20110203

2011-06-24 21:22:07.796:INFO::NO JSP Support for/SpringMVCRestTest, did not findorg.apache.jasper.servlet.JspServlet

2011-06-24 21:22:07.921:INFO::startedo.e.j.w.WebAppContext{/SpringMVCRestTest,file:/I:/programs/eclipse/SpringMVCRestTest/}

2011-06-2421:22:08.093:INFO:/SpringMVCRestTest:Initializing SpringFrameworkServlet 'rest'

2011-6-24 21:22:08org.springframework.web.servlet.FrameworkServletinitServletBean

信息: FrameworkServlet 'rest': initializationstarted

2011-6-24 21:22:08org.springframework.context.support.AbstractApplicationContextprepareRefresh

信息: Refreshing WebApplicationContext for namespace'rest-servlet': startup date [Fri Jun 24 21:22:08 CST 2011]; rootof context hierarchy

2011-6-24 21:22:08org.springframework.beans.factory.xml.XmlBeanDefinitionReaderloadBeanDefinitions

信息: Loading XML bean definitions fromServletContext resource [/conf/rest-servlet.xml]

2011-6-24 21:22:08org.springframework.beans.factory.support.DefaultListableBeanFactorypreInstantiateSingletons

信息: Pre-instantiating singletons inorg.springframework.beans.factory.support.DefaultListableBeanFactory@7736bd:defining beans[org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,springConfig,studentManager,studentServer,jaxb2Marshaller,annotationMethodHandlerAdapter];root of factory hierarchy

2011-6-24 21:22:08org.springframework.oxm.jaxb.Jaxb2MarshallercreateJaxbContextFromClasses

信息: Creating JAXBContext with classes to be bound[classcom.upc.upcgrid.guan.springMvcRestTest.bean.student.Student,classcom.upc.upcgrid.guan.springMvcRestTest.bean.student.StudentList]

2011-6-24 21:22:09org.springframework.web.servlet.handler.AbstractUrlHandlerMappingregisterHandler

信息: Mapped URL path [/students/{id}] onto handler[com.upc.upcgrid.guan.springMvcRestTest.spring.StudentServer@497904]

2011-6-24 21:22:09org.springframework.web.servlet.handler.AbstractUrlHandlerMappingregisterHandler

信息: Mapped URL path [/students/{id}.*] onto handler[com.upc.upcgrid.guan.springMvcRestTest.spring.StudentServer@497904]

2011-6-24 21:22:09org.springframework.web.servlet.handler.AbstractUrlHandlerMappingregisterHandler

信息: Mapped URL path [/students/{id}/] onto handler[com.upc.upcgrid.guan.springMvcRestTest.spring.StudentServer@497904]

2011-6-24 21:22:09org.springframework.web.servlet.handler.AbstractUrlHandlerMappingregisterHandler

信息: Mapped URL path [/students] onto handler[com.upc.upcgrid.guan.springMvcRestTest.spring.StudentServer@497904]

2011-6-24 21:22:09org.springframework.web.servlet.handler.AbstractUrlHandlerMappingregisterHandler

信息: Mapped URL path [/students.*] onto handler[com.upc.upcgrid.guan.springMvcRestTest.spring.StudentServer@497904]

2011-6-24 21:22:09org.springframework.web.servlet.handler.AbstractUrlHandlerMappingregisterHandler

信息: Mapped URL path [/students/] onto handler[com.upc.upcgrid.guan.springMvcRestTest.spring.StudentServer@497904]

2011-6-24 21:22:09org.springframework.web.servlet.FrameworkServletinitServletBean

信息: FrameworkServlet 'rest': initializationcompleted in 969 ms

2011-06-24 21:22:09.125:INFO::StartedSelectChannelConnector@202.194.158.128:8586

2011-06-24 21:22:09.125:INFO::StartedSelectChannelConnector@localhost:8585

从输出的内容可以看出,Spring服务已经正常启动,并且Jetty将会在本地的8586和8585两个端口进行监听。

7.  编写客户端测试程序

Rest的客户端可以使用Spring的RestTemplate进行编写。为了给使用这屏蔽掉我们后台与服务器进行通信的复杂操作,我们编写了一个RestUtility,这个类主要完成与服务器的通信。

RestUtility.java

public class RestUtility {

   private static String url ="http://202.194.158.128:8586/SpringMVCRestTest/rest/students/";   

   private static RestTemplate restTemplate;

   static {

      createRestTemplate();

    }

   

   public static RestTemplategetRestTemplate(){   

      return restTemplate;

    }

 

   private static void createRestTemplate() {

      restTemplate = new RestTemplate();

      List<HttpMessageConverter<?>>converters = newArrayList<HttpMessageConverter<?>>();//消息体转换器列表

      MarshallingHttpMessageConverter marshalConverter = newMarshallingHttpMessageConverter();//xom类型的消息体转换器

      Jaxb2Marshaller marshaller = newJaxb2Marshaller();//创建JAXB2类型的xom环境

      marshaller.setClassesToBeBound(Student.class,StudentList.class);//将类绑定到JAXB2

      marshalConverter.setMarshaller(marshaller);//设置编组器

      marshalConverter.setUnmarshaller(marshaller);//设置解组器

      converters.add(marshalConverter);//将xom消息体转换器添加到列表 

      converters.add(new StringHttpMessageConverter());

      restTemplate.setMessageConverters(converters);//将转换器列表放入RestTemplate

    }

   

   public static void addStudent(Student student)throws RestClientException, URISyntaxException{

      System.out.println(restTemplate.postForObject(newURI(url), student,String.class));

    }

   

   public static Student getStudent(String id){

      return restTemplate.getForObject(url+"{id}",Student.class, id);

    }

   

   public static void updateStudent(Stringid,Student student){

      restTemplate.put(url+"{id}", student, id);

    }

   

   public static void deleteStudent(Stringid){

      restTemplate.delete(url+"{id}", id);

    }

   

   public static StudentList getAllStudents()throws RestClientException, URISyntaxException{

 

      return restTemplate.getForObject(newURI(url),StudentList.class);

    }

}

这个类主要使用RestTemplate对服务器进行通信的。

最后给出一个测试程序,并给出结果.

 

public class RestClient {

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

      Student student = new Student();

      student.setAge(20);

      student.setName("Mary");

      student.setId("05080416"); 

      

      RestUtility.addStudent(student);      

      student.setAge(21);

      student.setName("Lucy");

      student.setId("05080411");

      RestUtility.addStudent(student);

 

      

      student = RestUtility.getStudent("05080416");

      System.err.println(student.getName());

      

      StudentList sl = RestUtility.getAllStudents();

      for(Student s : sl.getStudents())

      {

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

      }

}

在测试程序中,我们先创建了两个学生,并将两个学生添加到远程,之后从远程查询了一个学生,并输出信息,然后查询了所有学生,输出信息。整个过程没有对错误进行处理。

执行输出:

2011-6-24 21:36:55org.springframework.oxm.jaxb.Jaxb2MarshallercreateJaxbContextFromClasses

信息: Creating JAXBContext with classes to be bound[classcom.upc.upcgrid.guan.springMvcRestTest.bean.student.Student,classcom.upc.upcgrid.guan.springMvcRestTest.bean.student.StudentList]

05080416

05080411

Mary

Lucy

Mary

8.  程序结构

使用Spring <wbr>MVC <wbr>搭建Rest服务



Jar包:

使用Spring <wbr>MVC <wbr>搭建Rest服务

参考:

   Spring3.0官方文档

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

   教材:Restful Java with JAX-RS

   论文:Architectural Styles and the Design of Network-based SoftwareArchitectures

   Jetty:http://blog.sina.com.cn/s/blog_616e189f0100r1fs.html

   JAXB:http://blog.sina.com.cn/s/blog_616e189f0100slij.html

 

源码下载:http://guanxinquan.download.csdn.net/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值