REST:JAX-RS 與 Spring

来源:http://www.cnblogs.com/oopsguy/archive/2017/09/11/7503589.html
-Advertisement-
Play Games

或許您正在使用 REST 端點(endpoint)來擺脫 Web 服務和客戶端。如果您是一名 Java 開發人員,您可能已經嘗試過 JAX-RS、Spring REST 或者兩者。但哪一個好用呢?在這篇文章中,我將介紹兩者之間的差異,使用大體相同的代碼進行對比。在之後的博文中,我將向您展示如何輕鬆地... ...


原文https://developer.okta.com/blog/2017/08/09/jax-rs-vs-spring-rest-endpoints
作者:Brian Demers
譯者http://oopsguy.com

或許您正在使用 REST 端點(endpoint)來擺脫 Web 服務和客戶端。如果您是一名 Java 開發人員,您可能已經嘗試過 JAX-RS、Spring REST 或者兩者。但哪一個好用呢?在這篇文章中,我將介紹兩者之間的差異,使用大體相同的代碼進行對比。在之後的博文中,我將向您展示如何輕鬆地使用 Apache Shiro 和 Okta 來保護這些 REST 端點。

模型與 DAO

為了突出重點,我不再介紹本次示例所用的 Maven 依賴。您可以在 Github 上瀏覽完整的源碼,pom 文件應該描述得很清楚了:一個用於 JAX-RS,其他用於 Spring。

首先,我們需要把某些通用部分提取出來。所有示例中都使用到了一個簡單的模型和 DAO(Data Access Object,數據訪問對象)來註冊和管理 Stormtrooper 對象。

public class Stormtrooper {

    private String id;
    private String planetOfOrigin;
    private String species;
    private String type;

    public Stormtrooper() {
        // empty to allow for bean access
    }

    public Stormtrooper(String id, String planetOfOrigin, String species, String type) {
        this.id = id;
        this.planetOfOrigin = planetOfOrigin;
        this.species = species;
        this.type = type;
    }

    ...
    // bean accessor methods

Stormtrooper 對象包含id 和其他屬性:planetOfOriginspeciestype

DAO 介面也很簡單,使用基本的 CRUD 方法和一個額外的 list 方法:

public interface StormtrooperDao {

    Stormtrooper getStormtrooper(String id);

    Stormtrooper addStormtrooper(Stormtrooper stormtrooper);

    Stormtrooper updateStormtrooper(String id, Stormtrooper stormtrooper);

    boolean deleteStormtrooper(String id);

    Collection<Stormtrooper> listStormtroopers();
}

StormtrooperDao 的具體實現對於這些示例來說並不重要,如果您感興趣,可以查看 DefaultStormtrooperDao 的代碼,該代碼生成了 50 個隨機的 Stormtrooper。

嘗試 Spring

我們提取了通用部分,現在可以開始 Spring 示例了。這是一個再簡單不過的 Spring Boot 應用程式:

@SpringBootApplication
public class SpringBootApp {

    @Bean
    protected StormtrooperDao stormtrooperDao() {
        return new DefaultStormtrooperDao();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApp.class, args);
    }
}

有幾點要指出的是:

  • @SpringBootApplication 註解設置啟用 Spring 自動配置和掃描 classpath 中的組件
  • @BeanDefaultStormtrooperDao 實例綁定到 StormtrooperDao 介面
  • main 方法使用 SpringApplication.run() 輔助方法來引導應用程式

Spring 控制器

接下來,我們要實現 REST 端點,也可以說是 Spring 中的一個 Controller。我們使用該類來將 DAO 映射到傳入的 HTTP 請求。

@RestController
@RequestMapping("/troopers")
public class StormtrooperController {

    private final StormtrooperDao trooperDao;

    @Autowired
    public StormtrooperController(StormtrooperDao trooperDao) {
        this.trooperDao = trooperDao;
    }

    @GetMapping
    public Collection<Stormtrooper> listTroopers() {
        return trooperDao.listStormtroopers();
    }

    @GetMapping("/{id}")
    public Stormtrooper getTrooper(@PathVariable("id") String id) throws NotFoundException {

        Stormtrooper stormtrooper = trooperDao.getStormtrooper(id);
        if (stormtrooper == null) {
            throw new NotFoundException();
        }
        return stormtrooper;
    }

    @PostMapping
    public Stormtrooper createTrooper(@RequestBody Stormtrooper trooper) {
        return trooperDao.addStormtrooper(trooper);
    }

    @PostMapping("/{id}")
    public Stormtrooper updateTrooper(@PathVariable("id") String id,
                                      @RequestBody Stormtrooper updatedTrooper) throws NotFoundException {
        return trooperDao.updateStormtrooper(id, updatedTrooper);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(value = HttpStatus.NO_CONTENT)
    public void deleteTrooper(@PathVariable("id") String id) {
        trooperDao.deleteStormtrooper(id);
    }
}

讓我們來分解以下代碼:

@Controller
@RequestMapping("/troopers")
public class StormtroooperController {

@RestController@Controller@ResponseBody 的快捷註解,它將此類標記為在 classpath 掃描期間要發現的 Web 組件。類級別的 @RequestMapping 註解定義了用於此類中任何 RequestMapping 註解的基本路徑映射。示例中,此類中的所有端點將以 URL /troopers 為開頭。

@PostMapping("/{id}")
public @ResponseBody Stormtrooper updateTrooper(@PathVariable("id") String id,
                                                @RequestBody Stormtrooper updatedTrooper) throws NotFoundException {
    return trooperDao.updateStormtrooper(id, updatedTrooper);
}

PostMapping@RequestMapping 註解的 POST 別名,它有許多選項,此示例只使用一小部分:

  • @PathVariable("id") 結合使用的 path = "/{id}" 將 URL 路徑中的 {id} 部分映射到給定的方法參數 - 示例URL:/troopers/FN-2187
  • value = HttpStatus.NO_CONTENT 設置需要返回的 HTTP 響應代碼,即 204 狀態碼

使用了 @RequestBody 註解的方法參數將在被傳遞給該方法之前從 HTTP 請求反序列化。使用 @ResponseBody 註解(或簡單地使用 @RestController),返回值直接被序列化為 HTTP 響應,同時將繞過所有 MVC 模板。

在此代碼塊中,updateTrooper() 方法接收了對 /trooper/{id} 的 HTTP POST 請求,此請求包含了一個序列化的 Stormtrooper(JSON)。如果請求路徑為 /troopers/FN-2187,路徑的 id 部分將被分配給方法的 id 參數。之後將更新後的 Stormtrooper 對象返回並序列化為 HTTP 響應。

在上面的例子中,我們簡單地使用 POST 應用於創建和更新方法。為了讓這個例子更加美觀簡潔,實際上 DAO 實現並不做部分更新,所以應該是一個 PUT。看看這篇博文,瞭解更多關於什麼時候使用 PUT 和 POST

運行 Spring 示例

要運行此示例,請下載源碼,切換到 spring-boot 目錄下,使用 mvn spring-boot:run 啟動應用程式,並向伺服器發出請求。

要得到所有 Stormtrooper 的列表,只需要向 /troopers 發出請求。

$ curl http://localhost:8080/troopers

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Tue, 08 Nov 2016 20:33:36 GMT
Transfer-Encoding: chunked
X-Application-Context: application

[
    {
        "id": "FN-2187",
        "planetOfOrigin": "Unknown",
        "species": "Human",
        "type": "Basic"
    },
    {
        "id": "FN-0984",
        "planetOfOrigin": "Coruscant",
        "species": "Human",
        "type": "Aquatic"
    },
    {
        "id": "FN-1253",
        "planetOfOrigin": "Tatooine",
        "species": "Unidentified",
        "type": "Sand"
    },
    ...
]

要獲取單個 Stormtrooper,可以利用它的 ID:

$ curl http://localhost:8080/troopers/FN-2187

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Tue, 08 Nov 2016 20:38:53 GMT
Transfer-Encoding: chunked
X-Application-Context: application

{
    "id": "FN-2187",
    "planetOfOrigin": "Unknown",
    "species": "Human",
    "type": "Basic"
}

相當簡單吧?現在您可以使用 Ctrl-C 來停止伺服器,並轉到下一個示例。

JAX-RS

我們在 JAX-RS 示例中使用相同的模型和 DAO,我們所需要做的只有更改 StormtroooperController 類的註解。

由於 JAX-RS 是一個 API 規範,您需要選擇一個實現,在本示例中,我們將使用 Jersey 作為實現。雖然可以創建一個沒有直接依賴於特定 JAX-RS 實現的 JAX-RS 應用程式,但這將使得示例更加啰嗦。

我選擇 Jersey 有幾個原因,主要是因為我可以不用繞圈子就可以輕鬆地獲得簡單的依賴註入,畢竟我們是把它在和 Spring 做對比。Apache Shiro 有一個示例,可在 JerseyRestEasyApache CXF 上運行相同的代碼,如果你感興趣不妨看一看。

此示例與 Spring Boot 不同之處在於,它打包成 WAR,而 Spring Boot 是單個 JAR。此示例也可以打包進可執行的 jar 中,但此內容不在本文範圍之內。

在 JAX-RS 中與 SpringBootApplication 相當的是一個 Application 類。Jersey 的 Application 子類 ResourceConfig 添加了一些便捷的實用方法。以下代碼配置 classpath 掃描以檢測我們的各個資源類,並將 DefaultStormtrooperDao 實例綁定到 StromtrooperDao 介面。

@ApplicationPath("/")
public class JaxrsApp extends ResourceConfig {

    public JaxrsApp() {

        // scan the resources package for our resources
        packages(getClass().getPackage().getName() + ".resources");

        // use @Inject to bind the StormtrooperDao
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(stormtrooperDao()).to(StormtrooperDao.class);
            }
        });
    }

    private StormtrooperDao stormtrooperDao() {
        return new DefaultStormtrooperDao();
    }
}

另外要指出的是,在上面的類中,@ApplicationPath 註解將這個類標記為一個 JAX-RS 應用程式並綁定到一個特定的 url 路徑,這匹配了上面的 Spring 例子,我們只使用了根路徑:/。資源包中檢測到的每個資源都將被追加到該基本路徑。

JAX-RS 資源實現看起來非常類似於上述的 Spring 版本(重命名為 StormtroooperResource,以符合命名約定):

@Path("/troopers")
@Produces("application/json")
public class StormtroooperResource {

    @Inject
    private StormtrooperDao trooperDao;

    @Path("/{id}")
    @GET
    public Stormtrooper getTrooper(@PathParam("id") String id) throws NotFoundException {

        Stormtrooper stormtrooper = trooperDao.getStormtrooper(id);
        if (stormtrooper == null) {
            throw new NotFoundException();
        }
        return stormtrooper;
    }

    @POST
    public Stormtrooper createTrooper(Stormtrooper trooper) {
        return trooperDao.addStormtrooper(trooper);
    }

    @Path("/{id}")
    @POST
    public Stormtrooper updateTrooper(@PathParam("id") String id,
                                      Stormtrooper updatedTrooper) throws NotFoundException {
        return trooperDao.updateStormtrooper(id, updatedTrooper);
    }

    @Path("/{id}")
    @DELETE
    public void deleteTrooper(@PathParam("id") String id) {
        trooperDao.deleteStormtrooper(id);
    }

    @GET
    public Collection<Stormtrooper> listTroopers() {
        return trooperDao.listStormtroopers();
    }
}

我們先來分解以下片段:

@Path("/troopers")
@Produces("application/json")
public class StormtroooperResource {

類似於上面的 Spring 示例,類級別上的 @Path 表示此類中的每個註解方法都將位於 /troopers 基本路徑下。@Produces 註解定義了預設響應內容類型(除非被其他方法的註解所覆蓋)。

與 Spring 示例不同,其中 @RequestMapping 註解定義了請求的路徑、方法和其他屬性,在 JAX-RS 資源中,每個屬性都使用單獨的註解。與上述類似,如果我們分解了 updateTrooper() 方法:

@Path("/{id}")
@POST
public Stormtrooper updateTrooper(@PathParam("id") String id,
                                  Stormtrooper updatedTrooper) throws NotFoundException {
    return trooperDao.updateStormtrooper(id, updatedTrooper);
}

我們看到 @Path("/{id}") 以及 @PathParam("id") 允許將路徑的 id 部分轉換為方法參數。與 Spring 示例不同的是,Stromtrooper 參數和返回值不需要額外的註解,由於此類上的 @Produces("application/json") 註解,它們將自動序列化/反序列化為 JSON。

運行 JAX-RS 示例

進入 Jersey 目錄,使用 maven 命令:mvn jetty:run 運行此示例。

發出與上述相同的兩個請求,我們可以發出 GET 請求列出所有 trooper:

$ curl http://localhost:8080/troopers

HTTP/1.1 200 OK
Content-Length: 3944
Content-Type: application/json
Date: Tue, 08 Nov 2016 21:57:55 GMT
Server: Jetty(9.3.12.v20160915)

[
    {
        "id": "FN-2187",
        "planetOfOrigin": "Unknown",
        "species": "Human",
        "type": "Basic"
    },
    {
        "id": "FN-0064",
        "planetOfOrigin": "Naboo",
        "species": "Nikto",
        "type": "Sand"
    },
    {
        "id": "FN-0069",
        "planetOfOrigin": "Hoth",
        "species": "Twi'lek",
        "type": "Basic"
    },
    {
        "id": "FN-0169",
        "planetOfOrigin": "Felucia",
        "species": "Kel Dor",
        "type": "Jump"
    },

    ...

或者 GET 一個特定的資源:

$ curl http://localhost:8080/troopers/FN-2187

HTTP/1.1 200 OK
Content-Length: 81
Content-Type: application/json
Date: Tue, 08 Nov 2016 22:00:02 GMT
Server: Jetty(9.3.12.v20160915)

{
    "id": "FN-2187",
    "planetOfOrigin": "Unknown",
    "species": "Human",
    "type": "Basic"
}

現在我們已經看到了基本相同的代碼在 Spring 和 JAX-RS 應用程式中運行,只需更改註解即可。我更喜歡 JAX-RS 的註解,他們更簡潔。既然如此,為什麼要在兩者選擇呢?Jersey 和 RestEasy 都支持 Spring(以及 Guice 和 CDI/Weld)。讓我們來創建一個結合了這兩者的第三個例子。

JAX-RS 與 Spring 整合

針對此示例,我們需要三個類:Spring Boot 應用類、Jersey 配置類和我們的資源類。

我們的 SpringBootAppStormtrooperResource 類與之前的版本相同,唯一的區別就是 Jersey 配置類:

@Component
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {

        // scan the resources package for our resources
        packages(getClass().getPackage().getName() + ".resources");
    }
}

該類與之前的示例有點類似。首先,您可能註意到了用於標記此類由 Spring 管理的 `@Configuration 註解。剩下的就是指示 Jersey 再次掃描資源包,其餘的都是您的處理邏輯。

進入 spring-jaxrs 目錄中,使用 mvn spring-boot:run 命令啟動此示例。

Spring 與 JAX-RS 對照表

為了幫助您在 Spring 和 JAX-RS 的之間作出區分,這裡給出了一份對照表。儘管不是很詳盡,但它包含最常見的註解。

Spring Annotation JAX-RS Annotation
@RequestMapping(path = "/troopers") @Path("/troopers")
@PostMapping @POST
@PutMapping @PUT
@GetMapping @GET
@DeleteMapping @DELETE
@ResponseBody N/A
@RequestBody N/A
@PathVariable("id") @PathParam("id")
@RequestParam("xyz") @QueryParam("xyz")
@RequestParam(value="xyz") @FormParam("xyz")
@RequestMapping(produces = {"application/json"}) @Produces("application/json")
@RequestMapping(consumes = {"application/json"}) @Consumes("application/json")

何時在 Spring 上使用 JAX-RS?

如果你已經是一個 Spring 用戶,就使用 Spring 吧。如果你正在創建一個對象 JSON/XML REST 層,那麼您選擇的 DI 框架(如 Spring、Guice 等)支持 JAX-RS 資源可能是一個不錯的選擇。伺服器端渲染頁面並不是 JAX-RS 規範的一部分(雖然它是擴展支持的)。我曾在 Jersey 中使用了 Thymeleaf 視圖,但我認為這是 Spring MVC 該做的。

目前為止,我們還沒有把 Spring Boot 應用程式與 WAR 打包的應用程式進行詳細地對比。Dropwizard(使用嵌入式 Jetty 容器和 Jersey)可能是與 Spring Boot 應用程式最接近的。希望這篇文章能給你帶來一些靈感,您可以做自己的對比。如果您有任何問題,歡迎 Twitter @briandemers!

示例代碼

https://github.com/oktadeveloper/jaxrs-spring-blog-example


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 近期逆向一個程式,發現有保護措施,不能載入。用machoview後,發現有__RESTRICT段,因此改為RRSTRICT。 先前用iphone4調試,沒有問題,只是調試過程中老是卡死,要等個半天才能有反應。因此換了iphone5s,9.3.3。安裝後,把修改過的二進位scp到設備,發現閃退了。莫名 ...
  • 因為本人對web機制瞭解較少,在使用C/libcurl中遇到了很多問題。主要的解決方式: 1,在網頁源碼中找到提交form的定位。也就是網頁文件對應form中的action屬性。往往都是提交到jsp或者php等腳本中運行的,而不是直接提交到本網頁。 2,要註意http頭。http頭的不同會引起網頁的 ...
  • C++遠征之封裝篇(上)筆記 所有內容都是聽課筆記,願課堂視頻如下: C++遠征之封裝篇(上)-慕課網http://www.imooc.com/learn/382 類和對象 1 什麼是類,什麼是對象? 對象是具體的事物,而類是出於不同的目的從對象中抽象出來的,所以,同一事物可以抽象成不同的類。 舉個 ...
  • 摘要:ThinkPHP是一個小型網站很常用的低端框架,但是不專業的文檔和編碼導致使用者很容易只知其表不知其里。這裡僅就官方文檔中未曾提及的在thinkphp中使用jquery實現ajax非同步交互略作總結。 環境:ThinkPHP3.2.3,jQuery 閱讀目錄: 正文: 在一般的網站中,都需要用到 ...
  • 這是一個高級Java面試系列題中的第一部分。這一部分論述了可變參數,斷言,垃圾回收,初始化器,令牌化,日期,日曆等等Java核心問題。 1. 什麼是可變參數? 可變參數允許調用參數數量不同的方法。請看下麵例子中的求和方法。此方法可以調用1個int參數,或2個int參數,或多個int參數。 //int ...
  • 1、首先在MyEclipse菜單欄找到"windows"下拉菜單中找到首選項(英文Prefenerces),彈出首選項界面。 2、打開java ——>editor > templates 就出現了templates界面 3、然後點擊右上角的"new",按鈕創建你的組合鍵,在"Name"處輸入你的快捷 ...
  • 剛開始先從最簡單的爬蟲邏輯入手 爬蟲最簡單的解析面真的是這樣 這隻是一個函數而已 那麼在下麵加上: 哈哈,搞定 就是這麼一個爬蟲了 太神奇 但是得到的只是網頁的html頁面的東西 而且還沒篩選 那麼就篩選吧 那就用上面的來解析一下我的博客園 解析的是<a>...</a>之間的東西 看起來還不錯吧 我 ...
  • 詳細說明:http://php.662p.com/thread-1023-1-1.html ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...