jersey spring

想要在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>

泽西小服务器

注意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类如下所示:

播客

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 ,位置标头指定在何处创建资源
  • 错误时:
    • 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 ,位置标头指定在何处创建资源
  • 错误时:
    • 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 spring

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

  1. Spring 和 JAVA 的牵绊

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

  2. jersey spring_实施Jersey 2 Spring集成

    jersey spring Jersey是Oracle提供的出色的Java JAX-RS规范参考实现. 去年,当我们开始为大容量网站构建RESTful后端Web服务时,我们选择使用JAX-RS API ...

  3. jersey put 服务_项目学生:带有Jersey的Web服务服务器

    jersey put 服务 这是Project Student的一部分. 其他职位包括带有Jersey的Webservice Client , 业务层和带有Spring Data的持久性 . REST ...

  4. jersey客户端_项目学生:带有Jersey的Web服务客户端

    jersey客户端 这是Project Student的一部分. 其他职位包括带有Jersey的Webservice Client, 带有Spring Data的 业务层和持久性 . RESTful ...

  5. 项目学生:带有Jersey的Web服务服务器

    这是Project Student的一部分. 其他职位包括带有Jersey的Webservice Client , 业务层和带有Spring Data的持久性 . RESTful Web应用程序洋葱的 ...

  6. 项目学生:带有Jersey的Web服务客户端

    这是Project Student的一部分. 其他职位包括带有Jersey的Webservice Client , 业务层和带有Spring Data的持久性 . RESTful Web应用程序洋葱的 ...

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

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

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

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

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

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

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

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

最新文章

  1. 完美解决百度地图MarkerClusterer 移动地图时,Marker 的Label 丢失的问题
  2. ASP.Net分页组件1.0开发下载了...
  3. 实录分享 | 计算未来轻沙龙:“法律+AI”前沿研讨会(PPT下载)
  4. VBA and Access
  5. obs多推流地址_苹果手机IOS系统:抖音+易推流直播手机游戏——墨涩网
  6. 网站性能测试指标:QPS、TPS、吞吐量、响应时间概述
  7. SAP License:煤化工行业名词MTOMTP
  8. 华为机试HJ51:输出单向链表中倒数第k个结点
  9. win10 C语言qt调试,如何在Windows中调试Qt(MSVC)应用程序
  10. 多功能雨伞项目计划书_共享雨伞商业计划书完整版.doc
  11. 计算机毕业论文基于Python实现的仓库库存管理系统进销存储系统
  12. Linux 挂载 IP SAN
  13. 图中PNP型三极管发射极和基极为什么要加个100K(R21)的电阻?
  14. 二叉树期权定价与BSM期权定价
  15. 在线Markdown table生成工具,Markdown 表格编辑器
  16. zb薄片怎么往里加厚_烘焙爱好者的必修课----无敌详细的【千层酥皮】做法
  17. 前端必会的anime动画库
  18. BootStrap一页通(样式+组件+插件)
  19. 直观上理解PCA中特征值和特征向量
  20. 中国艺术孙溟㠭书画《光》

热门文章

  1. PDM - Product Data Management(产品数据管理)
  2. 模型设置(1)——边界条件
  3. imx226_【索尼IMX136LQJ-C、IMX236LQJ-C、IMX226CQJ-C、IMX274LQC-C、】价格_厂家 - 中国供应商...
  4. 旋转矩阵中6保6_双色球旋转矩阵公式中6保4的
  5. 四菱天线怎么加强_关于自制电视四菱天线材料好坏和买连接线和转接头问题
  6. c++求平均值_2020五一建模:C题 饲料混合加工(二)
  7. 红蜘蛛多媒体网络教室v7.2版一款网络教学的软件
  8. matlab版VLFeat安装
  9. python爬淘宝评论源代码_一篇文章教会你用Python爬取淘宝评论数据(写在记事本)...
  10. lammps教程:晶体建模之Atomsk方法(1)