jersey

想要在Java中使用REST? 然后您来对地方了,因为在博客文章中,我将向您介绍如何“美丽”地设计REST API,以及如何使用Jersey框架在Java中实现它。 在本教程中开发的RESTful API将为存储在MySql数据库中的播客资源演示完整的Create,_read,_update_and_delete(CRUD)功能。

1.例子

为什么?

在开始之前,让我告诉你为什么写这篇文章–好吧,我的意图是将来为Podcastpedia.org提供REST API。 当然,可以像我目前对AJAX调用那样使用Spring自己的REST实现,但是我还想看看“正式”实现的样子。 因此,了解该技术的最佳方法是使用该技术构建原型。 这就是我所做的,也是我在这里介绍的,我可以说我对泽西岛非常满意。 继续阅读以了解原因!!!

注意:您可以使用jQuery和Spring MVC访问我的帖子自动完成搜索框,以了解Spring如何处理REST请求。

它有什么作用?

本教程中管理的资源是播客。 REST API将允许创建,检索,更新和删除此类资源。

建筑与技术

该演示应用程序基于“德米特法则(LoD)或最少知识原理” [16]使用了多层体系结构:

  • 第一层是通过Jersey实施的REST支持,具有外观的作用并将逻辑委托给业务层
  • 业务层是逻辑发生的地方
  • 数据访问层是与持久性存储(在本例中为MySql数据库)进行通信的地方

关于使用的技术/框架的几句话:

1.3.1。 泽西岛(门面)

Jersey RESTful Web服务框架是开源,生产质量的,用于以Java开发RESTful Web服务的框架,该框架提供对JAX-RS API的支持,并充当JAX-RS (JSR 311和JSR 339)参考实现。

1.3.2。 Spring(业务层)

我喜欢将东西与Spring粘在一起,这个示例也不例外。 在我看来,没有更好的方法来制作具有不同功能的POJO。 您将在本教程中找到将Jersey 2与Spring集成所需的功能。

1.3.3。 JPA 2 / Hibernate(持久层)

对于持久层,我仍然使用DAO模式,尽管要实现它,我使用的是JPA 2,正如某些人所说,它应该使DAO变得多余(我不喜欢EntityManager / JPA专用代码)。 JPA 2的AS支持框架我正在使用Hibernate。

有关Java中的持久性主题的有趣讨论,请参见我的Spring,JPA2和Hibernate的Java持久性示例。

1.3.4。 网络容器

一切都与Maven打包为.war文件,并且可以部署在任何Web容器上–我使用Tomcat和Jetty ,但是也可以是Glassfih,Weblogic,JBoss或WebSphere。

1.3.5。 MySQL

示例数据存储在MySQL表中:

1.3.6。 技术版本

  1. 泽西2.9
  2. Spring4.0.3
  3. Hibernate4
  4. Maven 3
  5. Tomcat7
  6. 码头9
  7. MySQL的5.6

注意:帖子中的主要焦点将是REST api设计及其通过Jersey JAX-RS实现的实现,所有其他技术/层均被视为促成因素。

源代码

此处提供的项目的源代码可在GitHub上获得,其中包含有关如何安装和运行项目的完整说明:

  • Codingpedia / Demo-rest-jersey-spring

2.配置

在开始介绍REST API的设计和实现之前,我们需要做一些配置,以便所有这些奇妙的技术可以一起发挥作用

项目依赖

Jersey Spring扩展名必须存在于项目的类路径中。 如果使用的是Maven,请将其添加到项目的pom.xml文件中:

pom.xml中的Jersey-spring依赖项

<dependency><groupId>org.glassfish.jersey.ext</groupId><artifactId>jersey-spring3</artifactId><version>${jersey.version}</version><exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion>         <exclusion><groupId>org.springframework</groupId><artifactId>spring-web</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.glassfish.jersey.media</groupId><artifactId>jersey-media-json-jackson</artifactId><version>2.4.1</version>
</dependency>

注意: jersey-spring3.jar为Spring库使用其自己的版本,因此要使用所需的库(在本例中为Spring 4.0.3,请释放),您需要手动排除这些库。

代码警报:如果您想查看项目中还需要哪些其他依赖项(例如Spring,Hibernate,Jetty maven插件,测试等),则可以查看GitHub上完整的pom.xml文件。

web.xml

Web应用程序部署描述符

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"><display-name>Demo - Restful Web Application</display-name><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/applicationContext.xml</param-value></context-param><servlet><servlet-name>jersey-serlvet</servlet-name><servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class><init-param><param-name>javax.ws.rs.Application</param-name><param-value>org.codingpedia.demo.rest.RestDemoJaxRsApplication</param-value>          </init-param>     <load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>jersey-serlvet</servlet-name><url-pattern>/*</url-pattern></servlet-mapping><resource-ref><description>Database resource rest demo web application </description><res-ref-name>jdbc/restDemoDB</res-ref-name><res-type>javax.sql.DataSource</res-type><res-auth>Container</res-auth></resource-ref>
</web-app>

泽西servlet

注意Jersey Servlet配置[行18-33]。 javax.ws.rs.core.Application类定义JAX-RS应用程序的组件(根资源和提供程序类)。 我使用了ResourceConfig,它是Jersey自己对Application类的实现,并且提供了高级功能来简化JAX-RS组件的注册。 请查阅文档中的JAX-RS应用程序模型,以了解更多可能性。

我对ResourceConfig类的实现org.codingpedia.demo.rest.RestDemoJaxRsApplication ,注册了应用程序资源,过滤器,异常映射器和功能:

org.codingpedia.demo.rest.service.MyDemoApplication

package org.codingpedia.demo.rest.service;//imports omitted for brevity /*** Registers the components to be used by the JAX-RS application* * @author ama* */
public class RestDemoJaxRsApplication extends ResourceConfig {/*** Register JAX-RS application components.*/public RestDemoJaxRsApplication() {// register application resourcesregister(PodcastResource.class);register(PodcastLegacyResource.class);// register filtersregister(RequestContextFilter.class);register(LoggingResponseFilter.class);register(CORSResponseFilter.class);// register exception mappersregister(GenericExceptionMapper.class);register(AppExceptionMapper.class);register(NotFoundExceptionMapper.class);// register featuresregister(JacksonFeature.class);register(MultiPartFeature.class);}
}

请注意:

  • org.glassfish.jersey.server.spring.scope.RequestContextFilter ,这是一个Spring过滤器,它提供了JAX-RS和Spring请求属性之间的桥梁
  • org.codingpedia.demo.rest.resource.PodcastsResource ,这是一个“外观”组件,它通过注释公开了REST API,并将在稍后的文章中进行详尽介绍。
  • org.glassfish.jersey.jackson.JacksonFeature ,这是注册Jackson JSON提供程序的功能-应用程序需要它才能理解JSON数据

2.1.2.2。 Spring应用程序上下文配置

Spring应用程序上下文配置位于spring/applicationContext.xml下的类路径中:

Spring应用程序上下文配置

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"  xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="org.codingpedia.demo.rest.*" /><!-- ************ JPA configuration *********** --><tx:annotation-driven transaction-manager="transactionManager" />  <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><property name="entityManagerFactory" ref="entityManagerFactory" /></bean><bean id="transactionManagerLegacy" class="org.springframework.orm.jpa.JpaTransactionManager"><property name="entityManagerFactory" ref="entityManagerFactoryLegacy" /></bean>    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><property name="persistenceXmlLocation" value="classpath:config/persistence-demo.xml" /><property name="persistenceUnitName" value="demoRestPersistence" />        <property name="dataSource" ref="restDemoDS" /><property name="packagesToScan" value="org.codingpedia.demo.*" /><property name="jpaVendorAdapter"><bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"><property name="showSql" value="true" /><property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" /></bean></property></bean>     <bean id="entityManagerFactoryLegacy" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><property name="persistenceXmlLocation" value="classpath:config/persistence-demo.xml" /><property name="persistenceUnitName" value="demoRestPersistenceLegacy" /><property name="dataSource" ref="restDemoLegacyDS" /><property name="packagesToScan" value="org.codingpedia.demo.*" /><property name="jpaVendorAdapter"><bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"><property name="showSql" value="true" /><property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" /></bean></property></bean>        <bean id="podcastDao" class="org.codingpedia.demo.rest.dao.PodcastDaoJPA2Impl"/>  <bean id="podcastService" class="org.codingpedia.demo.rest.service.PodcastServiceDbAccessImpl" />   <bean id="podcastsResource" class="org.codingpedia.demo.rest.resource.PodcastsResource" /><bean id="podcastLegacyResource" class="org.codingpedia.demo.rest.resource.PodcastLegacyResource" /><bean id="restDemoDS" class="org.springframework.jndi.JndiObjectFactoryBean" scope="singleton"><property name="jndiName" value="java:comp/env/jdbc/restDemoDB" /><property name="resourceRef" value="true" />        </bean><bean id="restDemoLegacyDS" class="org.springframework.jndi.JndiObjectFactoryBean" scope="singleton"><property name="jndiName" value="java:comp/env/jdbc/restDemoLegacyDB" /><property name="resourceRef" value="true" />        </bean>
</beans>

这里没什么特别的,它只是定义了整个演示应用程序所需的bean(例如podcastsResource ,这是我们REST API的入口点类)。

3. REST API(设计和实现)

资源资源

3.1.1。 设计

如前所述,演示应用程序管理播客,这些播客代表了我们REST API中的资源。 资源是REST中的核心概念,其主要特征有两个:

  • 每个引用都有一个全局标识符(例如HTTP中的URI )。
  • 具有一个或多个表示形式,它们公开给外部世界并可以使用(在此示例中,我们将主要使用JSON表示形式)

在REST中,资源通常用名词(播客,客户,用户,帐户等)而不是动词(getPodcast,deleteUser等)表示。

本教程中使用的端点是:

  • /podcasts(请注意复数) URI标识了表示播客集合的资源
  • /podcasts/{id} –通过播客ID标识播客资源的URI

3.1.2。 实作

为了简单起见,播客将仅具有以下属性:

  • id –唯一标识播客
  • feed -播客的URL饲料
  • title –播客标题
  • linkOnPodcastpedia –您可以在Podcastpedia.org上找到播客
  • description -播客的简短描述

我本可以只使用一个Java类来表示代码中的播客资源,但是在那种情况下,该类及其属性/方法可能会因JPA和XML / JAXB / JSON注释而变得混乱不堪。 我想避免这种情况,而是使用了两个具有几乎相同属性的表示形式:

  • PodcastEntity.java –在DB和业务层中使用的JPA注释类
  • Podcast.java –在正面和业务层中使用的带有JAXB / JSON注释的类

注意:我仍在尝试使自己相信这是更好的方法,因此,如果对此有任何建议,请发表评论。

Podcast.java类的外观类似于以下内容:

Podcast.java

package org.codingpedia.demo.rest.resource;//imports omitted for brevity/*** Podcast resource placeholder for json/xml representation * * @author ama**/
@SuppressWarnings("restriction")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Podcast implements Serializable {private static final long serialVersionUID = -8039686696076337053L;/** id of the podcast */@XmlElement(name = "id")    private Long id;/** title of the podcast */@XmlElement(name = "title")    private String title;/** link of the podcast on Podcastpedia.org */@XmlElement(name = "linkOnPodcastpedia")    private String linkOnPodcastpedia;/** url of the feed */@XmlElement(name = "feed")    private String feed;/** description of the podcast */@XmlElement(name = "description")private String description; /** insertion date in the database */@XmlElement(name = "insertionDate")@XmlJavaTypeAdapter(DateISO8601Adapter.class)    @PodcastDetailedViewprivate Date insertionDate;public Podcast(PodcastEntity podcastEntity){try {BeanUtils.copyProperties(this, podcastEntity);} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InvocationTargetException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public Podcast(String title, String linkOnPodcastpedia, String feed,String description) {this.title = title;this.linkOnPodcastpedia = linkOnPodcastpedia;this.feed = feed;this.description = description;}public Podcast(){}//getters and setters now shown for brevity
}

并转换为以下JSON表示形式,它实际上是当今与REST一起使用的事实上的媒体类型:

{"id":1,"title":"Quarks & Co - zum Mitnehmen-modified","linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/1/Quarks-Co-zum-Mitnehmen","feed":"http://podcast.wdr.de/quarks.xml","description":"Quarks & Co: Das Wissenschaftsmagazin","insertionDate":"2014-05-30T10:26:12.00+0200"
}

即使JSON越来越成为REST API中的首选表示形式,您也不应忽略XML表示形式,因为大多数系统仍使用XML格式与其他方进行通信。

好消息是,在泽西岛,您可以一枪杀死两只兔子–使用JAXB bean(如上所述),您将能够使用相同的Java模型来生成JSON和XML表示形式。 另一个优势是使用这种模型的简单性以及Java SE Platform中API的可用性。

注意:本教程中定义的大多数方法都将产生和使用application / xml媒体类型,其中application / json是首选方式。

方法

在向您介绍API之前,让我告诉您

  • 创建= POST
  • 读取=获取
  • 更新= PUT
  • 删除=删除

并且不是严格的1:1映射。 为什么? 因为您还可以将PUT用于创建,将POST用于更新。 在接下来的段落中将对此进行解释和演示。

注意:对于“读取和删除”非常清楚,它们确实使用GET和DELETE HTTP操作一对一映射。 无论如何,REST是一种体系结构样式,不是一种规范,您应该使体系结构适应您的需求,但是如果您想公开自己的API并希望有人使用它,则应该遵循一些“最佳实践”。

如前所述, PodcastRestResource类是处理所有其余请求的类:

package org.codingpedia.demo.rest.resource;
//imports
......................
@Component
@Path("/podcasts")
public class PodcastResource {@Autowiredprivate PodcastService podcastService;.....................
}

注意类定义之前的@Path("/podcasts") –与播客资源相关的所有内容都将在此路径下发生。 @Path批注的值是相对URI路径。 在上面的示例中,Java类将托管在URI路径/podcastsPodcastService接口将业务逻辑公开给REST外观层。

代码警报:您可以在GitHub – PodcastResource.java上找到该类的全部内容。 我们将逐步介绍该文件,并说明与不同操作相对应的不同方法。

3.2.1。 创建播客

3.2.1.1。 设计

尽管资源创建的“最著名”方法是使用POST,但是如前所述,要创建新资源,我可以同时使用POST和PUT方法,而我做到了:

描述 URI HTTP方法 HTTP状态响应
添加新的播客 /播客/ 开机自检 创建了201
添加新的播客(必须发送所有值) / podcasts / {id} 创建了201

使用POST(不是幂等)之间的最大区别

“ POST方法用于请求源服务器接受请求中包含的实体作为请求行中Request-URI所标识资源的新下属[…]如果在源服务器上创建了资源,响应应为201(已创建),并包含描述请求状态并引用新资源的实体以及位置标头” [1]

和PUT(幂等)

“ PUT方法请求将封闭的实体存储在提供的Request-URI […]下,如果Request-URI没有指向现有资源,并且请求用户代理能够将该URI定义为新资源,原始服务器可以使用该URI创建资源。 如果创建了新资源,则原始服务器务必通过201(已创建)响应通知用户代理。” [1]

是对于PUT,您应该事先知道将在其中创建资源的位置,并发送该条目的所有可能值。

3.2.1.2。 实作

3.2.1.2.1。 使用POST创建单个资源

从JSON创建单个播客资源

/*** Adds a new resource (podcast) from the given json format (at least title* and feed elements are required at the DB level)* * @param podcast* @return* @throws AppException*/
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response createPodcast(Podcast podcast) throws AppException {Long createPodcastId = podcastService.createPodcast(podcast);return Response.status(Response.Status.CREATED)// 201.entity("A new podcast has been created").header("Location","http://localhost:8888/demo-rest-jersey-spring/podcasts/"+ String.valueOf(createPodcastId)).build();
}

注解

  • @POST –表示该方法响应HTTP POST请求
  • @Consumes({MediaType.APPLICATION_JSON}) –定义该方法接受的媒体类型,在这种情况下为"application/json"
  • @Produces({MediaType.TEXT_HTML}) –定义方法可以产生的媒体类型,在这种情况下为"text/html"

响应

  • 成功:文本/ html文档,HTTP状态为201 Created ,并且Location标头指定在何处创建资源
  • 错误时:
    • 400 Bad request如果没有提供足够的数据

3.2.1.2.2。 使用PUT创建单个资源(“播客”)

这将在下面的“更新播客”部分中进行处理。

3.2.1.2.3。 奖励–从表单创建单个资源(“播客”)

从表单创建单个播客资源

/*** Adds a new podcast (resource) from "form" (at least title and feed* elements are required at the DB level)* * @param title* @param linkOnPodcastpedia* @param feed* @param description* @return* @throws AppException*/
@POST
@Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
@Produces({ MediaType.TEXT_HTML })
@Transactional
public Response createPodcastFromApplicationFormURLencoded(@FormParam("title") String title,@FormParam("linkOnPodcastpedia") String linkOnPodcastpedia,@FormParam("feed") String feed,@FormParam("description") String description) throws AppException {Podcast podcast = new Podcast(title, linkOnPodcastpedia, feed,description);Long createPodcastid = podcastService.createPodcast(podcast);return Response.status(Response.Status.CREATED)// 201.entity("A new podcast/resource has been created at /demo-rest-jersey-spring/podcasts/"+ createPodcastid).header("Location","http://localhost:8888/demo-rest-jersey-spring/podcasts/"+ String.valueOf(createPodcastid)).build();
}

注解

  • @POST –表示该方法响应HTTP POST请求
  • @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) –定义该方法接受的媒体类型,在这种情况下为"application/x-www-form-urlencoded"
    • @FormParam –出现在方法的输入参数之前,此批注将请求实体主体中包含的表单参数的值绑定到资源方法参数。 除非使用“ Encoded注释”将其禁用,否则将对值进行URL解码
  • @Produces({MediaType.TEXT_HTML}) –定义该方法可以产生的媒体类型,在这种情况下为“ text / html”。 响应将是状态为201的html文档,它向调用方指示请求已得到满足并导致创建了新资源。

响应

  • 成功:文本/ html文档,HTTP状态为201 Created ,并且Location标头指定在何处创建资源
  • 错误时:
    • 400 Bad request如果没有提供足够的数据

3.2.2。 阅读播客

3.2.2.1。 设计

该API支持两种读取操作:

  • 返回播客的集合
  • 返回由ID标识的播客
描述 URI HTTP方法 HTTP状态响应
返回所有播客 / podcasts /?orderByInsertionDate = {ASC | DESC}&numberDaysToLookBack = {val} 得到 200 OK
添加新的播客(必须发送所有值) / podcasts / {id} 得到 200 OK

请注意收集资源的查询参数– orderByInsertionDate和numberDaysToLookBack。 将过滤器添加为URI中的查询参数而不是路径的一部分是很有意义的。

3.2.2.2。 实作

3.2.2.2.1。 阅读所有播客(“ /”)

阅读所有资源

/*** Returns all resources (podcasts) from the database* * @return* @throws IOException* @throws JsonMappingException* @throws JsonGenerationException* @throws AppException*/
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public List<Podcast> getPodcasts(@QueryParam("orderByInsertionDate") String orderByInsertionDate,@QueryParam("numberDaysToLookBack") Integer numberDaysToLookBack)throws JsonGenerationException, JsonMappingException, IOException,AppException {List<Podcast> podcasts = podcastService.getPodcasts(orderByInsertionDate, numberDaysToLookBack);return podcasts;
}

注解

  • @GET –表示该方法响应HTTP GET请求
  • @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) –定义该方法可以产生的媒体类型),在这种情况下为"application/json""application/xml" (您需要在@XmlRootElement前面Podcast类)。 响应将是JSON或XML格式的播客列表。

响应

  • 数据库中的播客列表和HTTP状态200 OK

3.2.2.2.1。 阅读一个播客

通过ID读取一种资源

@GET
@Path("{id}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getPodcastById(@PathParam("id") Long id)throws JsonGenerationException, JsonMappingException, IOException,AppException {Podcast podcastById = podcastService.getPodcastById(id);return Response.status(200).entity(podcastById).header("Access-Control-Allow-Headers", "X-extra-header").allow("OPTIONS").build();
}

注解

  • @GET –表示该方法响应HTTP GET请求
  • @Path("{id}") –标识类方法将为其请求服务的URI路径。 “ id”值是构成URI路径模板的嵌入式变量。 它与@PathParam变量结合使用。
    • @PathParam("id") –将URI模板参数(“ id”)的值绑定到资源方法参数。
  • @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) –定义方法可以产生的媒体类型,在这种情况下为"application/json""application/xml" (您需要在Podcast前面的@XmlRootElement课)。

响应

  • 成功:请求的播客的HTTP状态为200 OK 。 格式为xml或JSON,具体取决于客户端发送的Accept-header值(可能押于application / xml或application / json)
  • 错误: 404 Not found如果数据库中不存在具有给定ID的播客,则找不到

3.2.3。 更新播客

3.2.3.1。 设计

描述 URI HTTP方法 HTTP状态响应
更新播客(完整) / podcasts / {id} 200 OK
更新播客(部分) / podcasts / {id} 开机自检 200 OK

在REST领域,您将进行两种更新:

  1. 全面更新–您将在其中提供所有
  2. 部分更新–仅通过网络发送一些属性进行更新时

要进行完整的更新,很明显可以使用PUT方法,并且符合RFC 2616中该方法的规范。

现在,对于部分更新,有很多关于使用什么的建议/辩论:

  1. 通过PUT
  2. 通过POST
  3. 通过PATCH

让我告诉我为什么我认为第一种选择(使用PUT)是行不通的。 好吧,根据规格

“如果请求URI引用了已经存在的资源,则应将封闭的实体视为驻留在源服务器上的实体的修改版本。” [1]

如果我只想更新ID为2的播客的title属性

PUT命令进行部分更新

PUT http://localhost:8888/demo-rest-jersey-spring/podcasts/2 HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 155
Host: localhost:8888
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5){"title":"New Title"
}

然后,根据规范,“存储”在该位置的资源应仅具有ID和标题,显然我的意图不是。

通过POST第二个选项……好吧,我们可以“滥用”此选项,这恰恰是我在实现中所做的,但是它似乎与我不符,因为POST的规范指出:

“发布的实体从属于该URI,就像文件从属于包含它的目录,新闻文章从属于发布它的新闻组或记录从属于数据库一样。” [1 ]

在我看来,这似乎不是部分更新案例……

第三种选择是使用PATCH,我想这是该方法成功的主要原因:

“几个扩展超文本传输​​协议(HTTP)的应用程序需要功能来进行部分资源修改。 现有的HTTP PUT方法仅允许完全替换文档。该提案添加了新的HTTP方法PATCH,以修改现有的HTTP资源。” [2]

我很确定将来会使用它进行部分更新,但是由于它尚不是规范的一部分,并且尚未在Jersey中实现,因此我选择在演示中使用POST的第二个选项。 如果您真的想用PATCH在Java中实现部分更新,请查看这篇文章– JAX-RS 2.0中的透明PATCH支持

3.2.3.1。 实作

3.2.3.1.1。 完整更新

创建或完全更新资源实现方法

@PUT
@Path("{id}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response putPodcastById(@PathParam("id") Long id, Podcast podcast)throws AppException {Podcast podcastById = podcastService.verifyPodcastExistenceById(id);if (podcastById == null) {// resource not existent yet, and should be created under the// specified URILong createPodcastId = podcastService.createPodcast(podcast);return Response.status(Response.Status.CREATED)// 201.entity("A new podcast has been created AT THE LOCATION you specified").header("Location","http://localhost:8888/demo-rest-jersey-spring/podcasts/"+ String.valueOf(createPodcastId)).build();} else {// resource is existent and a full update should occurpodcastService.updateFullyPodcast(podcast);return Response.status(Response.Status.OK)// 200.entity("The podcast you specified has been fully updated created AT THE LOCATION you specified").header("Location","http://localhost:8888/demo-rest-jersey-spring/podcasts/"+ String.valueOf(id)).build();}
}

注解

  • @PUT –表示该方法响应HTTP PUT请求
  • @Path("{id}") –标识类方法将为其请求服务的URI路径。 “ id”值是构成URI路径模板的嵌入式变量。 它与@PathParam变量结合使用。
    • @PathParam("id") –将URI模板参数(“ id”)的值绑定到资源方法参数。
  • @Consumes({MediaType.APPLICATION_JSON}) –定义该方法接受的媒体类型,在这种情况下为"application/json"
  • @Produces({MediaType.TEXT_HTML}) –定义方法可以产生的媒体类型,在这种情况下为“ text / html”。

将是一个html文档,其中包含不同的消息和状态,具体取决于采取了哪些措施

回应

  • 在创造时

    • 成功时: 201 Created并且在Location标头中指定了创建资源的指定位置
    • 错误: 400 Bad request如果未提供插入所需的最低属性
  • 完整更新

    • 成功: 200 OK

3.2.3.1.2。 部分更新

部分更新

//PARTIAL update
@POST
@Path("{id}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response partialUpdatePodcast(@PathParam("id") Long id, Podcast podcast) throws AppException {podcast.setId(id);podcastService.updatePartiallyPodcast(podcast);return Response.status(Response.Status.OK)// 200.entity("The podcast you specified has been successfully updated").build();
}

注解

  • @POST –表示该方法响应HTTP POST请求
  • @Path("{id}") –标识类方法将为其请求服务的URI路径。 “ id”值是构成URI路径模板的嵌入式变量。 它与@PathParam变量结合使用。
    • @PathParam("id") –将URI模板参数(“ id”)的值绑定到资源方法参数。
  • @Consumes({MediaType.APPLICATION_JSON}) –定义该方法接受的媒体类型,在这种情况下为"application/json"
  • @Produces({MediaType.TEXT_HTML}) –定义方法可以产生的媒体类型,在这种情况下为"text/html"

响应

  • 成功: 200 OK
  • 错误: 404 Not Found ,如果在提供的位置没有资源可用

3.2.4。 删除播客

3.2.4.1。 设计

描述 URI HTTP方法 HTTP状态响应
删除所有播客 /播客/ 删除 204没有内容
删除指定位置的播客 / podcasts / {id} 删除 204没有内容

3.2.4.2。 实作

3.2.4.2.1。 删除所有资源

删除所有资源

@DELETE
@Produces({ MediaType.TEXT_HTML })
public Response deletePodcasts() {podcastService.deletePodcasts();return Response.status(Response.Status.NO_CONTENT)// 204.entity("All podcasts have been successfully removed").build();
}

注解

  • @DELETE –表示该方法响应HTTP DELETE请求
  • @Produces({MediaType.TEXT_HTML}) –定义该方法可以产生的媒体类型,在这种情况下为“ text / html”。

响应

  • 响应将是一个html文档,状态为204 No content,向调用方指示请求已完成。

3.2.4.2.2。 删除一项资源

删除一项资源

@DELETE
@Path("{id}")
@Produces({ MediaType.TEXT_HTML })
public Response deletePodcastById(@PathParam("id") Long id) {podcastService.deletePodcastById(id);return Response.status(Response.Status.NO_CONTENT)// 204.entity("Podcast successfully removed from database").build();
}

注解

  • @DELETE –表示该方法响应HTTP DELETE请求
  • @Path("{id}") –标识类方法将为其请求服务的URI路径。 “ id”值是构成URI路径模板的嵌入式变量。 它与@PathParam变量结合使用。
    • @PathParam("id") –将URI模板参数(“ id”)的值绑定到资源方法参数。
  • @Produces({MediaType.TEXT_HTML}) –定义该方法可以产生的媒体类型,在这种情况下为“ text / html”。

响应

  • 成功时:如果删除播客,则返回204 No Content成功状态
  • 错误:播客不再可用,并且返回404 Not found状态

4.记录

当日志记录级别设置为DEBUG时,将记录每个请求的路径和响应的实体。 在Jetty过滤器的帮助下,它像包装器一样具有AOP样式的功能。

有关此问题的更多详细信息,请参阅我的文章“ 如何使用SLF4J和Logback登录Spring” 。

5.异常处理

如果出现错误,我决定采用统一的错误消息结构进行响应。 这是一个错误响应的示例:

示例–错误消息响应

{"status": 400,"code": 400,"message": "Provided data not sufficient for insertion","link": "http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-with-jersey-and-spring","developerMessage": "Please verify that the feed is properly generated/set"
}

注意:请继续关注,因为以下文章将提供有关使用Jersey的REST中的错误处理的更多详细信息。

6.在服务器端添加CORS支持

我扩展了为该教程开发的API的功能,以支持服务器端的跨源资源共享(CORS) 。

有关此问题的更多详细信息,请参阅我的文章“ 如何使用Jersey使用Java在服务器端添加CORS支持” 。

7.测试

Java集成测试

为了测试该应用程序,我将使用Jersey Client并针对正在运行的Jetty服务器和部署了该应用程序的服务器执行请求。 为此,我将使用Maven故障安全插件。

7.1.1。 组态

7.1.1.1 Jersey客户端依赖性

要构建Jersey客户端,必须在类路径中提供jersey-client jar。 使用Maven,您可以将其作为依赖项添加到pom.xml文件中:

Jersey Client Maven依赖项

<dependency><groupId>org.glassfish.jersey.core</groupId><artifactId>jersey-client</artifactId><version>${jersey.version}</version><scope>test</scope>
</dependency>

7.1.1.2。 故障安全插件

Failsafe插件用于构建生命周期的集成测试和验证阶段,以执行应用程序的集成测试。 Failsafe插件在集成测试阶段不会使构建失败,因此可以执行集成测试后阶段。要使用故障安全插件,您需要将以下配置添加到pom.xml

Maven故障安全插件配置

<plugins>[...]<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-failsafe-plugin</artifactId><version>2.16</version><executions><execution><id>integration-test</id><goals><goal>integration-test</goal></goals></execution><execution><id>verify</id><goals><goal>verify</goal></goals></execution></executions></plugin>[...]
</plugins>

7.1.1.2。 Jetty Maven插件

集成测试将针对正在运行的码头服务器执行,该服务器仅在执行测试时启动。 为此,您必须在jetty-maven-plugin配置以下执行:

用于集成测试的Jetty Maven插件配置

<plugins><plugin><groupId>org.eclipse.jetty</groupId><artifactId>jetty-maven-plugin</artifactId><version>${jetty.version}</version><configuration><jettyConfig>${project.basedir}/src/main/resources/config/jetty9.xml</jettyConfig><stopKey>STOP</stopKey><stopPort>9999</stopPort><stopWait>5</stopWait><scanIntervalSeconds>5</scanIntervalSeconds>[...]</configuration><executions><execution><id>start-jetty</id><phase>pre-integration-test</phase><goals><!-- stop any previous instance to free up the port --><goal>stop</goal>             <goal>run-exploded</goal></goals><configuration><scanIntervalSeconds>0</scanIntervalSeconds><daemon>true</daemon></configuration></execution><execution><id>stop-jetty</id><phase>post-integration-test</phase><goals><goal>stop</goal></goals></execution></executions></plugin>[...]
</plugins>

注意:pre-integration-test阶段,在停止任何正在运行的实例以释放端口之后,将启动Jetty服务器,在post-integration-phase ,它将停止。 必须将scanIntervalSeconds设置为0,并将daemon为true。

代码警报:在GitHub上找到完整的pom.xml文件

7.1.2。 建立整合测试

我正在使用JUnit作为测试框架。 默认情况下,故障安全插件将自动包含具有以下通配符模式的所有测试类:

  • "**/IT*.java" –包括其所有子目录以及以“ IT”开头的所有Java文件名。
  • "**/*IT.java" –包括其所有子目录以及所有以“ IT”结尾的java文件名。
  • "**/*ITCase.java" –包括其所有子目录以及所有以“ ITCase”结尾的java文件名。

我已经创建了一个测试类RestDemoServiceIT ,它将测试读取(GET)方法,但其他所有过程均应相同:

public class RestDemoServiceIT {[....]@Testpublic void testGetPodcast() throws JsonGenerationException,JsonMappingException, IOException {ClientConfig clientConfig = new ClientConfig();clientConfig.register(JacksonFeature.class);Client client = ClientBuilder.newClient(clientConfig);WebTarget webTarget = client.target("http://localhost:8888/demo-rest-jersey-spring/podcasts/2");Builder request = webTarget.request(MediaType.APPLICATION_JSON);Response response = request.get();Assert.assertTrue(response.getStatus() == 200);Podcast podcast = response.readEntity(Podcast.class);ObjectMapper mapper = new ObjectMapper();System.out.print("Received podcast from database *************************** "+ mapper.writerWithDefaultPrettyPrinter().writeValueAsString(podcast));}
}

注意:

  • 我也必须为客户端注册JacksonFeature,以便可以编组JSON格式的播客响应– response.readEntity(Podcast.class)
  • 我正在针对端口8888上正在运行的Jetty进行测试–下一节将向您展示如何在所需的端口上启动Jetty
  • 我希望我的要求为200
  • 借助org.codehaus.jackson.map.ObjectMapper帮助,我以相当格式显示了JSON响应

7.1.3。 运行集成测试

可以通过调用构建生命周期的verify阶段来调用故障安全插件。

Maven命令调用集成测试

mvn verify

要在端口8888上启动码头,您需要将jetty.port属性设置为8888。在Eclipse中,我使用以下配置:

从Eclipse运行集成测试

与SoapUI的集成测试

最近,在大量使用SoapUI测试基于SOAP的Web服务之后,我重新发现了SoapUI 。 使用最新版本(在撰写本文时,最新版本是5.0.0),它提供了很好的功能来测试基于REST的Web服务,并且以后的版本应该对此进行改进。 因此,除非您开发自己的框架/基础结构来测试REST服务,否则为什么不尝试使用SoapUI。 我做到了,到目前为止,我对结果感到满意,因此我决定制作一个视频教程,现在您可以在我们的频道的YouTube上找到该视频教程:

8.版本控制

有三种主要可能性

  1. 网址:“ / v1 / podcasts / {id}”
  2. 接受/内容类型标头:application / json; 版本= 1

因为我是开发人员,而不是RESTafarian ,所以我会选择URL选项。 在本例中,我在实现方面要做的就是将PodcastResource类上的@Path的值批注从修改为

路径中的版本控制

@Component
@Path("/v1/podcasts")
public class PodcastResource {...}

当然,在生产应用程序上,您不希望每个资源类都以版本号为前缀,而是希望以某种方式通过AOP过滤器对版本进行处理。 也许这样的事情会出现在下面的帖子中……

以下是一些对事情有更好理解的人们的宝贵资源:

  • [视频] REST + JSON API设计–开发人员最佳实践
  • 您的API版本错误,这就是为什么我决定通过@troyhunt用3种不同的错误方式进行操作
  • REST服务版本控制
  • API版本控制的最佳做法? –关于Stackoverflow的有趣讨论

9.总结

好,就是这样。 如果您走了这么远,我必须向您表示祝贺,但是我希望您可以从本教程中了解有关REST的知识,例如设计REST API,用Java实现REST API,测试REST API等。 如果您愿意,我将不胜感激,如果您通过发表评论或在Twitter,Google +或Facebook上分享评论来帮助其传播。 谢谢! 不要忘记也查看Podcastpedia.org ,您肯定会找到有趣的Podcast和情节。 感谢您的支持。

如果您喜欢这篇文章,我们将非常感谢您为我们的工作做出的一点贡献! 立即向Paypal捐款。

10.资源

源代码

  • GitHub – Codingpedia / demo-rest-jersey-spring (有关如何安装和运行项目的说明)

网络资源

  1. HTTP –超文本传输​​协议— HTTP / 1.1 – RFC2616
  2. rfc5789 – HTTP的PATCH方法
  3. 泽西岛用户指南
  4. HTTP状态码定义
  5. REST – http://en.wikipedia.org/wiki/Representational_State_Transfer
  6. CRUD – http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
  7. RESTful服务的Java API(JAX-RS)
  8. 泽西岛-Java中的RESTful Web服务
  9. HTTP PUT,PATCH或POST –是部分更新还是完全替换?
  10. JAX-RS 2.0中的透明PATCH支持
  11. Maven故障安全插件
  12. Maven故障安全插件用法
  13. SoapUI 5.0今天发布了!
  14. SoapUI –使用脚本断言
  15. [视频] REST + JSON API设计–开发人员最佳实践
  16. [视频] RESTful API设计–第二版
  17. 得墨meter耳定律

Codingpedia相关资源

  • 带有Spring,JPA2和Hibernate的Java持久性示例
  • http://www.codingpedia.org/ama/spring-mybatis-integration-example/
  • http://www.codingpedia.org/ama/tomcat-jdbc-connection-pool-configuration-for-production-and-development/
  • http://www.codingpedia.org/ama/error-when-executing-jettyrun-with-jetty-maven-plugin-version-9-java-lang-unsupportedclassversionerror-unsupported-major-minor-version-51-0/
  • http://www.codingpedia.org/ama/autocomplete-search-box-with-jquery-and-spring-mvc/

翻译自: https://www.javacodegeeks.com/2014/08/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring.html

jersey

jersey_教程–带有Jersey和Spring的Java REST API设计和实现相关推荐

  1. 带有Jersey和Spring的RESTful Web应用程序

    几个月前,我们的任务是创建一个API,以向第三方开发人员公开我们系统中的某些功能. 我们选择将这些功能公开为一系列REST Web服务. 我开始使用Jersey ,它是JSR 311 (用于Restf ...

  2. java 接口api设计的注意事项_Java接口设计原则

    类原则 1.单一职责原则 – Single Responsibility Principle(SRP) 就一个类而言,应该仅有一个引起它变化的原因. 职责即为"变化的原因". 2. ...

  3. api 数据gzip压缩_如何使用GZip和Jersey压缩Java REST API中的响应

    api 数据gzip压缩 在某些情况下,您的REST api会提供非常长的响应,我们都知道移动设备/网络上的传输速度和带宽仍然非常重要. 我认为这是开发支持移动应用程序的REST api时需要解决的第 ...

  4. 如何使用GZip和Jersey压缩Java REST API中的响应

    在某些情况下,您的REST api会提供非常长的响应,我们都知道移动设备/网络上的传输速度和带宽仍然非常重要. 我认为这是开发支持移动应用程序的REST api时需要解决的第一个性能优化点. 你猜怎么 ...

  5. swagger api文档_带有Swagger的Spring Rest API –创建文档

    swagger api文档 使REST API易于使用的真正关键是好的文档. 但是,即使您的文档做得很好,您也需要设置公司流程的权利以正确,及时地发布它. 确保利益相关者按时收到是一回事,但是您也要负 ...

  6. 带有Swagger的Spring Rest API –创建文档

    使REST API易于使用的真正关键是好的文档. 但是,即使您的文档做得不错,您也需要设置公司流程的权利,以正确,及时地发布它. 确保利益相关者按时收到是一回事,但是您也要负责API和文档中的更新. ...

  7. Spring 和 JAVA 的牵绊

    Spring 和 JAVA 的牵绊 Spring框架是一个开放源代码的J2EE应用程序框架,是针对bean的生命周期进行管理的轻量级容(lightweight container).Spring提供了 ...

  8. Java编译器API

    本文是我们名为" 高级Java "的学院课程的一部分. 本课程旨在帮助您最有效地使用Java. 它讨论了高级主题,包括对象创建,并发,序列化,反射等. 它将指导您完成Java掌握的 ...

  9. 带有Jersey的JAX-RS教程,用于RESTful Web服务

    在当今世界,数据扮演着非常重要的角色. 如此众多的应用程序将各种类型的数据用于不同的操作,所以最重要的方面是应用程序之间的通信. 当应用程序可以通信时,它们之间的数据共享变得容易. 就像在亚洲运行的应 ...

  10. primefaces教程_Primefaces,带有JPA的Spring 4(Hibernate 4 / EclipseLink)示例教程

    primefaces教程 Java Persistence API is a standard specification. It provides a persistence model that' ...

最新文章

  1. java.lang.Runtime
  2. Kafka事务特性详解
  3. 1230: 最小花费(spfa)
  4. feign 序列化_Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题...
  5. Java并发程序设计(二)Java并行程序基础
  6. 数据挖掘:围绕 统计与概率、分类与聚类、检索方法 ,原理演示或应用程序
  7. python的语法格式
  8. 2020年的5种常见骇客行为,你的电脑安全吗?
  9. Python+Selenium程序执行完,浏览器自动关闭问题
  10. python哥德巴赫猜想
  11. 2022年的国外广告联盟,合格的EMU者有你吗?
  12. kill the one(pjone歌词摘录)
  13. 开发一个会员管理系统
  14. 字符串转换,大写变小写,小写变大写
  15. java-集合框架库-LinkedList
  16. LeetCode:838. 推多米诺————中等
  17. c语言农历的算法,农历两百年算法(1901~2100)【c语言代码】
  18. 【递推】Ybt_平铺方案
  19. Stata:非对称固定效应模型
  20. 用户留存率是什么?如何用sql求出次日留存率?

热门文章

  1. 自动化所宗成庆研究员:108页PPT干货读懂NLP的过去与现在!(附教材PPT)
  2. DICOM worklist入门一
  3. ColorUI 使用文档
  4. nohup 命令的简单理解
  5. 全国30m精度土壤类型分布矢量数据、土壤理化性质数据集
  6. 用51单片机和esp8266实现通过手机app控制单片机小灯
  7. 大学生集成电路设计大赛资源
  8. lammps教程:nve/nvt/npt系综设置方法
  9. java httpclient读取html文件,httpclient+nekohtml 解析HTML
  10. 图像增强总结-Retinex算法