将Spring MVC RESTful Web服务迁移到Spring 4

1引言

Spring 4为MVC应用程序带来了一些改进 。 在这篇文章中,我将重点介绍宁静的Web服务,并通过采用Spring 3.2实现的项目并将其升级到Spring 4来尝试这些改进。以下几点总结了本文的内容:

  • 从Spring 3.2迁移到Spring 4.0
  • 变化中的@ResponseBody和包容的@RestController
  • 同步和异步调用

以下项目的源代码可以在github上找到:

  • 原始项目(Spring3.2)
  • 迁移到Spring 4

2 Spring 3.2 RESTful示例

起始项目使用Spring 3.2( pom.xml )实现。 它包含一个Spring MVC应用程序,该应用程序访问数据库以检索有关电视连续剧的数据。 让我们看一下其REST API,以使其更加清晰:

Blog-rest-api

弹簧配置

<import resource="db-context.xml"/><!-- Detects annotations like @Component, @Service, @Controller, @Repository, @Configuration -->
<context:component-scan base-package="xpadro.spring.web.controller,xpadro.spring.web.service"/><!-- Detects MVC annotations like @RequestMapping -->
<mvc:annotation-driven/>

db-context.xml

<!-- Registers a mongo instance -->
<bean id="mongo" class="org.springframework.data.mongodb.core.MongoFactoryBean"><property name="host" value="localhost" />
</bean><bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"><constructor-arg name="mongo" ref="mongo" /><constructor-arg name="databaseName" value="rest-db" />
</bean>

服务实施

此类负责从mongoDB数据库中检索数据:

@Service
public class SeriesServiceImpl implements SeriesService {@Autowiredprivate MongoOperations mongoOps;@Overridepublic Series[] getAllSeries() {List<Series> seriesList = mongoOps.findAll(Series.class);return seriesList.toArray(new Series[0]);}@Overridepublic Series getSeries(long id) {return mongoOps.findById(id, Series.class);}@Overridepublic void insertSeries(Series series) {mongoOps.insert(series);}@Overridepublic void deleteSeries(long id) {Query query = new Query();Criteria criteria = new Criteria("_id").is(id);query.addCriteria(criteria);mongoOps.remove(query, Series.class);}
}

控制器实施

该控制器将处理请求并与服务进行交互,以检索系列数据:

@Controller
@RequestMapping(value="/series")
public class SeriesController {private SeriesService seriesService;@Autowiredpublic SeriesController(SeriesService seriesService) {this.seriesService = seriesService;}@RequestMapping(method=RequestMethod.GET)@ResponseBodypublic Series[] getAllSeries() {return seriesService.getAllSeries();}@RequestMapping(value="/{seriesId}", method=RequestMethod.GET)public ResponseEntity<Series> getSeries(@PathVariable("seriesId") long id) {Series series = seriesService.getSeries(id);if (series == null) {return new ResponseEntity<Series>(HttpStatus.NOT_FOUND);}return new ResponseEntity<Series>(series, HttpStatus.OK);}@RequestMapping(method=RequestMethod.POST)@ResponseStatus(HttpStatus.CREATED)public void insertSeries(@RequestBody Series series, HttpServletRequest request, HttpServletResponse response) {seriesService.insertSeries(series);response.setHeader("Location", request.getRequestURL().append("/").append(series.getId()).toString());}@RequestMapping(value="/{seriesId}", method=RequestMethod.DELETE)@ResponseStatus(HttpStatus.NO_CONTENT)public void deleteSeries(@PathVariable("seriesId") long id) {seriesService.deleteSeries(id);}
}

整合测试

这些集成测试将在模拟Spring MVC环境中测试我们的控制器。 这样,我们将能够测试处理程序方法的映射。 为此, MockMvc类变得非常有用。 如果您想学习如何编写Spring MVC控制器的测试,我强烈推荐Petri Kainulainen编写的Spring MVC Test Tutorial系列。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations={"classpath:xpadro/spring/web/test/configuration/test-root-context.xml","classpath:xpadro/spring/web/configuration/app-context.xml"})
public class SeriesIntegrationTest {private static final String BASE_URI = "/series";private MockMvc mockMvc;@Autowiredprivate WebApplicationContext webApplicationContext;@Autowiredprivate SeriesService seriesService;@Beforepublic void setUp() {reset(seriesService);mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();when(seriesService.getAllSeries()).thenReturn(new Series[]{new Series(1, "The walking dead", "USA", "Thriller"), new Series(2, "Homeland", "USA", "Drama")});when(seriesService.getSeries(1L)).thenReturn(new Series(1, "Fringe", "USA", "Thriller"));}@Testpublic void getAllSeries() throws Exception {mockMvc.perform(get(BASE_URI).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(content().contentType("application/json;charset=UTF-8")).andExpect(jsonPath("$", hasSize(2))).andExpect(jsonPath("$[0].id", is(1))).andExpect(jsonPath("$[0].name", is("The walking dead"))).andExpect(jsonPath("$[0].country", is("USA"))).andExpect(jsonPath("$[0].genre", is("Thriller"))).andExpect(jsonPath("$[1].id", is(2))).andExpect(jsonPath("$[1].name", is("Homeland"))).andExpect(jsonPath("$[1].country", is("USA"))).andExpect(jsonPath("$[1].genre", is("Drama")));verify(seriesService, times(1)).getAllSeries();verifyZeroInteractions(seriesService);}@Testpublic void getJsonSeries() throws Exception {mockMvc.perform(get(BASE_URI + "/{seriesId}", 1L).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(content().contentType("application/json;charset=UTF-8")).andExpect(jsonPath("$.id", is(1))).andExpect(jsonPath("$.name", is("Fringe"))).andExpect(jsonPath("$.country", is("USA"))).andExpect(jsonPath("$.genre", is("Thriller")));verify(seriesService, times(1)).getSeries(1L);verifyZeroInteractions(seriesService);}@Testpublic void getXmlSeries() throws Exception {mockMvc.perform(get(BASE_URI + "/{seriesId}", 1L).accept(MediaType.APPLICATION_XML)).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_XML)).andExpect(xpath("/series/id").string("1")).andExpect(xpath("/series/name").string("Fringe")).andExpect(xpath("/series/country").string("USA")).andExpect(xpath("/series/genre").string("Thriller"));verify(seriesService, times(1)).getSeries(1L);verifyZeroInteractions(seriesService);}
}

我正在展示一些已实施的测试。 检查SeriesIntegrationTesting以获得完整的实现。

功能测试

该应用程序使用RestTemplate类包含一些功能测试。 您需要部署Web应用程序才能对此进行测试。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:xpadro/spring/web/configuration/root-context.xml","classpath:xpadro/spring/web/configuration/app-context.xml"})
public class SeriesFunctionalTesting {private static final String BASE_URI = "http://localhost:8080/spring-rest-api-v32/spring/series";private RestTemplate restTemplate = new RestTemplate();@Autowiredprivate MongoOperations mongoOps;@Beforepublic void setup() {List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();converters.add(new StringHttpMessageConverter());converters.add(new Jaxb2RootElementHttpMessageConverter());converters.add(new MappingJacksonHttpMessageConverter());restTemplate.setMessageConverters(converters);initializeDatabase();}private void initializeDatabase() {try {mongoOps.dropCollection("series");mongoOps.insert(new Series(1, "The walking dead", "USA", "Thriller"));mongoOps.insert(new Series(2, "Homeland", "USA", "Drama"));} catch (DataAccessResourceFailureException e) {fail("MongoDB instance is not running");}}@Testpublic void getAllSeries() {Series[] series = restTemplate.getForObject(BASE_URI, Series[].class);assertNotNull(series);assertEquals(2, series.length);assertEquals(1L, series[0].getId());assertEquals("The walking dead", series[0].getName());assertEquals("USA", series[0].getCountry());assertEquals("Thriller", series[0].getGenre());assertEquals(2L, series[1].getId());assertEquals("Homeland", series[1].getName());assertEquals("USA", series[1].getCountry());assertEquals("Drama", series[1].getGenre());}@Testpublic void getJsonSeries() {List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();converters.add(new MappingJacksonHttpMessageConverter());restTemplate.setMessageConverters(converters);String uri = BASE_URI + "/{seriesId}";ResponseEntity<Series> seriesEntity = restTemplate.getForEntity(uri, Series.class, 1l);assertNotNull(seriesEntity.getBody());assertEquals(1l, seriesEntity.getBody().getId());assertEquals("The walking dead", seriesEntity.getBody().getName());assertEquals("USA", seriesEntity.getBody().getCountry());assertEquals("Thriller", seriesEntity.getBody().getGenre());assertEquals(MediaType.parseMediaType("application/json;charset=UTF-8"), seriesEntity.getHeaders().getContentType());}@Testpublic void getXmlSeries() {String uri = BASE_URI + "/{seriesId}";ResponseEntity<Series> seriesEntity = restTemplate.getForEntity(uri, Series.class, 1L);assertNotNull(seriesEntity.getBody());assertEquals(1l, seriesEntity.getBody().getId());assertEquals("The walking dead", seriesEntity.getBody().getName());assertEquals("USA", seriesEntity.getBody().getCountry());assertEquals("Thriller", seriesEntity.getBody().getGenre());assertEquals(MediaType.APPLICATION_XML, seriesEntity.getHeaders().getContentType());}
}

就是这样,Web应用程序已经过测试并正在运行。 现在是时候迁移到Spring 4了。

3迁移到Spring 4

检查此页面以阅读有关从早期版本的Spring框架进行迁移的信息

3.1更改Maven依赖项

本节说明应修改哪些依赖项。 您可以在此处查看完整的pom.xml。

第一步是将Spring依赖版本从3.2.3.RELEASE更改为4.0.0.RELEASE:

<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.0.0.RELEASE</version>
</dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.0.0.RELEASE</version>
</dependency>

下一步是更新到Servlet 3.0规范。 这一步很重要,因为某些Spring功能基于Servlet 3.0,因此将不可用。 事实上,试图执行SeriesIntegrationTesting将导致一个ClassNotFoundException由于这个原因,这也解释了这里 。

<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version>
</dependency>

3.2更新Spring名称空间

不要忘记更改spring配置文件的名称空间:

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd

请查看第2节中链接的信息页面,因为有关mvc名称空间的某些更改。

3.3弃用杰克逊库

如果再次检查SeriesFunctionalTesting(设置方法),您会发现杰克逊转换器已被弃用。 如果您尝试运行测试,由于Jackson库中的方法更改,它将抛出NoSuchMethodError:

java.lang.NoSuchMethodError: org.codehaus.jackson.map.ObjectMapper.getTypeFactory()Lorg/codehaus/jackson/map/type/TypeFactory

在Spring4中,已不再支持Jackson 1.x,而支持Jackson v2。 让我们更改旧的依赖项:

<dependency><groupId>org.codehaus.jackson</groupId><artifactId>jackson-mapper-asl</artifactId><version>1.4.2</version>
</dependency>

对于这些:

<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.3.0</version>
</dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.3.0</version>
</dependency>

最后,如果要显式注册消息转换器,则需要为新版本更改不推荐使用的类:

//converters.add(new MappingJacksonHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());

3.4迁移完成

迁移完成。 现在,您可以运行该应用程序并执行其测试。 下一节将回顾我在本文开头提到的一些改进。

4 Spring 4 Web改进

4.1 @ResponseBody和@RestController

如果您的REST API以JSON或XML格式提供内容,则某些API方法(用@RequestMapping注释)的返回类型将用@ResponseBody注释。 使用此注释,返回类型将包含在响应主体中。 在Spring4中,我们可以通过两种方式简化此过程:

用@ResponseBody注释控制器

现在可以在类型级别添加此注释。 这样,注释就被继承了,我们不必强迫将注释放在每个方法中。

@Controller
@ResponseBody
public class SeriesController {

用@RestController注释控制器

@RestController
public class SeriesController {

此注释甚至进一步简化了控制器。 如果我们检查此注释,我们将看到它本身已使用@Controller和@ResponseBody进行了注释:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

包括此注释将不会影响使用@ResponseEntity注释的方法。 处理程序适配器查找返回值处理程序的列表,以确定谁有能力处理响应。 的
在ResponseBody类型之前会先询问负责处理ResponseEntity返回类型的处理程序 ,因此,如果方法中存在ResponseEntity批注,则将使用该处理程序。

4.2异步调用

使用实用程序类RestTemplate调用RESTful服务将阻塞线程,直到它收到响应为止。 Spring 4包含AsyncRestTemplate以便执行异步调用。 现在,您可以拨打电话,继续进行其他计算,并在以后检索响应。

@Test
public void getAllSeriesAsync() throws InterruptedException, ExecutionException {logger.info("Calling async /series");Future<ResponseEntity<Series[]>> futureEntity = asyncRestTemplate.getForEntity(BASE_URI, Series[].class);logger.info("Doing other async stuff...");logger.info("Blocking to receive response...");ResponseEntity<Series[]> entity = futureEntity.get();logger.info("Response received");Series[] series = entity.getBody();assertNotNull(series);assertEquals(2, series.length);assertEquals(1L, series[0].getId());assertEquals("The walking dead", series[0].getName());assertEquals("USA", series[0].getCountry());assertEquals("Thriller", series[0].getGenre());assertEquals(2L, series[1].getId());assertEquals("Homeland", series[1].getName());assertEquals("USA", series[1].getCountry());assertEquals("Drama", series[1].getGenre());
}

带回调的异步调用

尽管前面的示例进行了异步调用,但如果尚未发送响应,则尝试使用futureEntity.get()检索响应时,线程将阻塞。

AsyncRestTemplate返回ListenableFuture ,它扩展了Future并允许我们注册一个回调。 以下示例进行异步调用,并继续执行其自己的任务。 当服务返回响应时,它将由回调处理:

@Test
public void getAllSeriesAsyncCallable() throws InterruptedException, ExecutionException {logger.info("Calling async callable /series");ListenableFuture<ResponseEntity<Series[]>> futureEntity = asyncRestTemplate.getForEntity(BASE_URI, Series[].class);futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<Series[]>>() {@Overridepublic void onSuccess(ResponseEntity<Series[]> entity) {logger.info("Response received (async callable)");Series[] series = entity.getBody();validateList(series);}@Overridepublic void onFailure(Throwable t) {fail();}});logger.info("Doing other async callable stuff ...");Thread.sleep(6000); //waits for the service to send the response
}

5结论

我们使用了Spring 3.2.x Web应用程序,并将其迁移到Spring 4.0.0的新版本中。 我们还回顾了可以应用于Spring 4 Web应用程序的一些改进。

我正在Google Plus和Twitter上发布我的新帖子。 如果您要更新新内容,请关注我。

参考:在XavierPadró的Blog博客上,从我们的JCG合作伙伴 Xavier Padro将Spring MVC RESTful Web服务迁移到Spring 4 。

翻译自: https://www.javacodegeeks.com/2014/02/migrating-spring-mvc-restful-web-services-to-spring-4.html

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

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

相关文章

java scrollpane 设置透明_java swing 之 JScrollPane(滚动面板)的使用

/*** java swing 之JScrollPane面板* 在设置界面时&#xff0c;可能会遇到在一个较小的容器窗体中显示一个较大部分的内容&#xff0c;这时可以使用* JScrollPane面板&#xff0c;JscrollPane面板是带滚动条的面板&#xff0c;也是一种容器&#xff0c;但是常用于布置单个* 控件…

软件工程(2019)第三次个人作业

目录 软件工程第三次作业问题描述分析并设计程序程序流程图选择覆盖标准并设计测试样例软件工程第三次作业 项目地址 问题描述 题目(1)&#xff1a;最大连续子数组和&#xff08;最大子段和&#xff09; 背景 问题&#xff1a; 给定n个整数&#xff08;可能为负数&#xff09;组…

Flutter - 创建侧滑菜单

侧滑菜单在安卓App里面非常常见&#xff0c;比如Gmail&#xff0c;Google Play&#xff0c;Twitter等。看下图 网上也有很多创建侧滑菜单的教程&#xff0c;我也来记录一下&#xff0c;自己学习创建Drawer的过程。 1. 创建一个空的App import package:flutter/material.dart;cl…

java框架白话_Java NIO框架Netty教程(二) 白话概念

"Hello World"的代码固然简单&#xff0c;不过其中的几个重要概念(类)和 Netty的工作原理还是需要简单明确一下&#xff0c;至少知道其是负责什。方便自己以后更灵活的使用和扩展。声明&#xff0c;笔者一介码农&#xff0c;不会那么多专业的词汇和缩写&#xff0c;只…

luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并

其实很水的一道题吧.... 题意是&#xff1a;每次给定一个串\(T\)以及\(l, r\)&#xff0c;询问有多少个字符串\(s\)满足&#xff0c;\(s\)是\(T\)的子串&#xff0c;但不是\(S[l .. r]\)的子串 统计\(T\)本质不同的串&#xff0c;建个后缀自动机 然后自然的可以想到&#xff0c…

团队计划会议

跟航哥想了挺多要做什么&#xff0c;要完成什么&#xff0c;以什么为主要功能 提出了几个想法&#xff0c;并做了投票 最后决定一起做一个跑腿软件 最初的任务量&#xff1a; 跟航哥商量两个人一人负责两个模块 航哥负责管理员和下单 我负责接单跟其他琐碎的小功能 呐&#xff…

vue.js java php_准吗?Java程序员喜欢AngularJS,PHP程序员喜欢Vue.js!

编程语言与框架或者库之间有联系是很正常的事情&#xff0c;如果我们告诉你&#xff0c;使用某一种编程语言或技术的开发人员可能更喜欢某个框架&#xff0c;你会作何反应呢&#xff1f;Stack Overflow根据网站内最常访问的标签将开发人员分为多个组&#xff0c;并检查了每组每…

结构化日志:出错时你最想要的好朋友

目录 介绍什么是日志&#xff1f;Grab中日志的状况为什么改变&#xff1f;结构化日志支持不同格式的多写开发中类似生产环境的日志因果顺序但为什么要结构化记日志&#xff1f;原文&#xff1a;Structured Logging: The Best Friend You’ll Want When Things Go Wrong 介绍 在…

在vue项目中添加特殊字体

这里的特殊字体&#xff0c;指的是一般用户电脑未安装到本地的字体&#xff0c;要引入这样的字体&#xff0c;首先需要把字体文件下载下来。 就像上图这样的&#xff0c;ttf格式的&#xff0c;然后在项目里添加它。 然后我们在font.css里用font-face规则引入这个字体文件并命名…

使用Spring WS创建合同优先的Web服务

1引言 本文介绍了如何使用来实现和测试SOAP Web服务 Spring Web Services项目 。 本示例使用JAXB2进行&#xff08;取消&#xff09;编组。 为了开发服务&#xff0c;我将使用合同优先的方法&#xff0c;该方法首先定义服务合同&#xff0c;然后基于该合同实施服务。 本文分为…

转载 Net多线程编程—System.Threading.Tasks.Parallel

.Net多线程编程—System.Threading.Tasks.Parallel System.Threading.Tasks.Parallel类提供了Parallel.Invoke&#xff0c;Parallel.For&#xff0c;Parallel.ForEach这三个静态方法。 1 Parallel.Invoke 尽可能并行执行所提供的每个操作&#xff0c;除非用户取消了操作。 方法…

Fiddler教程--简介

1、开发环境host配置自己修改系统的host来回挺麻烦的 2、前后的接口调试 3、线上bugfix 4、性能分析和优化 5.等等... 工作原理 一个代理服务器地址改为 127.0.0.1:8888流模式边走边返回缓冲模式http请求完成所有的数据之后&#xff0c;才返回 界面功能介绍 1.工具栏 从下图红色…

跟面向对象卯上了,看看ES6的“类”

上回我们说到ES5的面向对象&#xff0c;以及被大家公认的最佳的寄生组合式继承。时代在进步&#xff0c;在ES6中对于面向对象这个大boss理所应当地进行了一次大改&#xff0c;从原先那种比较长的写法转变为“小清新”写法。我们一起来看一下。 在ES6中是有类这个概念&#xff0…

Java 8中的5个功能将改变您的编码方式

Java 8在JVM和语言级别都包含了一些非常令人兴奋的功能。 虽然最初为该发行版设想的某些功能已扩大范围或已推出到第9版&#xff0c;但实际上有数十个新功能。 许多新添加的内容在编译器&#xff0c;JVM或帮助系统级别都进行了后台改进。 这样&#xff0c;虽然我们可能会从中受…

Java相关资料分享(视频+电子书籍)

关注微信公众号【Java典籍】&#xff0c;获取百度网盘提取码 ▼微信扫一扫下图↓↓↓二维码关注 转载于:https://www.cnblogs.com/bingyimeiling/p/10279049.html

vue项目 一行js代码搞定点击图片放大缩小

一行js代码搞定xue项目需要点击图片放大缩小&#xff0c;其实主要用的是用到了vue:class的动态切换&#xff0c;内容比较简单。一开始我把维护的需求想得太复杂了&#xff0c;和测试小姐姐聊了一下才反应过来。 两个月不到跟了四个项目&#xff0c;现在是维护改bug阶段&#x…

指针系统学习8-小结

1.有关指针的数据类型的小结 2.指针运算小结 一、指针变量加&#xff08;减&#xff09;一个整数,会指向上&#xff08;下&#xff09;1&#xff08;i&#xff09;个元素  例如&#xff1a;&#xff50;&#xff0b;&#xff0b;、&#xff50;&#xff0d;&#xff0d;、&am…

Spring集成–使用RMI通道适配器

1.引言 本文介绍了如何使用Spring Integration RMI通道适配器通过RMI发送和接收消息。 它由以下部分组成&#xff1a; 实施服务&#xff1a;第一部分着重于创建和公开服务。 实现客户端&#xff1a;显示如何使用MessagingTemplate类调用服务。 抽象SI逻辑&#xff1a;最后&a…

jquery点击非div区域隐藏div

点击非div区域隐藏div&#xff0c;如图&#xff0c;点击圆的头像&#xff08;.person-msg&#xff09;弹出白色底框(.person-centre)。点击圆头像以外的区域隐藏白色底框 html代码 <div class"per_c"><div class"person-msg pull-right"><i…

如何在Ubuntu上轻松安装Oracle Java

Ubuntu上的开发人员习惯于至少看到两种Java风格。 OpenJDK是Java运行时和编译器的开源构建。 Oracle JDK以此为基础&#xff0c;但是增加了一些封闭源组件。 从理论上讲&#xff0c;OpenJDK是Java 7的官方参考实现 &#xff0c;并且完全可以满足您的所有需求。 在实践中&#…