當REST遇上Java

當REST遇上Java

今天我們來一起學習和了解rest的相關知識,做後臺程序的或多說少都聽說過甚至使用過rest技術,但很多同學可能都沒有真正瞭解什麼是rest,我通過查詢資料,copy總結出了一篇文章,這裏我們就一起來學習一下。

rest的前世今生

一、起源
REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的。
這裏寫圖片描述

Fielding是一個非常重要的人,他是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的作者之一、Apache基金會的第一任主席。所以,他的這篇論文一經發表,就引起了關注,並且立即對互聯網開發產生了深遠的影響。
他這樣介紹論文的寫作目的:

“本文研究計算機科學兩大前沿—-軟件和網絡—-的交叉點。長期以來,軟件研究主要關注軟件設計的分類、設計方法的演化,很少客觀地評估不同的設計選擇對系統行爲的影響。而相反地,網絡研究主要關注系統之間通信行爲的細節、如何改進特定通信機制的表現,常常忽視了一個事實,那就是改變應用程序的互動風格比改變互動協議,對整體表現有更大的影響。我這篇文章的寫作目的,就是想在符合架構原理的前提下,理解和評估以網絡爲基礎的應用軟件的架構設計,得到一個功能強、性能好、適宜通信的架構。”
(This dissertation explores a junction on the frontiers of two research disciplines in computer science: software and networking. Software research has long been concerned with the categorization of software designs and the development of design methodologies, but has rarely been able to objectively evaluate the impact of various design choices on system behavior. Networking research, in contrast, is focused on the details of generic communication behavior between systems and improving the performance of particular communication techniques, often ignoring the fact that changing the interaction style of an application can have more impact on performance than the communication protocols used for that interaction. My work is motivated by the desire to understand and evaluate the architectural design of network-based application software through principled use of architectural constraints, thereby obtaining the functional, performance, and social properties desired of an architecture. )

二、名稱
Fielding將他對互聯網軟件的架構原則,定名爲REST,即Representational State Transfer的縮寫。我對這個詞組的翻譯是”表現層狀態轉化”。
如果一個架構符合REST原則,就稱它爲RESTful架構。
要理解RESTful架構,最好的方法就是去理解Representational State Transfer這個詞組到底是什麼意思,它的每一個詞代表了什麼涵義。如果你把這個名稱搞懂了,也就不難體會REST是一種什麼樣的設計。
三、資源(Resources)
REST的名稱”表現層狀態轉化”中,省略了主語。”表現層”其實指的是”資源”(Resources)的”表現層”。
所謂”資源”,就是網絡上的一個實體,或者說是網絡上的一個具體信息。它可以是一段文本、一張圖片、一首歌曲、一種服務,總之就是一個具體的實在。你可以用一個URI(統一資源定位符)指向它,每種資源對應一個特定的URI。要獲取這個資源,訪問它的URI就可以,因此URI就成了每一個資源的地址或獨一無二的識別符。
所謂”上網”,就是與互聯網上一系列的”資源”互動,調用它的URI。
四、表現層(Representation)
“資源”是一種信息實體,它可以有多種外在表現形式。我們把”資源”具體呈現出來的形式,叫做它的”表現層”(Representation)。
比如,文本可以用txt格式表現,也可以用HTML格式、XML格式、JSON格式表現,甚至可以採用二進制格式;圖片可以用JPG格式表現,也可以用PNG格式表現。
URI只代表資源的實體,不代表它的形式。嚴格地說,有些網址最後的”.html”後綴名是不必要的,因爲這個後綴名錶示格式,屬於”表現層”範疇,而URI應該只代表”資源”的位置。它的具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段纔是對”表現層”的描述。


五、狀態轉化(State Transfer)

訪問一個網站,就代表了客戶端和服務器的一個互動過程。在這個過程中,勢必涉及到數據和狀態的變化。
互聯網通信協議HTTP協議,是一個無狀態協議。這意味着,所有的狀態都保存在服務器端。因此,如果客戶端想要操作服務器,必須通過某種手段,讓服務器端發生”狀態轉化”(State Transfer)。而這種轉化是建立在表現層之上的,所以就是”表現層狀態轉化”。
客戶端用到的手段,只能是HTTP協議。具體來說,就是HTTP協議裏面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源。
六、綜述
綜合上面的解釋,我們總結一下什麼是RESTful架構:
  (1)每一個URI代表一種資源;
  (2)客戶端和服務器之間,傳遞這種資源的某種表現層;
  (3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現”表現層狀態轉化”。

以上是從別人博客引用 阮一峯-理解RESTful架構,此人是業界知名人士,博客質量很高,大家可以長期關注,我關注有很長時間了,此人以前是經濟學博士,現在就職於阿里,任前端專家(我瞭解的八卦,呵呵)

總結:
根據前面的介紹大家應該基本對REST有了一定的瞭解,現在簡單的舉例子說明一下,其實rest就是讓方法以及參數一起有一定的語義,如下

設計一個根據id獲取用戶的rest風格的api
http://www.jjshome.com/getuser/778123
根據刪除某個類別
http://www.jjshome.com/delete/category/1
這個是csdn博客編輯的url,也有rest的風格
http://write.blog.csdn.net/postedit/49662031

java中的REST

  Java平臺能夠像現在這樣強大,很大程度上是因爲有了社區的參與,java中有一個對規範進行管理的社區 JCP,所有的新技術都會在先向這裏提交申請,JSR(Java規範請求,Java Specification Requests)正式規範文檔,描述被提議加入到Java體系中的的規範和技術,然後通過委員會投票(JCP Executive Committee),當提議通過後JCP會提供一個參考實現。

JCP主要就是制定標準,然後提供一個參考實現,然後各大軟件開發商就會按照規範提供自己的實現,將軟件發佈到互聯網之後就會有這種對比,一般來講最好用、最方便的實現就是我們的Java程序員都在使用的軟件包。
這裏寫圖片描述

比如說Servlet規範,我們通常用的是tomcat,因爲Apache軟件協會提供了tomcat的實現,實現了JSP/Servlet等一系列JavaEE規範,如JNDI、WebSocket等等,其實我們寫的Servlet程序只是針對規範寫的,並沒有針對某個實現,也就是說我們只依賴了接口,誰實現了接口我們的程序就能在哪裏運行,比如還有Jetty、JBoss、Weblogic等很多容器都實現了Servlet規範,所有我們的程序也能直接放到這些容器中去運行,包括我們這裏講的Java REST技術,也有標準,下面是相關的JSR定義

339是規範編號,JAX-RS是名稱,後面是相關描述
JSR 339: JAX-RS 2.0: The Java API for RESTful Web Services

Java的rest常見實現有 Jersey ,CXF(Apache提供),JBoss的RestEasy, Dubbo的rest使用了RestEasy的實現

基本使用

rest的使用我們這裏主要描述規範中有定義的,不針對特定實現,看一個基本使用

@Path("/users")
public class UserResource {

    @GET
    @Path("getUser/{username}")
    @Produces("text/xml")
    public String getUser(@PathParam("username") String userName) {
        ...
    }
}

然後是在web.xml中添加配置,然後只需要配置讓rest框架加載這個類就可以了,這裏提供的是resteasy的,其他框架也差不多

    <!-- Auto scan REST service -->
    <context-param>
        <param-name>resteasy.scan</param-name>
        <param-value>true</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>resteasy-servlet</servlet-name>
        <servlet-class>
            org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
        </servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>resteasy-servlet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

這就是rest對外提供服務所需要的代碼,類似於Spring的component-scan。

參數獲取示例

寫服務類很重要的一點就是獲取http的請求參數,包括請求的header、cookie、post的數據等等,我們就來看各種獲取參數的方法

@QueryParam用來獲取GET請求的參數,如 http://www.jjshome.com/?username=testuser&age=26 ,@DefaultValue() 用於指定默認值

@Path("smooth")
@GET
public Response smooth(
    @DefaultValue("2") @QueryParam("step") int step,
    @DefaultValue("true") @QueryParam("min-m") boolean hasMin,
    @DefaultValue("true") @QueryParam("max-m") boolean hasMax,
    @DefaultValue("true") @QueryParam("last-m") boolean hasLast,
    @DefaultValue("blue") @QueryParam("min-color") ColorParam minColor,
    @DefaultValue("green") @QueryParam("max-color") ColorParam maxColor,
    @DefaultValue("red") @QueryParam("last-color") ColorParam lastColor) {
    ...
}

@PathParam

@PathParam用於將url的一部分綁定到參數中,比如url:http://server:port/login/415546,可以通過以下方式獲取

  @GET
  @Path("login/{zip}")
  public String login(@PathParam("zip") String id) {
   return "Id is " +id;
  }

@HeaderParam

@HeaderParam用於獲取http頭信息,如獲取瀏覽器信息

@GET
public String callService(@HeaderParam("User-Agent") String whichBrowser) {
...
}

@CookieParam

獲取Cookie的值

@GET

public String callService(@CookieParam("sessionid") String sessionid) {

...
 }



@MatrixParam


@MatrixParam參數,這種參數平時用的不多,單rest提供了直接獲取值得參數,看如下url:http://server:port/login;name=francesco;surname=marchioni

@GET

public String callService(@MatrixParam("name") String name,

                                @MatrixParam("surname") String surname) {

...
 }



@BeanParam @FormParam


以上獲取參數適合參數較少的方法獲取參數,如果參數比較多全部都寫在參數上,代碼就很難看了,來看一個示例

@POST
public void post(@BeanParam MyBeanParam beanParam, String entity) {
    final String pathParam = beanParam.getPathParam(); // contains injected path parameter "p"
    ...
}

把所有參數都放到一個對象裏,下面是MyBeanParam的聲明,可以在MyBeanParam中使用前面的每一種註解獲取參數

public class MyBeanParam {
    @PathParam("p")
    private String pathParam;

    @MatrixParam("m")
    @Encoded
    @DefaultValue("default")
    private String matrixParam;

    @HeaderParam("header")
    private String headerParam;

    private String queryParam;

    public MyBeanParam(@QueryParam("q") String queryParam) {
        this.queryParam = queryParam;
    }

    public String getPathParam() {
        return pathParam;
    }
    ...
}

@Context註解


可以用@Context屬性來將這些對象注入到屬性中

javax.ws.rs.core.HttpHeaders
javax.ws.rs.core.UriInfo
javax.ws.rs.core.Request
javax.servlet.HttpServletRequest
javax.servlet.HttpServletResponse
javax.servlet.ServletConfig
javax.servlet.ServletContext
javax.ws.rs.core.SecurityContext

rest 過濾器和攔截器

過濾器
一共有兩種過濾器
ContainerRequestFilter:方法調用之前調用
ContainerResponseFilter :方法調用之後調用

這裏是一個權限驗證的攔截器,實現ContainerRequestFilter

import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

public class AuthorizationRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext)
                    throws IOException {

        final SecurityContext securityContext =
                    requestContext.getSecurityContext();
        if (securityContext == null ||
                    !securityContext.isUserInRole("privileged")) {

                requestContext.abortWith(Response
                    .status(Response.Status.UNAUTHORIZED)
                    .entity("User cannot access the resource.")
                    .build());
        }
    }
}

一個爲所有Response添加http頭信息的ContainerResponseFilter

import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;

public class PoweredByResponseFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
        throws IOException {

            responseContext.getHeaders().add("X-Powered-By", "baidu webengin :-)");
    }
}

SpringMVC的REST

現在SpringMVC非常流行,可能大家都使用了rest,類似於前面的那種方式,springMVC並沒有實現JSR-303(java rest標準),只是提供了rest風格的使用方式,其實springMVC的rest也做的很好用了,只是沒有那麼多的接口,比如@PathVariable ,接口的全路徑是org.springframework.web.bind.annotation.PathVariable,這些都是Spring自己提供的實現

關於Dubbo和Dubbox

dubbo很早就就沒有維護了,版本只到2.5.3就停止了,團隊也早就解散了,根據我查的一些資料來看,阿里內部並沒有大規模的使用dubbo,而是繼續使用之前的分佈式技術,沒有大規模的用dubbo並不是代表dubbo不好,因爲大規模切換使用的技術成本是很大的,如果不是有安全問題或者新技術相對於老技術並沒有顯著的優勢,企業是不會隨便切換的,尤其是考慮到系統的穩定性。

dubbox是對dubbo的維護版本,也添加了一些新的特性,主要是由噹噹網的架構師沈理來維護,最主要的就是添加了rest的支持

沈理:目前在噹噹網的架構部和技術委員會擔任架構師,主要負責噹噹網的SOA實施(即服務化)以及分佈式服務框架的開發。

我的想法
最近在項目中也使用了dubbox提供的rest來進行webservice的開發,因爲我的service是既要給dubbo調用,同時也要給http客戶端調用,於是就設計了一個service,以不同的協議來暴露,分別是 dubbo和rest,如下

<bean id="acctRegisterService" class="com.xx.xx.AcctRegisterServiceImpl" />

<dubbo:service interface="com.xx.xx.IAcctRegisterService" ref="acctRegisterService"  protocol="dubbo"  />

<dubbo:service interface="com.jjshome.umc.api.client.acct.IAcctRegisterService" ref="acctRegisterService" protocol="rest" />

這樣dubbo和http接口都可以同時調用,這樣做確實減少了一些工作量,否則需要同時維護兩個業務相同的service,如果這樣使用,dubbox提供的rest還有點意義,接下來我們來看另一種場景

如果你提供的服務只需要提供http接口,如果使用dubbox提供的rest,我認爲是沒必要的

我們來看一下使用dubbox的rest的優缺點
缺點:如果要以dubbo形式暴露服務,必須實現接口
缺點:dubbox是基於resteasy的,不能換rest框架
缺點:使用的時候發現dubbox的rest還有很多未知問題,而且文檔很少,(已有的文檔都是rest框架提供的使用文檔,而出現問題則都是dubbox和resteasy集成時的問題,基本上要靠看源碼才能解決)

優點:可以同時提供dubbo服務和http服務
優點:可以通過dubbo的註冊中心查看到部署的rest服務

總結:如果僅僅是需要暴露http接口,直接使用 rest框架更直接,更方便,如果是想同時暴露dubbo和rest服務,則可以考慮用dubbox提供的rest

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章