数据为王
数据和软件相比,谁更重要?
如果你问问业务员,他们毫不犹豫的回答数据。
近几年以信息为中心的表述行状态转移(REST)已经替换传统SOAP Web服务的流行方案。SOAP一般会关注行为和处理,而REST关注的是要处理的数据。
了解REST
当谈论REST的时候,有一种常见的错误就是将其视为“基于URL的Web服务”——将REST作为另一种类型的远程过程调用(RPC)机制,就像SOAP一样,只不过是通过简单的HTTP URL来出发,而不是使用SOAP大量的XML命名空间。
其实,恰好相反。REST与RPC几乎没有任何关系,RPC是面向服务的,并关注于行为和动作;而REST是面向资源的,强调应用程序的事物和名词。
为了了解REST到底是什么,我们将它的名字拆分:
- Representational(表述性):REST资源实际上可以用各种形式来进行表述,包括XML/JSON/HTML等等。
- State(状态):当使用REST的时候,我们更关注资源的状态而不是对资源采取的行为。
- Transfer(转移):REST涉及到转移资源数据,它以某种表述性形式从一个应用到另一个应用。
也就是说REST将资源的状态以最合适的形式(JSON或者XML等等)从服务器(客户端)到客户端(服务器)。
在REST中,资源通过URL进行识别和定位。至于RESTful URL的结构并没有严格的规则。但是URL应该能够识别资源,而不是简单的发一条命令到服务器上。再次强调,关注的核心是事物,而不是行为。
REST中会有行为,它们是通过HTTP方法来定义的。具体来讲,也就是GET、POST、PUT、DELETE、PATCH以及其他的HTTP方法构成了REST的动作。
这些HTTP方法匹配为如下的CRUD动作:
- Create:POST
- Read:GET
- Update:PUT/PATCH
- Delete:DELETE
通常来讲HTTP方法映射为CRUD动作,但是这并不严格。因为PUT方法可以用来创建新资源,POST方法也可以用来更新资源。基于对REST的这种观点,所以我尽量避免使用诸如REST服务、REST Web服务或类似的术语,这些术语会不恰当的强调行为。相反,我们应该更愿意强调REST面向资源的本质,并讨论RESTful资源。
Spring是如何支持REST的
- 控制器可以处理所有的HTTP方法,包含四个主要的REST方法:GET/PUT/DELETE/POST,以及之后推出的PATCH。
- 借助@PathVariable,控制器能够处理参数化的URL。
- 借助Spring的视图和视图解析器,资源能够以多种方式进行表述,比如XML,JSON,Atom以及RSS的view实现。
- 可以使用ContentNegotiatingViewResolver来选择最核实的客户端表述。
- 借助@ResponseBody和各种HttpMethodConverter实现,能够替换基于视图的渲染方式。
- 类似的,@RequestBody以及HttpMethodConverter实现可以将传入HTTP数据转化为传入控制器处理方法的Java对象。
- 借助RestTempalte,Spring应用能够方便的使用REST资源。
创建第一个REST端点
借助Spring的支持来实现REST功能有一个很有利的地方,那就是我们已经掌握了很多创建RESTful控制器的知识。
首先,我们会在名为SpittleApiController的新控制器中创建第一个REST端点。
如下代码展现了这个新REST控制器的起始样子,它会提供Spittle资源。
@Controller
@RequestMapping("/spittles")
public class SpittleController {
private static final String MAX_LONG_AS_STRING = "" + Long.MAX_VALUE;
private SpittleRepository spittleRepository;
@Autowired
public SpittleController(SpittleRepository spittleRepository) {
this.spittleRepository = spittleRepository;
}
@RequestMapping(method = RequestMethod.GET)
public List<Spittle> spittles(
@RequestParam(value = "max", defaultValue = MAX_LONG_AS_STRING) long max,
@RequestParam(value = "count", defaultValue = "20") int count
) {
return spittleRepository.findSpittles(max, count);
}
}
你可能会惊讶,这不就是我们之前写的代码么?并没有地方表明它是RESTful、服务于资源的控制器。我们回忆一下,当发起对“/spittles”的GET请求时,将会调用spittles()方法,它会查找并返回Spittle列表。而这个列表会通过注入的SpittleRepository获取到。列表又会被放入模型中,用于视图的渲染。对于基于浏览器的Web应用,这可能意味着模型数据会渲染到HTML页面中。
但是,我们现在讨论的是创建REST API。在这种情况下,HTML并不是合适的数据表述形式。
表述是REST中很重要的一个方面,它是关于客户端和服务器端针对某一资源是如何通信的。你可以使用JSON可以使用XML,甚至使用HTML。
为此,Spring提供了两种方法:
- 内容协商(Content negotiation):选择一个视图,它能够将模型渲染为呈现给客户端的表述形式。
- 消息转换器(Message Conversion):通过一个消息转换器所返回的对象转换为呈现给客户端的表述形式。
协商资源表述
当控制器的处理方法返回时,会有一个return "xxx"的视图名,如果方法不直接返回视图名,那么会根据请求的url判断得出。
在面向人类访问的Web应用程序中,通常选择的视图都会渲染为HTML。当要将视图名解析为能够产生资源表述的视图时,我们就有另外一个纬度需要考虑了。视图不仅要匹配视图名,而且所选择的视图要合适的客户端。如果客户端想要JSON,那么渲染HTML的视图就不行了。
Spring的ContentNegotiatingViewResolver是一个特殊的视图解析器,它考虑到了客户端所需要的内容类型。ContentNegotiatingViewResolver可以按照下述的形式就行配置:
@Bean
public ViewResolver cnViewResolver(){
return new ContentNegotiatingViewResolver();
}
在这个简单的bean声明背后会涉及到很多事情。要理解ContentNegotiatingViewResolver如何工作,这设计到内容协商的两个步骤:
- 确定请求的媒体类型。
- 找到核实请求媒体类型的最佳视图。
让我们深入了解每个步骤来了解ContentNegotiatingViewResolver如何完成其任务的,首先从弄明白客户端需要什么类型的内容开始。
确定请求的媒体类型
表面上看,这可能是很简单的事,难道请求的Accept头部信息不是清楚的表明要发送什么样的表述给客户端了么?遗憾的是,Accept的头部信息并不总是可靠的。如果客户端是Web浏览器,那并不能保证客户端需要的类型就是浏览器在Accept头部所发送的值。Web浏览器一般只接受对人类用户友好的内容类型(text/html)。
ContentNegotiatingViewResolver会考虑到Accept头部信息,并使用它所请求的媒体类型,但是它会首先查看URL的文件拓展名,如果含有拓展名的话,ContentNegotiatingViewResolver会基于该拓展名确定所需类型。如果拓展名为json,那么所需内容必须是“application/json”,如果拓展名为".xml"的话,那么就是"application/xml",当然,html拓展名表明客户端所需的资源表述为HTML(text/html)。
如果识别不出,才会考虑Accept头部信息。再如果两者都没有,那么会使用“/”作为默认的内容类型。
影响媒体类型的选择
在上述的选择过程中,我们阐述了确定所请求媒体类型的默认策略。但是通过为其设置一个ContentNegotiatingManager,我们能改变它的默认行为,比如忽视请求的Accept头部信息等等。
一般而言我们使用XML配置ContentNegotiatingManager的话,那最有用的将会是ContentNegotiatingManagerFactoryBean。例如,我们可能希望在XML中配置ContentNegotiatingManager使用“applicaition/json”作为默认的内容类型。
而如果使用Java配置,最简单的方法就是扩展WebMvcConfigurerAdapter并重载configureContentNegotiation()方法:
idea重载快捷键ctrl+o (o表示override)
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
我们可以看到,这个方法参数中有一个ContentNegotiationConfigurer对象,ContentNegotiationConfigurer中的一些方法对应于ContentNegotiationManager的Setter方法,这样我们就能在ContentNegotiationManager创建时,设置任意内容协商相关的属性。我们调用defaultContentType()将默认的内容类型设置为"application/json"。
现在我们已经有了ContentNegotiationManager bean,接下来就需要将它注入到ContentNegotiatingViewResolver
的contentNegotiationManager属性中,这需要我们修改一下之前声明的ContentNegotiatingViewResolver的@Bean方法:
@Bean
public ViewResolver cnViewResolver(ContentNegotiationManager cnm) {
ContentNegotiatingViewResolver cnvr =
new ContentNegotiatingViewResolver();
cnvr.setContentNegotiationManager(cnm);
return cnvr;
}
这个@Bean方法注入了ContentNegotiationManager,并且使用它调用了setContentNegotiationManager()。这样的结果将会是ContentNegotiatingViewResolver将会使用ContentNegotiationManager所定义的行为。
配置ContentNegotionManager有很多细节,这里无法对其一一叙述。如下的程序清单是一个非常简单的配置样例,当我使用ContentNegotiatingViewResolver的时候,通常会使用这种方法:
它会默认使用HTML视图,但是对特定的视图名称会渲染为JSON输出:
@Configuration
public static class ContentNegotiationConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver cnViewResolver(ContentNegotiationManager cnm) {
ContentNegotiatingViewResolver cnvr =
new ContentNegotiatingViewResolver();
cnvr.setContentNegotiationManager(cnm);
return cnvr;
}
@Override
//默认为HTML
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.TEXT_HTML);
}
@Bean
//以bean的形式查找视图
public ViewResolver beanNameViewResolver() {
return new BeanNameViewResolver();
}
@Bean
//将“spittles”定义为JSON视图
public View spittles() {
return new MappingJackson2JsonView();
}
}
}
使用HTTP信息转换器
暂略