本文为博主自学笔记整理,内容来源于互联网,如有侵权,请联系删除。
01 | 构建一个 RESTful 风格的 Web 服务
理解 RESTful 架构风格
REST(Representational State Transfer,表述性状态转移)这种架构风格把位于服务器端的访问入口看作一个资源,在传输协议上使用标准的 HTTP 方法,比如最常见的 GET、PUT、POST 和 DELETE。
下表展示了 RESTful 风格的一些具体示例:
对于 RESTful 风格设计 HTTP 端点,业界也存在一些约定。以 Account 这个领域实体为例,如果我们把它视为一种资源,那么 HTTP 端点的根节点命名上通常采用复数形式,即“/accounts”。
在设计 RESTful API 时,针对常见的 CRUD 操作,下图展示了 RESTful API 与非 RESTful API 的一些区别。
使用基础注解
- @RestController
@RestController 注解继承自 Spring MVC 中的 @Controller 注解,是一个基于 RESTful 风格的 HTTP 端点,并且会自动使用 JSON 实现 HTTP 请求和响应的序列化/反序列化方式。
- @GetMapping
@GetMapping 的注解的定义与 @RequestMapping 非常类似,只是默认使用了 RequestMethod.GET 指定 HTTP 方法。
控制请求的输入
- @PathVariable
@PathVariable 注解用于获取路径参数。即从类似 url/{id} 这种形式的路径中获取 {id} 参数的值。
- @RequestParam
@RequestParam 注解也是用于获取请求中的参数,但是它面向类似 url?id=XXX 这种路径形式。
- @RequestMapping
在 HTTP 协议中,content-type 属性用来指定所传输的内容类型,我们可以通过 @RequestMapping 注解中的 produces 属性来设置这个属性。
- @RequestBody
@RequestBody 注解用来处理 content-type 为 application/json 类型的编码内容,通过 @RequestBody 注解可以将请求体中的 JSON 字符串绑定到相应的 JavaBean 上。
02 | 如何使用 RestTemplate 消费 RESTful 服务?
创建 RestTemplate
如果我们想创建一个 RestTemplate 对象,最简单且最常见的方法是直接 new 一个该类的实例,如下代码所示:
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
复制代码
我们查看下 RestTemplate 的无参构造函数,如下代码所示:
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
this.messageConverters.add(new SourceHttpMessageConverter<>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
//省略其他添加 HttpMessageConverter 的代码
}
复制代码
RestTemplate 的无参构造函数添加了一批用于实现消息转换的 HttpMessageConverter 对象。
RestTemplate 还有另外一个更强大的有参构造函数,如下代码所示:
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
复制代码
可以用来设置 HTTP 请求的超时时间等属性,如下代码所示:
@Bean
public RestTemplate customRestTemplate(){
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
// 连接请求超时时间
httpRequestFactory.setConnectionRequestTimeout(3000);
// 连接超时时间
httpRequestFactory.setConnectTimeout(3000);
httpRequestFactory.setReadTimeout(3000);
return new RestTemplate(httpRequestFactory);
}
复制代码
使用 RestTemplate 访问 Web 服务
- GET
public <T> T getForObject(URI url, Class<T> responseType)
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
复制代码
也可以使用 getForEntity 方法返回一个 ResponseEntity 对象。
- POST
Order order = new Order();
order.setOrderNumber("Order0001");
order.setDeliveryAddress("DemoAddress");
ResponseEntity<Order> responseEntity = restTemplate.postForEntity("http://localhost:8082/orders", order, Order.class);
return responseEntity.getBody();
复制代码
postForObject 的操作方式也与此类似。
- exchange
ResponseEntity<Order> result = restTemplate.exchange("http://localhost:8082/orders/{orderNumber}", HttpMethod.GET, null, Order.class, orderNumber);
复制代码
//设置 HTTP Header
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//设置访问参数
HashMap<String, Object> params = new HashMap<>();
params.put("orderNumber", orderNumber);
//设置请求 Entity
HttpEntity entity = new HttpEntity<>(params, headers);
ResponseEntity<Order> result = restTemplate.exchange(url, HttpMethod.GET, entity, Order.class);
复制代码
RestTemplate 其他使用技巧
- 指定消息转换器
假如,我们希望把支持 Gson 的 GsonHttpMessageConverter 加载到 RestTemplate 中,就可以使用如下所示的代码。
@Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new GsonHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate(messageConverters);
return restTemplate;
}
复制代码
- 设置拦截器
这方面最典型的应用场景是在 Spring Cloud 中通过 @LoadBalanced 注解为 RestTemplate 添加负载均衡机制。我们可以在 LoadBalanceAutoConfiguration 自动配置类中找到如下代码。
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
复制代码
- 处理异常
请求状态码不是返回 200 时,RestTemplate 在默认情况下会抛出异常,并中断接下来的操作,如果我们不想采用这个处理过程,那么就需要覆盖默认的 ResponseErrorHandler。示例代码结构如下所示:
RestTemplate restTemplate = new RestTemplate();
ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
return true;
}
@Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
//添加定制化的异常处理代码
}
};
restTemplate.setErrorHandler(responseErrorHandler);
复制代码
03 | RestTemplate 远程调用的实现原理
RestTemplate 的类层结构,如下图所示:
整个类层结构清晰地分成两条支线,左边支线用于完成与 HTTP 请求相关的实现机制,而右边支线提供了基于 RESTful 风格的操作入口,并使用了面向对象中的接口和抽象类完成这两部分功能的聚合。
InterceptingHttpAccessor
它是一个抽象类,包含的核心变量如下代码所示:
public abstract class InterceptingHttpAccessor extends HttpAccessor {
private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
private volatile ClientHttpRequestFactory interceptingRequestFactory;
…
}
复制代码
interceptors 负责设置和管理请求拦截器;interceptingRequestFactory 负责创建客户端 HTTP 请求。
InterceptingHttpAccessor 同样存在一个父类 HttpAccessor:
public abstract class HttpAccessor {
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
…
}
复制代码
HttpAccessor 中创建了 SimpleClientHttpRequestFactory 作为系统默认的 ClientHttpRequestFactory。
RestOperations
RestOperations 接口的定义,如下代码所示:
public interface RestOperations {
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables) throws RestClientException;
void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException;
void delete(String url, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
Class<T> responseType, Object... uriVariables) throws RestClientException;
…
}
复制代码
RestTemplate 核心执行流程
我们可以从具备多种请求方式的 exchange 方法入手,该方法的定义如下代码所示:
@Override
public <T> ResponseEntity<T> exchange(String url,
HttpMethod method,
@Nullable HttpEntity<?> requestEntity, Class<T> responseType,
Object... uriVariables)
throws RestClientException {
//构建请求回调
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
//构建响应体抽取器
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
//执行远程调用
return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}
复制代码
execute 方法定义如下代码所示:
@Override
@Nullable
public <T> T execute(String url,
HttpMethod method,
@Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor,
Object... uriVariables) throws RestClientException {
URI expanded = getUriTemplateHandler().expand(url, uriVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
复制代码
doExecute 方法定义如下代码所示:
protected <T> T doExecute(URI url,
@Nullable HttpMethod method,
@Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor)
throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
// 1. 创建请求对象
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
//执行对请求的回调
requestCallback.doWithRequest(request);
}
// 2. 获取调用结果
response = request.execute();
// 3. 处理调用结果
handleResponse(url, method, response);
//使用结果提取从结果中提取数据
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
} catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on "
+ method.name() +
" request for \"" + resource + "\": "
+ ex.getMessage(), ex);
} finally {
if (response != null) {
response.close();
}
}
}
复制代码