微信公众号:跟着老万学java
欢迎关注,了解更多编程技巧,一块儿交流,一块儿成长。html
RestTemplate是一种更优雅的调用RESTful服务的方式,而且能结合Ribbon一块儿使用。那么,你真的会使用它吗?java
一、各类请求的传参有什么注意事项?web
二、怎么传递json格式参数spring
三、怎么修改底层的http链接方式,怎么添加线程池配置apache
四、怎么结合ribbon实现负载均衡调用编程
五、怎么自定义转换器json
若是这些你都不了解,那么这篇文章多是你须要的。
api
概述
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通讯方式,统一了RESTful的标准,封装了http连接, 咱们只须要传入url及返回值类型便可。相较于以前经常使用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。微信
在Spring应用程序中访问第三方REST服务与使用Spring RestTemplate类有关。RestTemplate类的设计原则与许多其余Spring *模板类(例如JdbcTemplate、JmsTemplate)相同,为执行复杂任务提供了一种具备默认行为的简化方法。并发
RestTemplate默认依赖JDK提供http链接的能力(HttpURLConnection),若是有须要的话也能够经过setRequestFactory方法替换为例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。
考虑到RestTemplate类是为调用REST服务而设计的,所以它的主要方法与REST的基础紧密相连就不足为奇了,后者是HTTP协议的方法:HEAD、GET、POST、PUT、DELETE和OPTIONS。例如,RestTemplate类具备headForHeaders()、getForObject()、postForObject()、put()和delete()等方法。
实现
最新api地址:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html
RestTemplate包含如下几个部分:
HttpMessageConverter 对象转换器
ClientHttpRequestFactory 默认是JDK的HttpURLConnection
ResponseErrorHandler 异常处理
ClientHttpRequestInterceptor 请求拦截器
get请求
请求接口
@GetMapping("/users")
public String getUser1(String username, HttpServletRequest request){
return "获取用户"+username+"的信息";
}
@GetMapping("/users/{username}")
public String getUser2(@PathVariable String username){
return "获取用户"+username+"的信息";
}
get请求
@Test
public void getRequestTest(){
RestTemplate template = new RestTemplate();
String url = "http://127.0.0.1:8080/users?username={username}";
String url2 = "http://127.0.0.1:8080/users/laowan";
//一、使用getForObject请求接口, 顺序传入参数
String result1 = template.getForObject(url, String.class, "laowan");
System.out.println("result1====================" + result1);
//二、使用getForObject请求接口 使用HashMap传入参数
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("username", "laowan");
String result2 = template.getForObject(url, String.class, paramMap);
System.out.println("result2====================" + result1);
//三、使用url路径变量PathVariable
String result3 = template.getForObject(url2, String.class);
System.out.println("result3====================" + result1);
ResponseEntity<String> responseEntity = template.getForEntity(url2, String.class);
System.out.println("getForEntity请求====================" + responseEntity.getBody());
//四、使用exchange请求接口
ResponseEntity<String> response2 = template.exchange(url, HttpMethod.GET, null, String.class,paramMap);
System.out.println("result4====================" + response2.getBody());
//五、使用exchange请求接口,能够封装HttpEntity对象传递header参数
HttpHeaders headers = new HttpHeaders();
headers.set("username", "laowan");
HttpEntity httpEntity = new HttpEntity(null,headers);
ResponseEntity<String> response5 = template.exchange(url, HttpMethod.GET, httpEntity, String.class,paramMap);
System.out.println("result5====================" + response5.getHeaders());
}
注意事项
get请求的参数传递,必定要在url后面拼接,可使用?号后面拼接,也能够采用路径参数。
传递参数有2中方法,一种是传入多个参数值,会按顺序填充到url后面的参数占位符中,一种是采用map传入多个参数,这时必定要使用HashMap
get请求若是须要传递header参数,必定要采用exchange方法,封装HttpEntity对象
post请求
请求接口
@PostMapping("/user")
public User addUser(String username,Integer age){
User user = new User();
user.setAge(age);
user.setUsername(username);
log.info("新增用户{}", JSON.toJSONString(user));
return user;
}
@PostMapping("/user/add")
public User addUser(User user){
log.info("新增用户{}", JSON.toJSONString(user));
return user;
}
@PostMapping("/user/json")
public User addUserJson(@RequestBody User user){
log.info("新增用户{}", JSON.toJSONString(user));
return user;
}
post请求
@Test
public void postRequestTest() {
String url = "http://127.0.0.1:8080/user";
String url2 = "http://127.0.0.1:8080/user/add";
RestTemplate template = new RestTemplate();
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("username", "laowan");
paramMap.add("age", 22);
User user = template.postForObject(url, paramMap, User.class);
log.info("result1的结果为:{}",user);
user = template.postForObject(url2, paramMap, User.class);
log.info("result2的结果为:{}",user);
}
//postForEntity方式的json请求
@Test
public void postRequestTest4() {
String url2 = "http://127.0.0.1:8080/user/json";
RestTemplate restTemplate = new RestTemplate();
String reqJsonStr = "{\"username\":\"laowan\", \"age\":\"12\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
ResponseEntity<User> resp = restTemplate.postForEntity(url2,entity, User.class);
log.info("result1的结果为:{}",resp.getBody());
}
//经过exchange调用post请求,传递json格式参数
@Test
public void postRequestTest2() {
String url2 = "http://127.0.0.1:8080/user/json";
RestTemplate restTemplate = new RestTemplate();
String reqJsonStr = "{\"username\":\"laowan\", \"age\":\"12\"}";
HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
ResponseEntity<User> resp = restTemplate.exchange(url2, HttpMethod.POST, entity, User.class);
log.info("result1的结果为:{}",resp.getBody());
}
//经过exchange调用post请求,传递x-www-form-urlencoded格式参数
@Test
public void postRequestTest3() {
String url2 = "http://127.0.0.1:8080/user/add";
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("username", "laowan");
paramMap.add("age", 22);
HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<MultiValueMap<String, Object>>(paramMap,headers);
ResponseEntity<User> resp = restTemplate.exchange(url2, HttpMethod.POST, entity, User.class);
if( resp.getStatusCode().equals( HttpStatus.OK )){
log.info("result1的结果为:{}",resp.getBody());
}else{
throw new RuntimeException( resp.toString() );
}
}
注意事项
post请求传递普通参数,必定要使用LinkedMultiValueMap对参数进行封装,否则会接收不到参数值。
post请求能够传递json格式参数,须要在头部参数中指定headers.setContentType(MediaType.APPLICATION_JSON);
返回ResponseEntity结果,主要是能够从中获取返回的状态码和请求头信息。能够方便对请求结果进行断定,对请求异常进行处理。
if( resp.getStatusCode().equals( HttpStatus.OK )){
log.info("result1的结果为:{}",resp.getBody());
}else{
throw new RuntimeException( resp.toString() );
}
结合ribbon使用
RestTemplate负载均衡示例
核心是经过@LoadBalanced注解声明RestTemplate实例,主要逻辑是给RestTemplate增长拦截器,在请求以前对请求的地址进行替换,或者根据具体的负载均衡策略选择服务地址,而后再去调用,这就是@LoadBalanced的原理。
引入jar包依赖
也能够不引用,eureka中已经引用了ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
将服务rest注册到Eureka
spring.application.name=rest
使用@LoadBalanced注解声明RestTemplate实例
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
调用接口
修改调用的url,将IP+PORT改成服务名称,也就是注册到Eureka的名称。
@SpringBootTest
class RestApplicationTests {
@Autowired
RestTemplate restTemplate;
@Test
void restRibbonTest() {
String result3 = restTemplate.getForObject("http://rest/users/laowan", String.class);
}
}
设置底层链接方式
RestTemplate默认依赖JDK提供http链接的能力(HttpURLConnection),若是有须要的话也能够经过setRequestFactory方法替换为例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。
实现原理是经过RestTemplate(ClientHttpRequestFactory requestFactory)的构造方法,指定requestFactory。
常规配置
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder){
return restTemplateBuilder
.basicAuthentication("username", "password")
.setConnectTimeout(Duration.ofSeconds(3000))
.setReadTimeout(Duration.ofSeconds(5000))
.rootUri("http://api.example.com/")
.build();
// return new RestTemplate();
}
改用httpclient的实现
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4.1</version>
</dependency>
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// restTemplate.setRequestFactory(okHttpClient());
restTemplate.setRequestFactory(clientHttpRequestFactory());
//restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
return true;
}
}).build();
httpClientBuilder.setSslcontext(sslContext);
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
// 注册http和https请求
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();
// 开始设置链接池
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
poolingHttpClientConnectionManager.setMaxTotal(500); // 最大链接数500
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); // 同路由并发数100
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重试次数
HttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient链接配置
clientHttpRequestFactory.setConnectTimeout(20000); // 链接超时
clientHttpRequestFactory.setReadTimeout(30000); // 数据读取超时时间
clientHttpRequestFactory.setConnectionRequestTimeout(20000); // 链接不够用的等待时间
return clientHttpRequestFactory;
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
// log.error("初始化HTTP链接池出错", e);
}
return null;
}
}
改用okhttp的实现
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
@Configuration
public class OkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory(), x509TrustManager())
.retryOnConnectionFailure(false)
.connectionPool(pool())
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30,TimeUnit.SECONDS)
.build();
}
@Bean
public X509TrustManager x509TrustManager() {
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
}
@Bean
public SSLSocketFactory sslSocketFactory() {
try {
//信任任何连接
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return null;
}
@Bean
public ConnectionPool pool() {
return new ConnectionPool(200, 5, TimeUnit.MINUTES);
}
}
能够发现,在使用httpclient和okhttp均可以配置链接池connectionPool,相信可以在必定程度上提升http请求的速度。
手动指定转换器
调用reseful接口传递的数据内容是json格式的字符串,返回的响应也是json格式的字符串。然而restTemplate.postForObject方法的请求参数RequestBean和返回参数ResponseBean却都是java类。
是RestTemplate经过HttpMessageConverter自动帮咱们作了转换的操做。
默认状况下RestTemplate自动帮咱们注册了一组HttpMessageConverter用来处理一些不一样的contentType的请求。
如StringHttpMessageConverter来处理text/plain;
MappingJackson2HttpMessageConverter来处理application/json;
MappingJackson2XmlHttpMessageConverter来处理application/xml。
RestTemplate的无参构造方法中的转化器配置部分源码:
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
} else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
} else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
} else if (jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}
你能够在org.springframework.http.converter包下找到全部spring帮咱们实现好的转换器。
若是现有的转换器不能知足你的需求,你还能够实现org.springframework.http.converter.HttpMessageConverter接口本身写一个。详情参考官方api。
选好了HttpMessageConverter后怎么把它注册到咱们的RestTemplate中呢。
RestTemplate restTemplate = new RestTemplate();
//获取RestTemplate默认配置好的全部转换器
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
//默认的MappingJackson2HttpMessageConverter在第7个 先把它移除掉
messageConverters.remove(6);
//添加上GSON的转换器
messageConverters.add(6, new GsonHttpMessageConverter());
这个简单的例子展现了如何使用GsonHttpMessageConverter替换掉默认用来处理application/json的MappingJackson2HttpMessageConverter。
总结
一、RestTemplate的常规调用方法和注意事项
二、RestTemplate结合Ribbon的负载均衡调用
三、RestTemplate底层链接的设置
四、RestTemplate的转换器设置
参考:
https://blog.csdn.net/u014692324/article/details/98876094
https://blog.csdn.net/LDY1016/article/details/80002126
更多精彩,关注我吧。

本文分享自微信公众号 - 跟着老万学java(douzhe_2019)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。