通过本文,我将介绍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. React Native常用组件之ListView
  2. Metasploit编码模块技巧
  3. 第2题——DNA片段
  4. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | 查找 DexFile 对应的C代码 | dalvik_system_DexFile.cpp 分析 )
  5. 数学符号的读法和英文表示
  6. 【报错笔记】项目使用了JSONArray类,导了import net.sf.json.JSONArray;包报错
  7. 如何利用python在yi'ge_【GE查找Python面试题】面试问题:使用 TVM … - 看准网
  8. 关于 HANA CE Function
  9. flex 学习篇 ---- 导航类容器
  10. 10恢复出厂设置_笔记本电脑怎么恢复出厂设置
  11. 快递送不上门,谁的锅?
  12. 如何解决w3wp占用CPU和内存问题
  13. Nginx系列(3):Nginx配置文件nginx.conf中文详解
  14. 手动批量下载ts文件并合并
  15. 你的Android HTTPS真的安全吗?(转载)
  16. 恢复Redis被误删的数据
  17. 彻底删除微软拼音输入法
  18. 百度招聘Android客服端(2)
  19. Arduino Nano技术指标
  20. staf linux运行模式,IBM 自动化测试框架STAF介绍

热门文章

  1. 稳坐CACTI,遥知千里
  2. XXX集团财务决策支持系统——用友财务系统现有的财务报表(系列四)
  3. redis 系列27 Cluster高可用 (2)
  4. C语言中#define的用法
  5. oracle常见sql积累
  6. 十一、JUC包中的锁
  7. 再也不用为word 中表达式的上标和下标发愁了
  8. android ExpandableListView
  9. 月经贴:当落魄的.NET基佬遇上不可一世的JAVA派 developer
  10. Java 创建线程的三种方法比较