关于Spring的RestTemplate的使用
现在微服务项目中, 各个服务之间的互相请求调用, 越来越频繁, 所以出现了很多Http请求工具, 而Spring提供的 RestTemplate,则是比较友好的封装集成了一些常见的Http请求工具,可以根据不同的使用场景,灵活的切换
1 RestTemplate的简介
RestTemplate是Spring3.0推出的, 官方给的解释是: 同步客户端执行HTTP请求,暴露出一个简单,模板的基于底层HTTP客户端库的方法API, 像JDK自带的HttpURLConnection, Apache HttpComponents和其他.
从RestTemplate代码可知, 其继承抽象类InterceptingHttpAccessor,而InterceptingHttpAccessor又是继承自抽象类HttpAccessor, 两个抽象父类, 都比较简单. 其中HttpAccessor中主要是定义了客户端请求工厂对象和客户端请求初始化对象(其中RestTemplate想要切换不同的Http客户端请求,就实现不同的客户端请求工厂即可).而InterceptingHttpAccessor中主要定义了客户端请求拦截对象, 而RestTemplate中主要是类型转换对象(其中包含各种常用的如json,gson等), 响应异常处理等. 此外, RestTemplate还实现了RestOperations接口, 该接口定义了很多Http类型的请求操作方法, 主要有以下几类: GET, HEAD , POST, PUT,PATCH, DELETE , OPTIONS , exchange,General execution通用执行.
HTTP方法 | RestTemplate方法 |
---|---|
GET | getForObject getForEntity |
HEAD | headForHeaders |
POST | postForLocation postForObject postForEntity |
PUT | put |
PATCH | patchForObject |
DELETE | delete |
OPTIONS | optionsForAllow |
exchange | exchange |
General execution | execute |
RestTemplate部分源码
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
private static final boolean romePresent;
private static final boolean jaxb2Present;
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
private static final boolean jackson2SmilePresent;
private static final boolean jackson2CborPresent;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
static {
ClassLoader classLoader = RestTemplate.class.getClassLoader();
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
// 构造 通过Http请求工厂创建一个实例对象
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
}
RestTemplate常用配置
@Slf4j
@Configuration
public class RestTemplateConfig {
/**
* 连接池最大连接数
*/
private int maxTotal = 500;
/**
* 同路由并发数
*/
private int defaultMaxPerRoute = 50;
/**
* 重试次数
*/
private int retryCount = 3;
/**
* 连接超时时间/毫秒 超出该时间抛出connect timeout
*/
private int connectTimeout = 5000;
/**
* 数据读取超时时间(socketTimeout)/毫秒 超出该时间抛出 read timeout
*/
private int readTimeout = 5000;
/**
* 请求连接的超时时间/毫秒 从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException:
* Timeout waiting for connection from pool
*/
private int connectionRequestTimeout = 500;
private OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory;
/**
* Http连接管理
*/
@Bean
public HttpClientConnectionManager httpClientConnectionManager() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
30, TimeUnit.SECONDS);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory
.getSocketFactory()).build();
// 默认构造就是 注册http和https请求 也可通过上述方式手动添加
// PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
registry);
poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
return poolingHttpClientConnectionManager;
}
/**
* Http客户端
*/
@Bean
public HttpClient httpClient(HttpClientConnectionManager httpClientConnectionManager) {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
// 请求配置 可以在Http客户端构建的过程中配置, 也可以在请求工厂类中配置
/* RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(readTimeout)
.setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(connectionRequestTimeout)
.build();*/
// 请求头参数
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));
return httpClientBuilder
// 添加请求配置
// .setDefaultRequestConfig(requestConfig)
// 添加Http连接管理
.setConnectionManager(httpClientConnectionManager)
// 添加请求头
.setDefaultHeaders(headers)
// 保持长连接配置,需要在头添加Keep-Alive
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
// 设置重试次数 默认是3次
.setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, true))
.build();
}
/**
* 客户端请求工厂配置
*/
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
// 创建org.apache.http.client请求工厂
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
// 客户端请求配置
clientHttpRequestFactory.setConnectTimeout(connectTimeout);
clientHttpRequestFactory.setReadTimeout(readTimeout);
clientHttpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout);
return clientHttpRequestFactory;
}
@Bean
@Primary // 表示优先使用该注解的Bean
@LoadBalanced // 让RestTemplate具备负载均衡的能力 (下篇讲)
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
// 初始化RestTemplate, 添加默认的类型转换
RestTemplate restTemplate = new RestTemplate();
// 添加请求工厂 此处添加的是org.apache.http.client请求工厂, 如要替换成OkHttpClient请求, 则换成OkHttp3ClientHttpRequestFactory请求工厂
restTemplate.setRequestFactory(clientHttpRequestFactory);
// 重新设置 StringHttpMessageConverter 字符集为UTF-8,解决中文乱码问题
List<HttpMessageConverter<?>> httpMessageConverterList = restTemplate
.getMessageConverters();
HttpMessageConverter<?> httpMessageConverter = null;
for (HttpMessageConverter<?> messageConverter : httpMessageConverterList) {
if (messageConverter instanceof StringHttpMessageConverter) {
httpMessageConverter = messageConverter;
break;
}
}
// 先删除 StringHttpMessageConverter 类型转换
if (null != httpMessageConverter) {
httpMessageConverterList.remove(httpMessageConverter);
}
// 添加 StringHttpMessageConverter 类型转换 设置字符集为UTF-8 (该处默认的是 ISO-8859-1 )
httpMessageConverterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
// 加入FastJson转换器
httpMessageConverterList.add(new FastJsonHttpMessageConverter());
// 添加请求头拦截器 在请求头添加一些信息如 token等 (可选)
List<ClientHttpRequestInterceptor> clientHttpRequestInterceptorList = new ArrayList<>();
clientHttpRequestInterceptorList.add(new MyInterceptor());
restTemplate.setInterceptors(clientHttpRequestInterceptorList);
return restTemplate;
}
}
自定义客户端请求拦截器
public class MyInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// 请求头添加信息 如唯一标识 token等
request.getHeaders().set("token","token值");
return execution.execute(request,body);
}
}
2 RestTemplate的使用
准备两个SpringBoot微服务, consumer-a和provider-a, 然后服务consumer-a通过RestTemplate调用provider-a,查看调用结果.
consumer-a服务
RestTemplate配置文件
@Slf4j
@Configuration
public class RestTemplateConfig {
/**
* 连接池最大连接数
*/
private int maxTotal = 500;
/**
* 同路由并发数
*/
private int defaultMaxPerRoute = 50;
/**
* 重试次数
*/
private int retryCount = 3;
/**
* 连接超时时间/毫秒 超出该时间抛出connect timeout
*/
private int connectTimeout = 5000;
/**
* 数据读取超时时间(socketTimeout)/毫秒 超出该时间抛出 read timeout
*/
private int readTimeout = 5000;
/**
* 请求连接的超时时间/毫秒 从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException:
* Timeout waiting for connection from pool
*/
private int connectionRequestTimeout = 500;
/**
* Http连接管理
*/
@Bean
public HttpClientConnectionManager httpClientConnectionManager() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory
.getSocketFactory()).build();
// 默认构造就是 注册http和https请求 也可通过上述方式手动添加
// PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
registry);
poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
return poolingHttpClientConnectionManager;
}
/**
* Http客户端
*/
@Bean
public HttpClient httpClient(HttpClientConnectionManager httpClientConnectionManager) {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
// 请求配置 可以在Http客户端构建的过程中配置, 也可以在请求工厂类中配置
/* RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(readTimeout)
.setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(connectionRequestTimeout)
.build();*/
// 请求头参数
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));
return httpClientBuilder
// 添加请求配置
// .setDefaultRequestConfig(requestConfig)
// 添加Http连接管理
.setConnectionManager(httpClientConnectionManager)
// 添加请求头
.setDefaultHeaders(headers)
// 保持长连接配置,需要在头添加Keep-Alive
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
// 设置重试次数 默认是3次
.setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, true))
.build();
}
/**
* 客户端请求工厂配置
*/
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
// 创建org.apache.http.client请求工厂
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
// 客户端请求配置
clientHttpRequestFactory.setConnectTimeout(connectTimeout);
clientHttpRequestFactory.setReadTimeout(readTimeout);
clientHttpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout);
return clientHttpRequestFactory;
}
/*
// okhttpclient 构建
@Bean
public OkHttpClient okHttpClient(){
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newBuilder()
.connectTimeout(connectTimeout,TimeUnit.MILLISECONDS)
.readTimeout(readTimeout,TimeUnit.MILLISECONDS)
.build();
return okHttpClient;
}
*//**
* 客户端请求工厂配置
*//*
@Bean
public ClientHttpRequestFactory requestFactory(OkHttpClient okHttpClient) {
System.out.println("使用okhttpclient");
// 创建OkHttpClient请求工厂
OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient);
// 客户端请求配置
clientHttpRequestFactory.setConnectTimeout(connectTimeout);
clientHttpRequestFactory.setReadTimeout(readTimeout);
clientHttpRequestFactory.setWriteTimeout(connectionRequestTimeout);
return clientHttpRequestFactory;
}
*/
@Bean
@Primary // 表示优先使用该注解的Bean
@LoadBalanced // 让RestTemplate具备负载均衡的能力 (下篇讲)
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
// 初始化RestTemplate, 添加默认的类型转换
RestTemplate restTemplate = new RestTemplate();
// 添加请求工厂 此处添加的是org.apache.http.client请求工厂, 如要替换成OkHttpClient请求, 则换成OkHttp3ClientHttpRequestFactory请求工厂
restTemplate.setRequestFactory(clientHttpRequestFactory);
// 重新设置 StringHttpMessageConverter 字符集为UTF-8,解决中文乱码问题
List<HttpMessageConverter<?>> httpMessageConverterList = restTemplate
.getMessageConverters();
HttpMessageConverter<?> httpMessageConverter = null;
for (HttpMessageConverter<?> messageConverter : httpMessageConverterList) {
if (messageConverter instanceof StringHttpMessageConverter) {
httpMessageConverter = messageConverter;
break;
}
}
// 先删除 StringHttpMessageConverter 类型转换
if (null != httpMessageConverter) {
httpMessageConverterList.remove(httpMessageConverter);
}
// 添加 StringHttpMessageConverter 类型转换 设置字符集为UTF-8 (该处默认的是 ISO-8859-1 )
httpMessageConverterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
// 加入FastJson转换器
httpMessageConverterList.add(new FastJsonHttpMessageConverter());
// 添加请求头拦截器 在请求头添加一些信息如 token等 (可选,不次不用,故不添加)
// List<ClientHttpRequestInterceptor> clientHttpRequestInterceptorList = new ArrayList<>();
// clientHttpRequestInterceptorList.add(new MyInterceptor());
// restTemplate.setInterceptors(clientHttpRequestInterceptorList);
return restTemplate;
}
}
Controller控制器
@RestController
@Slf4j
@Api(value = "AConController", tags = "消费者控制器")
public class AConController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer")
public String list() {
String info = "我是consumerA,8081 ";
log.info(info);
// 在RestTemplate使用@LoadBalanced注解后, 可简化为且必须要使用服务名,如provider-a (使用ip和端口 访问不到)
// 没有添加该注解时, 需要使用ip和端口, 如 127.0.0.1:9091 (使用服务名, 访问不到)
// String url = "http://127.0.0.1:9091/provider";
String url = "http://provider-a/provider";
String result = restTemplate.getForObject(url, String.class);
return JSON.toJSONString(info + result);
}
}
application.yml配置文件
server:
port: 8081
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos 服务端地址
application:
name: consumer-a # 服务名称
provider-a服务
Controller控制器
@RestController
@Slf4j
@Api(value = "AProController", tags = "提供者控制器")
public class AProController {
@GetMapping("/provider")
public String list() {
String info = "我是 providerA,9091 ";
log.info(info);
return JSON.toJSONString(info);
}
}
application.yml配置文件
server:
port: 9091
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos 服务端地址
application:
name: provider-a # 服务名称
测试功能
1 启动本地nacos服务
2 依次启动provider-a服务, consumer-a服务
3 访问 http://localhost:8081/consumer 响应结果
"我是consumerA,8081 \"我是 providerA,9091 \""
4 构建RestTemplate时去掉@LoadBalanced , RestTemplate使用时, 使用ip和端口, 响应结果
"我是consumerA,8081 \"我是 providerA,9091 \""
通过以上测试,发现RestTemplate在各个服务之间的调用也非常方便, 而且可以根据不同的业务场景,来切换不同的客户端请求对象.
使用过程中遇到的问题
java.lang.IllegalStateException: No instances available for localhost
最初RestTemplate调用过程中,出现了java.lang.IllegalStateException: No instances available for localhost
报错异常, 查询资料后得知, 使用了@LoadBalanced, 就不能使用ip和端口的样式去调用服务,而是使用服务名称来调用.