Spring的REST分页

这是有关使用Spring 3.1和Spring Security 3.1和基于Java的配置来建立安全的RESTful Web Service的系列文章的第七篇。 本文将重点介绍RESTful Web服务中的分页实现

REST with Spring系列:

  • 第1部分– 使用Spring 3.1和基于Java的配置引导Web应用程序
  • 第2部分– 使用Spring 3.1和基于Java的配置构建RESTful Web服务
  • 第3部分– 使用Spring Security 3.1保护RESTful Web服务
  • 第4部分– RESTful Web服务可发现性
  • 第5部分– 使用Spring进行REST服务发现
  • 第6部分– 使用Spring Security 3.1的RESTful服务的基本身份验证和摘要身份验证

页面作为资源vs页面作为表示

在RESTful架构的上下文中设计分页时的第一个问题是将页面视为实际资源还是仅表示资源 。 将页面本身视为资源会带来许多问题,例如不再能够在调用之间唯一地标识资源。 这加上以下事实:在RESTful上下文之外,不能将页面视为适当的实体,但是在需要时构造的所有者会使选择变得简单: 页面是表示的一部分

在REST上下文中的分页设计中的下一个问题是在何处包括分页信息:

  • URI路径中 :/ foo / page / 1
  • URI查询/ foo?page = 1

请记住, 页面不是资源 ,因此不再可以将页面信息编码为URI。

URI查询中的页面信息

URI查询中对URI查询中的页面信息进行编码是解决此问题的标准方法。 但是,这种方法确实有一个缺点 –它切入了用于实际查询的查询空间:

/ foo?page = 1&size = 10

控制器

现在,对于实现– 用于分页的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();
}

这两个查询参数在请求映射中定义,并通过@RequestParam注入到控制器方法中 HTTP响应和Spring UriComponentsBuilder注入到Controller方法中以包含在事件中,因为实现可发现性将需要两者。

REST分页的可发现性

分页的范围内,满足RESTHATEOAS约束意味着使API的客户端能够基于导航中的当前页面发现下一页和上一页。 为此,将使用Link HTTP标头以及官方的 “ next ”,“ prev ”,“ first ”和“ last ”链接关系类型。

在REST中,可发现性是一个横切关注点 ,不仅适用于特定操作,还适用于操作类型。 例如,每次创建资源时,客户端应可发现该资源的URI。 由于此要求与ANY资源的创建有关,因此应分开处理并与主Controller流分离。

使用Spring,这种分离是通过事件来实现的 ,如上一篇文章中已充分讨论的那样,该文章侧重于RESTful服务的可发现性。 对于分页,在控制器中触发了事件– PaginatedResultsRetrievedEvent –,并且在此事件的侦听器中实现了可发现性:

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() );
}

简而言之,侦听器逻辑检查导航是否允许下一页,上一页,第一页和最后一页,如果允许,则将相关的URI添加到链接HTTP标头中。 它还确保链接关系类型是正确的-“下一个”,“上一个”,“第一个”和“最后一个”。 这是侦听器的唯一职责( 此处是完整代码 )。

测试驾驶分页

分页和可发现性的主要逻辑都应由小型,集中的集成测试广泛涵盖; 与上一篇文章一样 ,使用保证库来使用REST服务并验证结果。

这些是分页集成测试的一些示例; 要获得完整的测试套件,请查看github项目(本文结尾的链接):

@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() );
}

测试驾驶分页可发现性

测试分页的可发现性相对简单,尽管有很多基础要讲。 测试的重点是导航中当前页面位置以及应该从每个位置发现的不同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服务的集成测试的几个示例。

获取所有资源

关于分页和可发现性的同一主题,必须选择是否允许客户端一次检索系统中的所有资源 ,或者客户端必须要求对它们进行分页。

如果选择了客户端无法通过单个请求检索所有资源,并且分页不是可选的,而是必需的,则可以使用几个选项来响应对“获取所有”请求

一种选择是返回404( 未找到并使用Link标头使第一页可被发现:

链接= <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标头的REST Paginag

分页的一种相对不同的方法是使用HTTP Range标头Range,Content-Range,If-Range,Accept-Ranges –和HTTP状态码 – 206( 部分内容 ),413( 请求实体太大) ,416 ( 请求的范围无法满足 )。 关于这种方法的一种观点是HTTP Range扩展不是用于分页的,它们应该由服务器而不是由应用程序管理。

尽管在技术上不像本文中讨论的实现那样普遍,但是基于HTTP Range标头扩展实现分页还是可行的。

结论

本文介绍了使用Spring在RESTful服务中分页的实现,并讨论了如何实现和测试可发现性。 有关分页的完整实现,请查看github项目。

如果您读完本文, 则应 在Twitter上关注我 。

参考: Baeldung博客中我们JCG合作伙伴 Eugen Paraschiv的SpringREST分页


翻译自: https://www.javacodegeeks.com/2012/01/rest-pagination-in-spring.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/373283.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

众筹源码 php,助创cms众筹源码系统v1.0

什么是助创cms众筹系统?使用“预约团购”的众筹方式给自己的创意争取大家的关注和支持&#xff0c;是近年来非常火热的一种融资模式&#xff0c;助创cms众筹系统可以10分钟帮你打造一个和京东众筹一样的平台&#xff0c;包含产品众筹和公益众筹两个部分&#xff0c;可以直接拿…

Linq to SQL 的增删改查操作

Linq&#xff0c;全称Language Integrated Query&#xff0c;作为C#3.0新语法&#xff0c;是C#语言的一个扩展&#xff0c;可以将数据查询直接集成到编程语言本身中。 Linq表达式和SQL语句差不多&#xff0c;说白了就是颠倒sql语法&#xff0c; from where select ...&#xff…

扩展您的JPA POJO

可扩展性是许多体系结构的重要特征。 它衡量是否容易&#xff08;或困难&#xff09; 它是在不影响现有核心系统功能的情况下添加或更改功能。 让我们举一个简单的例子。 假设您的公司拥有一个核心产品来跟踪体育俱乐部中的所有用户。 在您的产品体系结构中&#xff0c;您有一个…

web框架--flask

flask介绍Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架&#xff0c;对于Werkzeug本质是Socket服务端&#xff0c;其用于接收http请求并对请求进行预处理&#xff0c;然后触发Flask框架&#xff0c;开发人员基于Flask框架提供的功能对请求进行…

php spider shell,ScrapyShell使用

Scrapy ShellScrapy终端是一个交互终端&#xff0c;我们可以在未启动spider的情况下尝试及调试代码&#xff0c;也可以用来测试XPath或CSS表达式&#xff0c;查看他们的工作方式&#xff0c;方便我们爬取的网页中提取的数据。如果安装了 IPython &#xff0c;Scrapy终端将使用 …

69 个经典 Spring 面试题和答案

Spring 概述 什么是spring?Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用&#xff0c;但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发&#xff0c;并通过POJO为基础的编程模型促进良好的编程习惯。使用Spring框架…

高性能MySql

1、索引是对DB优化最有效的方式 varchar(10)定义的是字符的个数&#xff0c;如果是utf-8的话&#xff0c;最大是3X10个字节 二、索引类型 1、MySql的索引是在存储引擎层实现的&#xff0c;各个存储引擎的的索引方式也是不同的 2、B-Tree索引 MyISAM索引通过数据的物理位置引用被…

Java Swing井字游戏

大家好&#xff01; 哇&#xff0c;自从我在这里发布了东西以来已经有一段时间了&#xff01; 我必须说我真的很想写东西&#xff0c;而且我保证我不会再陷入“作家的障碍”。 希望 ..最近两个月发生了很多事情&#xff0c;我有很多话要说。 但是在这篇文章中&#xff0c;我只是…

Java小青蛙跳台街,算法-青蛙跳台阶详解

/*[跳台阶][题目]一只青蛙一次可以跳上1级台阶&#xff0c;也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。[解析]与斐波那契数列的求解过程类似。典型的动态规划问题。对于第 n 级台阶&#xff0c;我们可以从第 n-1 级台阶一步到达&#xff0c;也可以从第 n-2 级…

apache服务器配置Net的实践

前置&#xff1a; 在xp系统中&#xff0c;打补丁之类或啥子操作引起或多或少的问题&#xff0c;最终导致iis不能使用&#xff1b; 不想装系统...忍着... 最近突发事件导致&#xff0c;需要摸一下apache服务器处理&#xff0c;好吧&#xff0c;那就搜索下吧..... 目标&#xff1…

TestNG或JUnit

多年以来&#xff0c;无论何时使用Java代码进行单元测试&#xff0c;我始终会回到TestNG。 每当我拿起TestNG时&#xff0c;人们都问我为什么要继续使用TestNG&#xff0c;尤其是默认开发环境&#xff08;例如Eclipse或Maven&#xff09;提供的JUnit时。 继续进行同样的战斗&am…

event php,PHP event 事件机制

/** PHP 事件机制*/class baseClass{private $_e;public function __set($name,$value){if( strncasecmp($name,"on",2) 0 ){if(!isset($this->_e[$name]))$this->_e[$name] array();return array_push($this->_e[$name] , $value);}}public function __g…

Android JNI编程(五)——C语言的静态内存分配、动态内存分配、动态创建数组...

版权声明&#xff1a;本文出自阿钟的博客&#xff0c;转载请注明出处:http://blog.csdn.net/a_zhon/。 目录(?)[] 一&#xff1a;什么是静态内存什么又是动态内存呢&#xff1f; 静态内存&#xff1a;是指在程序开始运行时由编译器分配的内存&#xff0c;它的分配是在程序开始…

Visual Studio-C#-20160411

函数的四个要素包括&#xff1a;名称&#xff0c;输入&#xff0c;输出&#xff0c;加工 注释的方式&#xff1a;//只注释一行&#xff1b;/**/注释一段区域&#xff1b; namespace ConsoleApplication6 ---------//命名空间{ class Program ---------------------------//类…

配置MyBatis 3

MyBatis是一个非常流行且也是最有效的SQL映射框架。 MyBatis可用于Java和.net语言。 MyBatis并不是Hibernate的真正替代品&#xff0c;但是我们可以使用该框架来减少MyBatis提供的高效和高性能的数据库相关代码。 本教程将向您展示使用数据库配置MyBatis 3的步骤。 MyBatis 3支…

php获取src,PHP读取文件

本文概述PHP提供了各种功能来从文件读取数据。有多种功能允许你读取所有文件数据, 逐行读取数据以及逐字符读取数据。下面提供了可用的PHP文件读取功能。fread()fgets()fgetc()PHP读取文件-fread()PHP fread()函数用于读取文件的数据。它需要两个参数&#xff1a;文件资源和文件…

HDOJ(HDU) 1406 完数

Problem Description 完数的定义&#xff1a;如果一个大于1的正整数的所有因子之和等于它的本身&#xff0c;则称这个数是完数&#xff0c;比如6&#xff0c;28都是完数&#xff1a;6123&#xff1b;28124714。 本题的任务是判断两个正整数之间完数的个数。 Input 输入数据包…

Allegro padstack

在ALLEGRO中&#xff0c;建立PCB封装是一件挺复杂的事&#xff0c;而要建立FOOTPRINT&#xff0c;首先要有一个PAD&#xff0c;所以就要新建PADSTACK。 焊盘可以分两种&#xff0c;表贴焊盘和通孔焊盘&#xff0c;表贴焊盘结构相对简单&#xff0c;下面首先分析表贴焊盘的成分&…

java中datetime类型转换,Java中日期格式和其他类型转换详解

涉及的核心类&#xff1a;Date类、SimpleDateFormat类、Calendar类一、Date型与long型Date型转换为long型Date date new Date();//取得当前时间Date类型long date2long date.getTime();//Date转longlong型转换为Date型long cur System.currentTimeMills();//取得当前时间lon…

GWT MVP变得简单

GWT Model-View-Presenter是用于大规模应用程序开发的设计模式。 它源于MVC&#xff0c;它在视图和逻辑之间进行划分&#xff0c;并有助于创建结构良好&#xff0c;易于测试的代码。 为了帮助像我这样的懒惰开发人员&#xff0c;我研究了如何减少使用声明式UI时要编写的类和接口…