javascript
Spring的REST分页
REST with Spring系列:
- 第1部分– 使用Spring 3.1和基于Java的配置引導(dǎo)Web應(yīng)用程序
- 第2部分– 使用Spring 3.1和基于Java的配置構(gòu)建RESTful Web服務(wù)
- 第3部分– 使用Spring Security 3.1保護RESTful Web服務(wù)
- 第4部分– RESTful Web服務(wù)可發(fā)現(xiàn)性
- 第5部分– 使用Spring進行REST服務(wù)發(fā)現(xiàn)
- 第6部分– 使用Spring Security 3.1的RESTful服務(wù)的基本身份驗證和摘要身份驗證
頁面作為資源vs頁面作為表示
在RESTful架構(gòu)的上下文中設(shè)計分頁時的第一個問題是將頁面視為實際資源還是僅表示資源 。 將頁面本身視為資源會帶來許多問題,例如不再能夠在調(diào)用之間唯一地標(biāo)識資源。 這加上以下事實:在RESTful上下文之外,不能將頁面視為適當(dāng)?shù)膶嶓w,但是在需要時構(gòu)造的所有者會使選擇變得簡單: 頁面是表示的一部分 。
在REST上下文中的分頁設(shè)計中的下一個問題是在何處包括分頁信息:
- 在URI路徑中 :/ foo / page / 1
- URI查詢 : / foo?page = 1
請記住, 頁面不是資源 ,因此不再可以將頁面信息編碼為URI。
URI查詢中的頁面信息
在URI查詢中對URI查詢中的頁面信息進行編碼是解決此問題的標(biāo)準(zhǔn)方法。 但是,這種方法確實有一個缺點 –它切入了用于實際查詢的查詢空間:
/ foo?page = 1&size = 10
控制器
現(xiàn)在,對于實現(xiàn)– 用于分頁的Spring MVC控制器非常簡單:
@RequestMapping( value = "admin/foo",params = { "page", "size" },method = GET ) @ResponseBody public List< Foo > findPaginated( @RequestParam( "page" ) int page, @RequestParam( "size" ) int size, UriComponentsBuilder uriBuilder, HttpServletResponse response ){Page< Foo > resultPage = service.findPaginated( page, size );if( page > resultPage.getTotalPages() ){throw new ResourceNotFoundException();}eventPublisher.publishEvent( new PaginatedResultsRetrievedEvent< Foo >( Foo.class, uriBuilder, response, page, resultPage.getTotalPages(), size ) );return resultPage.getContent(); }這兩個查詢參數(shù)在請求映射中定義,并通過@RequestParam注入到控制器方法中; HTTP響應(yīng)和Spring UriComponentsBuilder注入到Controller方法中以包含在事件中,因為實現(xiàn)可發(fā)現(xiàn)性將需要兩者。
REST分頁的可發(fā)現(xiàn)性
在分頁的范圍內(nèi),滿足REST的HATEOAS約束意味著使API的客戶端能夠基于導(dǎo)航中的當(dāng)前頁面發(fā)現(xiàn)下一頁和上一頁。 為此,將使用Link HTTP標(biāo)頭以及官方的 “ next ”,“ prev ”,“ first ”和“ last ”鏈接關(guān)系類型。
在REST中,可發(fā)現(xiàn)性是一個橫切關(guān)注點 ,不僅適用于特定操作,還適用于操作類型。 例如,每次創(chuàng)建資源時,客戶端應(yīng)可發(fā)現(xiàn)該資源的URI。 由于此要求與ANY資源的創(chuàng)建有關(guān),因此應(yīng)分開處理并與主Controller流分離。
使用Spring,這種分離是通過事件來實現(xiàn)的 ,如上一篇文章中已充分討論的那樣,該文章側(cè)重于RESTful服務(wù)的可發(fā)現(xiàn)性。 對于分頁,在控制器中觸發(fā)了事件– PaginatedResultsRetrievedEvent –,并且在此事件的偵聽器中實現(xiàn)了可發(fā)現(xiàn)性:
void addLinkHeaderOnPagedResourceRetrieval( UriComponentsBuilder uriBuilder, HttpServletResponse response, Class clazz, int page, int totalPages, int size ){String resourceName = clazz.getSimpleName().toString().toLowerCase();uriBuilder.path( "/admin/" + resourceName );StringBuilder linkHeader = new StringBuilder();if( hasNextPage( page, totalPages ) ){String uriNextPage = constructNextPageUri( uriBuilder, page, size );linkHeader.append( createLinkHeader( uriForNextPage, REL_NEXT ) );}if( hasPreviousPage( page ) ){String uriPrevPage = constructPrevPageUri( uriBuilder, page, size );appendCommaIfNecessary( linkHeader );linkHeader.append( createLinkHeader( uriForPrevPage, REL_PREV ) );}if( hasFirstPage( page ) ){String uriFirstPage = constructFirstPageUri( uriBuilder, size );appendCommaIfNecessary( linkHeader );linkHeader.append( createLinkHeader( uriForFirstPage, REL_FIRST ) );}if( hasLastPage( page, totalPages ) ){String uriLastPage = constructLastPageUri( uriBuilder, totalPages, size );appendCommaIfNecessary( linkHeader );linkHeader.append( createLinkHeader( uriForLastPage, REL_LAST ) );}response.addHeader( HttpConstants.LINK_HEADER, linkHeader.toString() ); }簡而言之,偵聽器邏輯檢查導(dǎo)航是否允許下一頁,上一頁,第一頁和最后一頁,如果允許,則將相關(guān)的URI添加到鏈接HTTP標(biāo)頭中。 它還確保鏈接關(guān)系類型是正確的-“下一個”,“上一個”,“第一個”和“最后一個”。 這是偵聽器的唯一職責(zé)( 此處是完整代碼 )。
測試駕駛分頁
分頁和可發(fā)現(xiàn)性的主要邏輯都應(yīng)由小型,集中的集成測試廣泛涵蓋; 與上一篇文章一樣 ,使用保證庫來使用REST服務(wù)并驗證結(jié)果。
這些是分頁集成測試的一些示例; 要獲得完整的測試套件,請查看github項目(本文結(jié)尾的鏈接):
@Test public void whenResourcesAreRetrievedPaged_then200IsReceived(){Response response = givenAuth().get( paths.getFooURL() + "?page=1&size=10" );assertThat( response.getStatusCode(), is( 200 ) ); } @Test public void whenPageOfResourcesAreRetrievedOutOfBounds_then404IsReceived(){Response response = givenAuth().get( paths.getFooURL() + "?page=" + randomNumeric( 5 ) + "&size=10" );assertThat( response.getStatusCode(), is( 404 ) ); } @Test public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources(){restTemplate.createResource();Response response = givenAuth().get( paths.getFooURL() + "?page=1&size=10" );assertFalse( response.body().as( List.class ).isEmpty() ); }測試駕駛分頁可發(fā)現(xiàn)性
測試分頁的可發(fā)現(xiàn)性相對簡單,盡管有很多基礎(chǔ)要講。 測試的重點是導(dǎo)航中當(dāng)前頁面的位置以及應(yīng)該從每個位置發(fā)現(xiàn)的不同URI:
@Test public void whenFirstPageOfResourcesAreRetrieved_thenSecondPageIsNext(){Response response = givenAuth().get( paths.getFooURL()+"?page=0&size=10" );String uriToNextPage = extractURIByRel( response.getHeader( LINK ), REL_NEXT );assertEquals( paths.getFooURL()+"?page=1&size=10", uriToNextPage ); } @Test public void whenFirstPageOfResourcesAreRetrieved_thenNoPreviousPage(){Response response = givenAuth().get( paths.getFooURL()+"?page=0&size=10" );String uriToPrevPage = extractURIByRel( response.getHeader( LINK ), REL_PREV );assertNull( uriToPrevPage ); } @Test public void whenSecondPageOfResourcesAreRetrieved_thenFirstPageIsPrevious(){Response response = givenAuth().get( paths.getFooURL()+"?page=1&size=10" );String uriToPrevPage = extractURIByRel( response.getHeader( LINK ), REL_PREV );assertEquals( paths.getFooURL()+"?page=0&size=10", uriToPrevPage ); } @Test public void whenLastPageOfResourcesIsRetrieved_thenNoNextPageIsDiscoverable(){Response first = givenAuth().get( paths.getFooURL()+"?page=0&size=10" );String uriToLastPage = extractURIByRel( first.getHeader( LINK ), REL_LAST );Response response = givenAuth().get( uriToLastPage );String uriToNextPage = extractURIByRel( response.getHeader( LINK ), REL_NEXT );assertNull( uriToNextPage ); }這些只是使用RESTful服務(wù)的集成測試的幾個示例。
獲取所有資源
關(guān)于分頁和可發(fā)現(xiàn)性的同一主題,必須選擇是否允許客戶端一次檢索系統(tǒng)中的所有資源 ,或者客戶端必須要求對它們進行分頁。
如果選擇了客戶端無法通過單個請求檢索所有資源,并且分頁不是可選的,而是必需的,則可以使用幾個選項來響應(yīng)對“獲取所有”請求 。
一種選擇是返回404( 未找到 )并使用Link標(biāo)頭使第一頁可被發(fā)現(xiàn):
鏈接= <http:// localhost:8080 / rest / api / admin / foo?page = 0&size = 10>; rel =“ first ”,<http:// localhost:8080 / rest / api / admin / foo?page = 103&size = 10>; rel =“ 最后一個 “另一個選擇是將重定向– 303( 請參閱其他 )返回到分頁的第一頁。
第三種選擇是為GET請求返回405( 不允許使用方法 ) 。
帶有范圍HTTP標(biāo)頭的REST Paginag
分頁的一種相對不同的方法是使用HTTP Range標(biāo)頭 – Range,Content-Range,If-Range,Accept-Ranges –和HTTP狀態(tài)碼 – 206( 部分內(nèi)容 ),413( 請求實體太大) ,416 ( 請求的范圍無法滿足 )。 關(guān)于這種方法的一種觀點是HTTP Range擴展不是用于分頁的,它們應(yīng)該由服務(wù)器而不是由應(yīng)用程序管理。
盡管在技術(shù)上不像本文中討論的實現(xiàn)那樣普遍,但是基于HTTP Range標(biāo)頭擴展實現(xiàn)分頁還是可行的。
結(jié)論
本文介紹了使用Spring在RESTful服務(wù)中分頁的實現(xiàn),并討論了如何實現(xiàn)和測試可發(fā)現(xiàn)性。 有關(guān)分頁的完整實現(xiàn),請查看github項目。
如果您讀完本文, 則應(yīng) 在Twitter上關(guān)注我 。
參考: Baeldung博客中我們JCG合作伙伴 Eugen Paraschiv的SpringREST分頁
翻譯自: https://www.javacodegeeks.com/2012/01/rest-pagination-in-spring.html
總結(jié)
以上是生活随笔為你收集整理的Spring的REST分页的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linuxC文件读写(linux .c文
- 下一篇: 扩展您的JPA POJO