通过本文,我将介绍REST的特点,基本设计原则及其简单讲解,最后给出spring3.0下开发的RESTful Web Services 简单实例,其中许多内容是在网络上摘得,并通过自己理解写上的本人观点的博客,如有不同意见请指正。

REST(Representational State Transfer ),有中文翻译为"具象状态传输"(也有:"代表性状态传输")。是由 Roy Thomas Fielding博士 在2000年就读加州大学欧文分校期间在学术论文中提出的一个术语。他首次系统全面地阐述了REST的架构风格和设计思想。这篇论文是Web发展史上一篇非常重要的技术文献,他也为WEB架构的设计与评判奠定了理论基础。

注:附件里有论文的中文版,有兴趣的朋友可以下载看看。

REST 定义了一组体系架构原则,您可以根据这些,包括使用不同语言编写的客户端如何通过 HTTP 处理和传输资源状态。所以在事实上,REST 对 Web的影响非常大,由于其使用相当方便,已经普遍地取代了基于 SOAP 和 WSDL 的接口设计。在多年以后的今天,REST的主要框架已经开始雨后春笋般的出现。

个人理解:

(一)  首先REST只是一种风格,不是一种标准
(二)  REST是以资源为中心
(三)  REST充分利用或者说极端依赖HTTP协议

一.对于今天正在吸引如此多注意力的最纯粹形式的 REST Web 服务,其具体实现应该遵循以下基本设计原则

1.1.显式地使用不同的 HTTP 请求方法
1.2.无状态
1.3.公开目录结构式的 URI(通过逻辑URI定位资源)。

1.1.显式地使用不同的 HTTP 请求方法

我们在 Web 应用中处理来自客户端的请求时,通常只考虑 GET 和 POST 这两种 HTTP 请求方法。实际上,HTTP 还有 HEAD、PUT、DELETE 等请求方法。而在 REST 架构中,用不同的 HTTP 请求方法来处理对资源的 CRUD(创建、读取、更新和删除)操作:

若要在服务器上创建资源,应该使用 POST 方法。 
    若要检索某个资源,应该使用 GET 方法。 
    若要更改资源状态或对其进行更新,应该使用 PUT 方法。 
    若要删除某个资源,应该使用 DELETE 方法。

经过这样的一番扩展,我们对一个资源的 CRUD 操作就可以通过同一个 URI 完成了:

[url]http://www.example.com/photo/logo[/url](读取)
仍然保持为 [GET] [url]http://www.example.com/photo/logo[/url]

[url]http://www.example.com/photo/logo/create[/url](创建)
改为 [POST] [url]http://www.example.com/photo/logo[/url]

[url]http://www.example.com/photo/logo/update[/url](更新)
改为 [PUT] [url]http://www.example.com/photo/logo[/url]

[url]http://www.example.com/photo/logo/delete[/url](删除)
改为 [DELETE] [url]http://www.example.com/photo/logo[/url]

从而进一步规范了资源标识的使用。

通过 REST 架构,Web 应用程序可以用一致的接口(URI)暴露资源给外部世界,并对资源提供语义一致的操作服务。这对于以资源为中心的 Web 应用来说非常重要。

1.2.无状态

在 REST 的定义中,一个 Web 应用总是使用固定的 URI 向外部世界呈现一个资源。
它认为Web是由一系列的抽象资源组成,这些抽象的资源具有不同的具体表现形式。
譬如,定义一个资源为photo,含义是照片,它的表现形式可以是一个图片,也可以是一个.xml的文件,其中包含一些描述该照片的元素,或是一个html文件。 并且这些具体的表现可以分布在不同的物理位置上。

1.3.通过逻辑URI定位资源

实现这种级别的可用性的方法之一是定义目录结构式的 URI。
此类 URI 具有层次结构,其根为单个路径,从根开始分支的是公开服务的主要方面的子路径。 根据此定义,URI 并不只是斜杠分隔的字符串,而是具有在节点上连接在一起的下级和上级分支的树。 例如,在一个收集photo的相册中,您可能定义类似如下的结构化 URI 集合:

http://www.example.com/photo/topics/{topic}

如:http://www.example.com/photo/topics/home

根 / photo之下有一个 /topics 节点。 该节点之下有一系列主题名称,例如生日照片,聚会照片等等,每个主题名称指向某个讨论线。 在此结构中,只需在 {topic}输入某个内容即可容易地收集讨论线程。

在某些情况下,指向资源的路径尤其适合于目录式结构。 例如,以按日期进行组织的资源为例,这种资源非常适合于使用层次结构语法。 
此示例非常直观,因为它基于规则:

http://www.example.com/photo/2010/02/22/{topic}

第一个路径片段是四个数字的年份,第二个路径片断是两个数字的月份,第三个片段是两个数字的日期。这就是我们追求的简单级别。 在语法的空隙中填入路径部分就大功告成了,因为存在用于组合 URI 的明确模式:
http://www.example.com/photo/{year}/{day}/{month}/{topic}

从而不需要我们去这样去传递信息:http://www.example.com/photo?year=xxxx&day=xxx%24month=xxx&topic=xxxx

二.Restful web service的优点:

2.1 HTTP头中可见的统一接口和资源地址

通过对于HTTP Head 的解析,我们便可以了解到当前所请求的资源和请求的方式。
这样做对于一些代理服务器的设置,将带来很高的处理效率。
REST 系统中所有的动作和要访问的资源都可以从HTTP和URI中得到,这使得代理服务器、缓存服务器和网关很好地协调工作。而RPC模型的SOAP 要访问的资源仅从 URI无法得知,要调用的方法也无法从HTTP中得知,它们都隐藏在 SOAP 消息中。
同样的,在REST系统中的代理服务器还可以通过 HTTP 的动作 (GET 、 POST)来进行控制。

2.2 返回一般的XML格式内容

一般情况下,一个RESTful Web Service将比一个传统SOAP RPC Web Service占用更少的传输带宽。

Xml代码  
  1. POST/Order HTTP/1.1
  2. Host:[url]www.northwindtraders.com[/url]
  3. Content-Type:text/xml
  4. Content-Length:nnnn
  5. <UpdatePO>
  6. <orderID>098</orderID>
  7. <customerNumber>999</customerNumber>
  8. <item>89</item>
  9. <quantity>3000</quantity>
  10. </UpdatePO>
Xml代码  
  1. POST/Order HTTP/1.1
  2. Host:[url]www.northwindtraders.com[/url]
  3. Content-Type:text/xml
  4. Content-Length:nnnn
  5. SOAPAction:"urn:northwindtraders.com:PO#UpdatePO"
  6. <SOAP-ENV:Envelope
  7. xmlns:xsi="[url]http://www.3w.org/1999/XMLSchema/instance[/url]"
  8. xmlns:SOAP-ENV="[url]http://schemas.xmlsoap.org/soap/envelope[/url]"
  9. xsi:schemaLocation="[url]http://www.northwindtraders.com/schema/NPOSchema.xsd[/url]">
  10. <SOAP-ENV:Body xsi:type="NorthwindBody">
  11. <UpdatePO>
  12. <orderID>098</orderID>
  13. <customerNumber>999</customerNumber>
  14. <item>89</item>
  15. <quantity>3000</quantity>
  16. </UpdatePO>
  17. </SOAP-ENV:Body>
  18. </SOAP-ENV:Envelope>

2.3 安全机制

REST使用了简单有效的安全模型。REST中很容易隐藏某个资源,只需不发布它的URI;而在资源上也很容易使用一些安全策略,比如可以在每个 URI 针对 4个通用接口设置权限;再者,以资源为中心的 Web服务是防火墙友好的,因为 GET的 意思就是GET, PUT 的意思就是PUT,管理员可以通过堵塞非GET请求把资源设置为只读的,而现在的基于RPC 模型的 SOAP 一律工作在 HTTP 的 POST上。而使用 SOAP RPC模型,要访问的对象名称藏在方法的参数中,因此需要创建新的安全模型。

 三. 使用REST架构

  对于开发人员来说,关心的是如何使用REST架构,这里我们来简单谈谈这个问题。REST带来的不仅仅是一种崭新的架构,它更是带来一种全新的Web开发过程中的思维方式:通过URL来设计系统结构。REST是一套简单的设计原则、一种架构风格(或模式),不是一种具体的标准或架构。到今天REST有很多成功的使用案例,客户端调用也极其方便。

  目前宣称支持REST的Java框架包括以下这些: 
  Restlet(http://www.restlet.org/) 
  Cetia4(https://cetia4.dev.java.net/) 
  Apache Axis2(http://http//ws.apache.org/axis2/) 
  sqlREST(http://sqlrest.sourceforge.net/) 
REST-art(http://rest-art.sourceforge.net/)

下面是我通过Spring3.0写的一个很简单的REST举例。

依赖包请去http://www.springsource.com/获得,Spring3.0于2009年12月发布,在GOOGLE中它的新特性被广泛提及的便是完整的springmvc rest支持。

另:Spring3已经完全采用Java5/6开发和编译构建,因此应该是不再支持Java1.4及更早版本了

说了些题外话,开始正题:

本实例是个简单的“article service”,分服务端和客户端,现在我先说下服务端开发:

注:服务端源代码请在附件里下载,是个maven建的eclipse工程。

web.xml

Xml代码  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  5. version="2.5">
  6. <display-name>Article Web Service</display-name>
  7. <servlet>
  8. <servlet-name>article</servlet-name>
  9. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  10. <load-on-startup>1</load-on-startup>
  11. </servlet>
  12. <servlet-mapping>
  13. <servlet-name>article</servlet-name>
  14. <url-pattern>/*</url-pattern>
  15. </servlet-mapping>
  16. </web-app>

这里声明了名字为“article”的Spring DispatcherServlet,并匹配所有“/*” 的“article”servlet,在Spring 3里,当它发现有 “article” servlet时,它会自动在WEB-INF目录下寻找“article”-servlet.xml,我现在贴出article-servlet.xml 内容:

Xml代码  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:oxm="http://www.springframework.org/schema/oxm"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
  8. http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
  9. <context:component-scan base-package="com.informit.articleservice" />
  10. <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
  11. <bean id="articleXmlView"
  12. class="org.springframework.web.servlet.view.xml.MarshallingView">
  13. <constructor-arg>
  14. <bean class="org.springframework.oxm.xstream.XStreamMarshaller">
  15. <property name="autodetectAnnotations" value="true"/>
  16. </bean>
  17. </constructor-arg>
  18. </bean>
  19. </beans>

这里它做了几件事情:

1.Spring会扫描com.informit.articleservice包或他的子包来作为他的servlet组件
2.声明了一个articleXmlView bean 为了初始化XStreamMarshaller,这个类会把我们接口中得到结果以XML文档形式展现出来

通过这个配置文档,我们声明我们的类和注释后,spring自己会照顾rest,现在我们看下Spring MVC ArticleController class:

Java代码  
  1. package com.informit.articleservice;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.validation.BindingResult;
  7. import org.springframework.web.bind.annotation.PathVariable;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RequestMethod;
  10. import org.springframework.web.bind.annotation.RequestParam;
  11. import org.springframework.web.servlet.ModelAndView;
  12. import com.informit.articleservice.model.Article;
  13. import com.informit.articleservice.model.Category;
  14. import com.informit.articleservice.service.ArticleService;
  15. @Controller
  16. public class ArticleController {
  17. @Autowired
  18. private ArticleService articleService;
  19. @RequestMapping(value = "/article/{category}/{id}", method = RequestMethod.GET)
  20. public ModelAndView loadArticle(@PathVariable String category, @PathVariable int id,
  21. @RequestParam(value = "mode", required = false) String mode) {
  22. // Load the article based on the mode
  23. Article article = null;
  24. System.out.println("mode:" + mode);
  25. if (mode != null && mode.equalsIgnoreCase("summary")) {
  26. article = articleService.getArticleSummary(category, id);
  27. } else {
  28. article = articleService.getArticle(category, id);
  29. }
  30. // Create and return a ModelAndView that presents the article to the caller
  31. ModelAndView mav = new ModelAndView("articleXmlView", BindingResult.MODEL_KEY_PREFIX + "article", article);
  32. return mav;
  33. }
  34. @RequestMapping(value = "/article", method = RequestMethod.GET)
  35. public ModelAndView loadArticleCategories() {
  36. List<Category> categories = articleService.loadCategories();
  37. ModelAndView mav = new ModelAndView("articleXmlView", BindingResult.MODEL_KEY_PREFIX + "category", categories);
  38. return mav;
  39. }
  40. @RequestMapping(value = "/article", method = RequestMethod.DELETE)
  41. public ModelAndView delArticleCategories() {
  42. List<Category> categories = articleService.loadCategories();
  43. System.out.println("delete oprate");
  44. ModelAndView mav = new ModelAndView("articleXmlView", BindingResult.MODEL_KEY_PREFIX + "category", categories);
  45. return mav;
  46. }
  47. @RequestMapping(value = "/addarticle", method = RequestMethod.POST)
  48. public ModelAndView addArticleCategories(Category category) {
  49. List<Category> categories = new ArrayList<Category>();
  50. System.out.println(category.getName());
  51. categories.add(category);
  52. ModelAndView mav = new ModelAndView("articleXmlView", BindingResult.MODEL_KEY_PREFIX + "category", categories);
  53. return mav;
  54. }
  55. @RequestMapping(value = "/addarticle/{name}", method = RequestMethod.POST)
  56. public ModelAndView addArticleCategoriesForName(@PathVariable String name) {
  57. List<Category> categories = new ArrayList<Category>();
  58. Category category = new Category();
  59. category.setName(name);
  60. System.out.println(name);
  61. categories.add(category);
  62. ModelAndView mav = new ModelAndView("articleXmlView", BindingResult.MODEL_KEY_PREFIX + "category", categories);
  63. return mav;
  64. }
  65. }

ArticleController class 被@Controller注释后,他会自动作为一个Spring MVC controller class,而@RequestMapping annotation(注释)会告诉Spring有关的URI,比如“/article”。“method = RequestMethod.GET”代表以GET方式传递HTTP请求,

我们可以通过http://localhost:8080/articleservice/article看下效果。

而"/article/{category}/{id}"代表{category}和{id}为传递进来的值作为URI,如通过http://localhost:8080/articleservice/article/kk/22来把相应的值传递进方法中,mailto:â@PathVariable String category”即为category赋值,@RequestParam(value = "mode", required = false) String mode即可获得mode参数值,如:http://localhost:8080/articleservice/article/kk/22?mode=jizhong

然后处理逻辑后再ModelAndView中通过“articleXmlView”bean把loadArticle()方法的article对象或loadArticleCategories()方法的list返回

下面给出业务逻辑类:

Java代码  
  1. package com.informit.articleservice.service;
  2. import java.util.List;
  3. import com.informit.articleservice.model.Article;
  4. import com.informit.articleservice.model.Category;
  5. public interface ArticleService {
  6. public Article getArticle(String category, int id);
  7. public Article getArticleSummary(String category, int id);
  8. public List<Category> loadCategories();
  9. }
Java代码  
  1. package com.informit.articleservice.service;
  2. import java.util.ArrayList;
  3. import java.util.Date;
  4. import java.util.List;
  5. import org.springframework.stereotype.Service;
  6. import com.informit.articleservice.model.Article;
  7. import com.informit.articleservice.model.Category;
  8. @Service("articleService")
  9. public class ArticleServiceImpl implements ArticleService {
  10. @Override
  11. public Article getArticle(String category, int id) {
  12. return new Article(1, "My Article", "Steven Haines", new Date(), "A facinating article",
  13. "Wow, aren't you enjoying this article?");
  14. }
  15. @Override
  16. public Article getArticleSummary(String category, int id) {
  17. return new Article(1, "My Article", "Steven Haines", new Date(), "A facinating article");
  18. }
  19. public List<Category> loadCategories() {
  20. List<Category> categories = new ArrayList<Category>();
  21. categories.add(new Category("fun"));
  22. categories.add(new Category("work"));
  23. return categories;
  24. }
  25. }

这里我列出使用的两个实体类:

Java代码  
  1. package com.informit.articleservice.model;
  2. import java.io.Serializable;
  3. import java.util.Date;
  4. import com.thoughtworks.xstream.annotations.XStreamAlias;
  5. @XStreamAlias( "article" )
  6. public class Article implements Serializable
  7. {
  8. private static final long serialVersionUID = 1L;
  9. private int id;
  10. private String title;
  11. private String author;
  12. private Date publishDate;
  13. private String summary;
  14. private String body;
  15. public Article()
  16. {
  17. }
  18. public Article( int id, String title, String author, Date publishDate, String summary, String body )
  19. {
  20. this.id = id;
  21. this.title = title;
  22. this.author = author;
  23. this.publishDate = publishDate;
  24. this.summary = summary;
  25. this.body = body;
  26. }
  27. public Article( int id, String title, String author, Date publishDate, String summary )
  28. {
  29. this.id = id;
  30. this.title = title;
  31. this.author = author;
  32. this.publishDate = publishDate;
  33. this.summary = summary;
  34. }
  35. public int getId()
  36. {
  37. return id;
  38. }
  39. public void setId( int id )
  40. {
  41. this.id = id;
  42. }
  43. public String getTitle()
  44. {
  45. return title;
  46. }
  47. public void setTitle( String title )
  48. {
  49. this.title = title;
  50. }
  51. public String getAuthor()
  52. {
  53. return author;
  54. }
  55. public void setAuthor( String author )
  56. {
  57. this.author = author;
  58. }
  59. public Date getPublishDate()
  60. {
  61. return publishDate;
  62. }
  63. public void setPublishDate( Date publishDate )
  64. {
  65. this.publishDate = publishDate;
  66. }
  67. public String getSummary()
  68. {
  69. return summary;
  70. }
  71. public void setSummary( String summary )
  72. {
  73. this.summary = summary;
  74. }
  75. public String getBody()
  76. {
  77. return body;
  78. }
  79. public void setBody( String body )
  80. {
  81. this.body = body;
  82. }
  83. }
Java代码  
  1. package com.informit.articleservice.model;
  2. import com.thoughtworks.xstream.annotations.XStreamAlias;
  3. @XStreamAlias("category")
  4. public class Category {
  5. private String name;
  6. public Category() {
  7. }
  8. public Category(String name) {
  9. this.name = name;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. }

他们被@XStreamAlias()注释了,那么他们在XML文档下的显示别名就以其属性名显示,如"title"

至此,服务端配置就完成了,您可以通过连接:

http://localhost:8080/articleservice/article

http://localhost:8080/articleservice/article/fun/1

http://localhost:8080/articleservice/article/fun/1?mode=summary

因为篇幅,下一讲我将专门写客户端调用的工程: RESTful web services using Spring's RestTemplate class

注:附件里提供源码,有兴趣的朋友下载看吧

下篇的文章地址为:http://www.iteye.com/admin/blogs/600680

  • REST_cn.pdf (792 KB)
  • 下载次数: 726
  • articleservice_20100223.rar (12.8 KB)
  • 下载次数: 516

转载于:https://www.cnblogs.com/chenying99/archive/2012/03/30/2424460.html

RESTful Web Services in Spring 3(上)转载相关推荐

  1. RESTful Web Services in Spring 3(下)转载

    上一篇我主要发了RESTful Web Services in Spring 3的服务端代码,这里我准备写客户端的代码. 上篇得连接地址为:http://yangjizhong.iteye.com/b ...

  2. Jboss RestEasy构建简单的RESTful Web Services示例(1)

    2019独角兽企业重金招聘Python工程师标准>>> 项目上要用到webservice,鉴于现在restful webservice比较流行,打算用restful来建立webser ...

  3. RESTful Web Services初探

    RESTful Web Services初探 作者:杜刚 近几年,RESTful Web Services渐渐开始流行,大量用于解决异构系统间的通信问题.很多网站和应用提供的API,都是基于RESTf ...

  4. 使用Hibernate-Validator优雅的验证RESTful Web Services的参数

    何为Hibernate-Validator 在RESTful Web Services的接口服务中,会有各种各样的入参,我们不可能完全不做任何校验就直接进入到业务处理的环节,通常我们会有一个基础的数据 ...

  5. JAX-RS(Java API for RESTful Web Services)常用注解

    为什么80%的码农都做不了架构师?>>>    概述 JAX-RS(Java API for RESTful Web Services)是Java 提供用于开发RESTful Web ...

  6. cxf开发Restful Web Services

    一.restful web services rest全称是Representation State Transfer(表述性状态转移).它是一种软件架构风格,只是提供了一组设计原则和约束条件.在re ...

  7. jboss7 Java API for RESTful Web Services (JAX-RS) 官方文档

    原文:https://docs.jboss.org/author/display/AS7/Java+API+for+RESTful+Web+Services+(JAX-RS) Content Tuto ...

  8. java官方 jax rs_jboss7 Java API for RESTful Web Services (JAX-RS) 官方文档

    原文:https://docs.jboss.org/author/display/AS7/Java+API+for+RESTful+Web+Services+(JAX-RS) Content Tuto ...

  9. Spring MVC 4 RESTFul Web Services CRUD例子(带源码)【这才是restful,超经典】

    [本系列其他教程正在陆续翻译中,点击分类:spring 4 mvc 进行查看.源码下载地址在文章末尾.] [翻译 by 明明如月 QQ 605283073] 原文地址:http://websystiq ...

  10. 《RESTful Web Services》第一章 使用统一接口

    序言 HTTP是一种应用层协议.SOAP和一些Ajax Web框架都将HTTP作为一种传输信息的协议,难以充分利用HTTP层的基础设施. 1.2 如何保持交互的可见性 可见性是HTTP的一个核心特征. ...

最新文章

  1. 分类的评估标准_机器学习:模型评估之评估指标
  2. 如果我们能够数字化,那么我们能够give away
  3. Mycat - 数据库分库分表中间件,国内最活跃的、性能最好的开源数据库中间件
  4. java 假设当前时间_Java中与日期和时间相关的类和方法
  5. Request/Response
  6. yum: command not found
  7. 算法证明题 8.9 HITTING SET
  8. word粘贴超出页面怎么办
  9. 什么是ASP.NET?
  10. 逆势马丁网格策略交易演示
  11. Technical support(技术支持)
  12. 山大青岛计算机学院郑雯,山东大学自招700余人过线 面试将刷掉20%考生
  13. jwt生成token与解析token
  14. 安装gtsam遇到的错误
  15. Twitter – 媒体与沉默的大多数
  16. 消费与储蓄的决定-中国视角下的宏观经济
  17. 长隆大马戏机器人_世界顶尖马戏开启广州“魔幻圣诞月”
  18. java 获取今天是几号
  19. 数字影像技术:第二期DIT QTAKE 线下实训营招生及课前答疑(北京站)
  20. 无限城为什么服务器繁忙,鬼灭之刃:无限城篇不完结,如何不烂尾,第二种结果真是局中局...

热门文章

  1. JS 回车提交,兼容IE、火狐、Opera、Chrome、Safari
  2. windbg调试环境变量记录
  3. 第八篇: UpdateProgress 控件--显示正在处理中的信息
  4. (转载)突然就看懂了《大话西游》
  5. linux 时间服务器安装配置
  6. 软件开发工具--自考2019年4月
  7. 11.string容器
  8. My third homework
  9. iOS阶段学习第31天笔记(UINavigationBar介绍)
  10. [译] libvirt 虚机的生命周期 (Libvirt Virtual Machine Lifecycle)