RPC是面向服务的,并关注与行为和动作;而REST是面向资源的,强调描述应用程序的事务的名词。REST将资源的状态以最适合客户端或服务端的形式从服务器端转移到客户端。
REST
Representational(表述性):REST资源实际上可以用各种形式来进行表述,包括XML,JSON深圳HTML
State(状态):当使用REST的时候,我们更关注资源的状态而不是对资源采取的行为
Transfer(转移):REST涉及到转移资源数据,它以某种表属性形式从一个应用转移到另一个应用
在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。这些HTTP方法通常会匹配如下CRUD动作:
Create:POST
Read:GET
Update:PUT或PATCH
Delete:DELETE
Spring支持一下方式来创建REST资源:
控制器可以出了力所有的HTTP方法,包含四个主要的REST方法:GET、PUT、DELETE和POST。Spring3.2及以上版本还支持PATCH方法
借助@PathVariable注解,控制其能够处理参数化URL
借助Spring的视图和视图解析器,资源能够以多种方式进行表述,包括将模型数据渲染为XML、JSON、Atom以及RSS的View实现
可以使用ContentNegotitaingViewResolver来选择最合适客户端的表述
借助@ResponseBody注解和各种HttpMethodConvertor实现,能够替换基于视图的渲染方式
@RequestBody注解以及HttpMethodConvertor实现可以将传入的HTTP数据转化为传入控制器处理方法的Java对象
借助RestTemplate,Spring应用能够方便的使用REST资源
Spring提供了两种方法将资源的Java表述形式转换为发送给客户端的表述形式
内容协商(Content negotiation):选择一个视图,它能够将模型渲染为呈现给客户端的表述形式
消息转换器(Message conversion):通过一个消息转换器将控制器所返回的对象转换为呈现给客户端的表述形式
Spring的ContentNegotiatingViewResolver是一个特殊的视图解析器,它考虑到了客户端所需要的内容类型。按照其最简单的形式,ContentNegitiatingViewResolver可以按照如下配置,它可以完成两个功能:确定请求的媒体类型和找到合适请求媒体类型的最佳视图
@Bean public ViewResolver cnViewResolver(){ return new ContentNegotiatingViewResolver(); }
确定请求的媒体类型
ContentNegotitaingViewResolver会考虑Accept头部信息并使用它所请求的媒体类型,但是它会首先查看URL的文件扩展名。如果URL在结尾处有文件扩展名,ContentNegotiatingViewResolver将会给予该扩展名确定所需的类型,如果扩展名是.json,那么所需的内容类型必须是"application/json"。如果扩展名是.xml,那么客户端请求的是"application/xml"。如果根据文件扩展名不能得到任何媒体类型的话,那么就会考虑Accept头部信息。此时,Accept头部信息中的值就表明了客户端想要的MIME类型。如果没有Accept头部信息,并且扩展名也无法提供任何帮助的话,ContentNegotiatingViewResolver将会使用/作为默认的内容,这意味着客户端必须要接受服务器发送的任何形式的表述。一旦内容类型确定之后,ContentNegotiatingViewResolver就会将逻辑视图名解析为渲染模型的View,ContentNegotiatingViewResolver本身不会解析视图,而是委托给其他的视图解析器,让它们来解析视图。ContentNegotiatingViewResolver要求其他的视图解析器将逻辑视图名解析为视图。解析得到的每个视图都会放到一个列表中,这个列表装配完成后,ContentNegotiatingViewResolver会循环客户端请求的所有媒体类型,在候选的收中查找能够产生对应内容类型的视图。第一个匹配的视图会用来渲染模型。
影响媒体类型的选择
通过设置ContentNegotiationManager可以改变选择媒体类型的策略,ContentNegotiationManager可以做到
指定默认的内容类型,如果根据请求无法得到内容类型的话,会使用默认值
通过请求参数指定内容类型
忽视请求的Accept头部信息
将请求的扩展名映射为特定的媒体类型
将JAF(Java Activation Framework)作为根据扩展名名查找媒体类型的备用方案
有三种配置ContentNegotiationManager方法
直接声明一个ContentNegotiationManager类型的bean
通过ContentNegotiationManagerFactoryBean间接创建bean
重载WebMvcConfigurerAdapater的configureContentNegotiation()方法。
使用Java配置,获得ContentNegotiationManager的最简便方法就是扩展WebMvcConfigurerAdapter并重新configureContentNegotiation()方法。
@Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer){ configurer.defaultContentType(MediaType.APPLICATION_JSON); }
ContentNegotiatingViewResolver最大的优势在于它在Spring MVC之上构建了REST资源表述层,控制器代码无需修改。相同一套控制器方法能够产生HTML,也可以产生JSON或XML。ContentNegotiatingViewResolver作为ViewResolver的实现,它只能决定资源该如何渲染到客户端,并没有涉及到客户端要发送什么样的表述给控制器,如果客户端发送JSON或XML的话,ContentNegotiatingViewResolver就无法识别。
使用HTTP信息转换器
消息转换(message conversion)提供了一种更为直接的方式,它能将控制器产生的数据转换为服务于客户端的表述形式。当使用消息转换功能时,DispatcherServlet不再需要那么麻烦地将模型数据传送到视图中。
Spring自带了各种各样的转换器,用于实现资源表述与各种Java类型之间的互相转换
AtomFeedHttpMessageConverter Rome Feed对象和Atom feed之间相互转换(如果Rome包在类路径下将会进行注册)
BufferedImageHttpMessageConverter BufferedImages与图片二进制数据之间相互转换
ByteArrayHttpMessageConverter 读取/写入字节数组。从所有媒体类型中读取,并以application/octet-stream格式写入
FormHttpMessageConverter 将application/x-www-form-urlencode内容读入到MultiValueMap<String, String>中,也会将MultiValueMap<String, String>写入到application/x-www-form-urlencode中或将MultiValueMap<String, Object>写入到multipart/form-data中。
Jaxb2RootElementHttpMessageConverter 在XML(text/xml或application/xml)和使用JAXB2注解的对象间互相读取和写入。如果JAXB v2库在类路径下,将进行注册
MappingJacksonHttpMessageConverter 在JSON和类型化的对象或非类型化的HashMap间相互读取和写入,如果Jackson JSON库在类路径下,将进行注册
MappingJackson2HttpMessageConverter 在JSON和类型化的对象或非类型化的HashMap间互相读取和写入,如果Jackson 2 JSON库在类路径下,将进行注册
MarshallingHttpMessageConverter 使用注入的编排器和解排器来读入和写入XML。支持的编排器和解排器保罗Castor,JAXB2,JIBX,XMLBeans以及Xstream。
ResourceHttpMessageConverter 读取或写入Resource
RssChannelHttpMessageConverter 在RSS feed和Rome Channel对象间互相读取和写入。若Rome库在类路径下,将进行注册
SourceHttpMessageConverter 在XML和javax.xml.transform.Source对象间互相读取和写入
StringHttpMessageConverter 将所有媒体类型读取为String,将String写入为text/plain
XmlAwareFormHttpMessageConverter FormHttpMessageConverter的扩展,使用SourceHttp MessageConverter来支持基于XML部分
正常情况下,当处理方法返回Java对象时,这个对象会放在模型中并在视图中渲染使用。但使用了消息转换功能的话,需要告诉Spring跳过正常的模型/视图流程,并使用消息转换器。最简单的方法是在Controller上添加@ResponseBody注解
默认情况下,Jackson JSON库将返回的对象转换为JSON资源表述时,会使用反射。但如果重构Java时,产生的JSON也可能会发生变化。因此我们可以在Java类型行使用Jackson的映射注解,从而改变产生JSON的行为。
当处理请求时,@ResponseBody和@RequestBody是启用消息转换的一种简洁和强大方式。若每个方法都需要信息转换功能,则这些注解会带来一定的重复性。Spring4.0引入了RestController注解,用@RestController代替@Controller,Spring将会为该控制器的所有处理方法应用消息转换功能。
Spring提供了三种方式来处理业务逻辑异常
使用@ResponseStatus注解可以指定状态码
控制器方法可以返回ResponseEntity对象,改对象能够包含更多响应相关的元数据
异常处理器能够应对错误场景,这样处理器方法就能够关注与正常的情况
使用@ResponseEntity作为@ResponseBody的替代方案,控制器方法可以返回一个ResponseEntity对象。ResponseEntity中可以包含响应相关的元数据以及要转换成资源表述的对象。
@RequestMapping(value="/{id}") public ResponseEntity<Order> getOrderById(@PathVariable String id){ Order order = orderRepository.findOne(id); HttpStatus status == order != null? HttpStatus.OK : HttpStatus.NOT_FOUND; return new ResponseEntity<Order>(order, status); }
使用@ExceptionHandler处理异常
public class Error{ private String id; private String message; public Error(String id, String message){ this.id = id; this.message = message; } public void setId(String id){ this.id = id; } public void getId(){ return id; } public void setMessage(String message){ this.message = message; } public String getMessage(){ return message; } } public class ModelNotFoundException extends RuntimeException{ private String id; public ModelNotFoundException(String id){ this.id = id; } public String getId(){ return id; } } // controller @RequestMapping("/getException") public Order getException(String id){ throw new ModelNotFoundException("id"); } @ExceptionHandler(ModelNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public Error modelNotFound(ModelNotFoundException e){ return new Error(4, e.getId + " not found"); }
当创建新资源的时候,将资源的URL放在响应的Location头部信息中,并返回给客户端是一种方式。HttpHeaders用来存放在响应中包含的头部信息值。HttpHeaders是MultiValueMap<String, String>的特殊实现。
public ResponseEntity<Spittle> saveSpittle(@RequestBody Spittle spittle){ Spittle spittle = spittleRepository.save(spittle); HttpHeaders headers = new HttpHeaders(); URI locationUri = URI.create("http://localhost:8080/spittr/spittles/" + spittle.getId()); headers.setLocation(locationUri); ResponseEntity<Spittle> responseEntity = new ResponseEntity<Spittle>(spittle, headers, HttpStatus.CREATED); return responseEntity; }
Spring提供了UriComponentsBuilder,它是一个构建类,通过逐步指定URL中的各个组成部分(host,端口,路径以及查询)。
public ResponseEntity<Spittle> saveSpittle(@RequestBody Spittle spittle, UriComponentsBuilder ucb){ Spittle spittle = spittleRepository.save(spittle); HttpHeaders headers = new HttpHeaders(); URI locationUri = ucb.path("/spittles/").path(String.valueOf(spittle.getId())).build().toUri(); headers.setLocation(locationUri); ResponseEntity<Spittle> responseEntity = new ResponseEntity<Spittle>(spittle, headers, HttpStatus.CREATED); return responseEntity; }
RestTemplate定义了36个与REST资源交互的方法,其中大多数对应与HTTP的方法。除了TRACE外,RestTemplate涵盖了所有Http动作,execute()和exchange()提供了较低层次的通用方法来使用任意的HTTP方法。
大多数操作以三种方法的形式进行了重载:
一个使用java.net.URI作为URL格式,不支持参数化URL
一个使用String作为URL格式,并使用Map指明URL参数
一个使用String作为URL格式,并使用可变参数列表指明URL参数
RestTemplate定义了11个独立的操作,每一个都有重载,一共36个方法
delete() 在特定的URL上对资源执行HTTP DELETE操作
exchange() 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体重映射得到的
execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
getForObject() 发送一个HTTP GET迁就,返回的请求体映射为一个对象
headForHeaders() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头
optionsForAlllow() 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息
postForEntity() POST数据到一个URL,返回包含一个对象的ResponseEntity,这个对象时从响应体中映射得到的
postForLocation() POST一个数据到URL,返回新创建资源的URL
postForObject() POST数据到一个URL,返回根据响应体匹配形成的对象
put() PUT资源到特定的URL
public Profile fetchFacebookProfile(String id){ RestTemplate rest = new RestTemplate(); return new rest.getForObject("http://graph.facebook.com/{spittler}", Profile.class, id); } public Spittle[] fetchFacebookProfile(String id){ Map<String, String> urlVariables = new HashMap<String, String>(); urlVariables.put("id", id); RestTemplate rest = new RestTemplate(); return rest.getForObject("http://graph.facebbook.com/{spitter}", Profile.class, urlVariables); }
getForEntity()会在ResponseEntity中返回相同的对象,而且ResponseEntity会带有额外的信息。
getHeaders()方法返回一个HttpHeaders对象,该对象提供了多个便利的方法:public List<MediaType> getAccept();public List<Charset> getAcceptCharset();public Set<HttpMethod> getAllow();public String getCacheControl();public List<String> getConnection();public long getContentLength();public MediaType getContentType();public long getDate();public String getETag();public long getExpires();public long getIfNotModifiedSince();public List<String> getIfNoneMatche();public long getLastModified();public URI getLocation();public String getOrigin();public String getPragma();public String getUpgrade();
PUT资源
RestTemplate提供了三个简单的put()方法。
void put(URI url, Object request) throws RestClientException;
void put(String url, Object request, Object... uriVariables) throws RestClientException;
void put(String url, Object request, Map<String, ?> uriVariables) throws RestClientException;
public void updateSpittle(Spittle spittle){ RestTemplate rest = new RestTemplate(); String url = "http://localhost:8080/spittles/" + spittle.getId(); rest.put(URI.create(url), spittle); } public void updateSpittle(Spittle spittle){ RestTemplate rest = new RestTemplate(); rest.put("http://localhost:8080/spittles/{id}", spittle, spittle.getId()); } public void updateSpittle(Spittle spittle){ RestTemplate rest = new RestTemplate(); Map<String, String> params = new HashMap<String, String>(); params.put("id", spittle.getId()); rest.put("http://localhost:8080/spittles/{id}", spittle, params); }
在所有版本的put()中,第二个参数都是表示资源的Java对象,它将按照指定的URI发送到服务器端。对象将被转换成什么样的内容很大程度上取决于传递给put()方法的类型。如果给定一个String值,将会用StringHttpMessageConverter,这个值直接被写到请求中,内容类型设置为text/plain。如果给定一个MultiValueMap<String, String>,那么这个Map中的值会被FormHttpMessageConverter以"application/x-www-form-urlencode"的格式写到请求体中。以为传进来的是一个java bean,所以需要一个能够处理任意对象的信息转换器。若类路径下包含Jackson2库,那么MappingJacksonHttpMessageConverter将以application/json格式将java bean写到请求中。
DELETE资源
当不需要在服务端保留某个资源时,可以调用RestTemplate的delete()方法。
void delete(String url, Object... uriVariables) throws RestClientException;
void delete(String url, Map<String, ?> uriVariables) throws RestClientException;
void delete(URI url) throws RestClientException;
public void deleteSpittle(long id){ RestTemplate rest = new RestTemplate(); rest.delete(URI.create("http://localhost:8080/spittles/" + id)); } public void deleteSpittle(long id){ RestTemplate rest = new RestTemplate(); rest.delete("http://localhost:8080/spittles/{id}", id); }
POST资源数据
POST资源到服务端的一种方式是使用RestTemplate的postForObject()方法。第一个参数都是资源要POST的URL,第二个参数似乎要发送的对象,第三个参数是预期返回的Java类型。第四个参数指定了URL变量
<T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;
<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
public Spitter postSpitterForObject(Spitter spitter){ RestTemplate rest = new RestTemplate(); return rest.postForObject("http://localhost:8080/spitters", spitter, Spitter.class); }
postForEntity()
<T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException;
<T> ResponsesEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
RestTemplate rest = new RestTemplate(); ResponseEntity<Spitter> response = rest.postForEntity("http://localhost:8080/spitters", spitter, Spitter.class); Spitter spitter = response.getBody(); URI url = response.getHeaders().getLocation();
在不需要将资源发送回来但需要是Location头信息的值时,可以使用RestTemplate的postForLocation()替代postForEntity()
URI postForLocation(String url, Object request, Oject... uriVariables) throws RestClientException;
URI postForLocation(String url, Object request, Map<String, ?> uriVariables) throws RestClientException;
URI postForLocation(URI url, Object request) throws RestClientException;
public String postSpitter(Spitter spitter){ RestTemplate rest = new RestTemplate(); return rest.postForLocation("http://localhost:8080/spitters", spitter).toString(); }
交换资源
RestTemplate的exchange可以在发送给服务端的请求中设置头信息。
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throwns RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throwns RestClientException;
ResponseEntity<Spitter> response = rest.exchange("http://localhost:8080/spitters/{spitter}", HttpMethod.GET, null, Spitter.class, spitterId); Spitter spitter = response.getBody(); MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>(); headers.add("Accept", "application/json"); HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers); ResponseEntity<Spitter> response = rest.exchange("http://localhost:8080/spitters/{spiiter}", HttpMethod.GET, requestEntity, Spitter.class, spitterId); Spitter spitter = response.getBody();