简介
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为例如 Apache HttpComponents、Netty或OkHttp等其它HTTP框架。
本篇介绍如何使用RestTemplate,以及在SpringBoot
实现逻辑
RestTemplate包含以下几个部分:
- HttpMessageConverter 对象转换器
- ClientHttpRequestFactory 默认是JDK的HttpURLConnection
- ResponseErrorHandler 异常处理
- ClientHttpRequestInterceptor 请求拦截器
用一张图可以很直观的理解:
直接使用方式很简单:
public class RestTemplateTest {
public static void main(String[] args) {
RestTemplate restT = new RestTemplate();
//通过Jackson JSON processing library直接将返回值绑定到对象
Quote quote = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
String quoteString = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random", String.class);
System.out.println(quoteString);
}
}
发送GET请求
// 1-getForObject()
User user1 = this.restTemplate.getForObject(uri, User.class);
// 2-getForEntity()
ResponseEntity<User> responseEntity1 = this.restTemplate.getForEntity(uri, User.class);
HttpStatus statusCode = responseEntity1.getStatusCode();
HttpHeaders header = responseEntity1.getHeaders();
User user2 = responseEntity1.getBody();
// 3-exchange()
RequestEntity requestEntity = RequestEntity.get(new URI(uri)).build();
ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
User user3 = responseEntity2.getBody();
发送POST请求
// 1-postForObject()
User user1 = this.restTemplate.postForObject(uri, user, User.class);
// 2-postForEntity()
ResponseEntity<User> responseEntity1 = this.restTemplate.postForEntity(uri, user, User.class);
// 3-exchange()
RequestEntity<User> requestEntity = RequestEntity.post(new URI(uri)).body(user);
ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
设置HTTP Header
// 1-Content-Type
RequestEntity<User> requestEntity = RequestEntity
.post(new URI(uri))
.contentType(MediaType.APPLICATION_JSON)
.body(user);
// 2-Accept
RequestEntity<User> requestEntity = RequestEntity
.post(new URI(uri))
.accept(MediaType.APPLICATION_JSON)
.body(user);
// 3-Other
RequestEntity<User> requestEntity = RequestEntity
.post(new URI(uri))
.header("Authorization", "Basic " + base64Credentials)
.body(user);
捕获异常
捕获HttpServerErrorException
try {
responseEntity = restTemplate.exchange(requestEntity, String.class);
} catch (HttpServerErrorException e) {
// log error
}
自定义异常处理器
public class CustomErrorHandler extends DefaultResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// todo
}
}
然后设置下异常处理器
@Configuration
public class RestClientConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new CustomErrorHandler());
return restTemplate;
}
}
配置类
创建RestClientConfig类,设置连接池大小、超时时间、重试机制等。配置如下:
@Configuration
public class RestClientConfig {
private static final Logger log = LoggerFactory.getLogger(RestClientConfig.class);
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(clientHttpRequestFactory());
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
// 使用UTF-8编码集的conver替换默认的conver(默认的stringConver的编码集为"ISO-8859-1"),解决响应体乱码问题
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
while (iterator.hasNext()) {
HttpMessageConverter<?> converter = iterator.next();
if (converter instanceof StringHttpMessageConverter) {
iterator.remove();
}
}
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
//解决微信返回text/plain的解析
//messageConverters.add(new WxMappingJackson2HttpMessageConverter());
return restTemplate;
}
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
return true;
}
}).build();
httpClientBuilder.setSSLContext(sslContext);
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();// 注册http和https请求
//使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
poolingHttpClientConnectionManager.setMaxTotal(100); // 最大连接数500
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); // 同路由并发数100
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重试次数
//设置默认请求头
//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"));
//httpClientBuilder.setDefaultHeaders(headers);
HttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient连接配置
clientHttpRequestFactory.setConnectTimeout(20000); // 连接超时
clientHttpRequestFactory.setReadTimeout(10000); // 数据读取超时时间,即SocketTimeout
clientHttpRequestFactory.setConnectionRequestTimeout(200); // 连接不够用的等待时间,不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的
return clientHttpRequestFactory;
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
log.error("初始化HTTP连接池出错", e);
}
return null;
}
}
注意,如果没有apache的HttpClient类,需要在项目构造工具配置文件中添加:
- pom.xml
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
- build.gradle
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3'
发送文件
MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();
multiPartBody.add("file", new ClassPathResource("/tmp/user.txt"));
RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity
.post(uri)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(multiPartBody);
下载文件
// 小文件
RequestEntity requestEntity = RequestEntity.get(uri).build();
ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);
byte[] downloadContent = responseEntity.getBody();
// 大文件
ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor<ResponseEntity<File>>() {
@Override
public ResponseEntity<File> extractData(ClientHttpResponse response) throws IOException {
File rcvFile = File.createTempFile("rcvFile", "zip");
FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));
return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);
}
};
File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor);
Service注入
@Service
public class DeviceService {
private static final Logger logger = LoggerFactory.getLogger(DeviceService.class);
@Autowired
private RestTemplate restTemplate;
}
实际使用例子
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationTests {
private static final Logger logger = LoggerFactory.getLogger(ApplicationTests.class);
/** @LocalServerPort 提供了 @Value("${local.server.port}") 的代替*/
@LocalServerPort
private int port;
@Autowired
private RestTemplate restTemplate;
@Test
public void testRestTemplate() {
logger.info("解绑成功后推送消息给对应的POS机");
LoginParam param = new LoginParam();
param.setUsername("admin");
param.setPassword("12345678");
String loginUrl = String.format("http://localhost:%d/login", port);
BaseResponse r = restTemplate.postForObject(loginUrl, param, BaseResponse.class);
assertThat(r.isSuccess(), is(true));
logger.info("推送消息登录认证成功");
String token = (String) r.getData();
UnbindParam unbindParam = new UnbindParam();
unbindParam.setImei("imei");
unbindParam.setLocation("location");
// 设置HTTP Header信息
String unbindUrl = String.format("http://localhost:%d/unbind", port);
URI uri;
try {
uri = new URI(unbindUrl);
} catch (URISyntaxException e) {
logger.error("URI构建失败", e);
throw new RuntimeException("URI构建失败");
}
RequestEntity<UnbindParam> requestEntity = RequestEntity
.post(uri)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", token)
.body(unbindParam);
ResponseEntity<BaseResponse> responseEntity = restTemplate.exchange(requestEntity, BaseResponse.class);
BaseResponse r2 = responseEntity.getBody();
assertThat(r2.isSuccess(), is(true));
assertThat(r2.getData(), is("unbind"));
}
}