使用RestTemplate进行restful调用,你真的会了吗

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 使用RestTemplate进行restful调用,你真的会了吗

RestTemplate是一种更优雅的调用RESTful服务的方式,并且能结合Ribbon一起使用。


概述


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";
        //1、使用getForObject请求接口,  顺序传入参数
        String result1 = template.getForObject(url, String.class, "laowan");
        System.out.println("result1====================" + result1);
        //2、使用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);
        //3、使用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());
        //4、使用exchange请求接口
        ResponseEntity<String> response2 = template.exchange(url, HttpMethod.GET, null, String.class,paramMap);
        System.out.println("result4====================" + response2.getBody());
        //5、使用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());
    }

注意事项


1.get请求的参数传递,一定要在url后面拼接,可以使用?号后面拼接,也可以采用路径参数。

2.传递参数有2中方法,一种是传入多个参数值,会按顺序填充到url后面的参数占位符中,一种是采用map传入多个参数,这时一定要使用HashMap

3.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() );
        }
    }

注意事项


1.post请求传递普通参数,一定要使用LinkedMultiValueMap对参数进行封装,不然会接收不到参数值。

2.post请求可以传递json格式参数,需要在头部参数中指定headers.setContentType(MediaType.APPLICATION_JSON);

3.返回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 RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(okHttp3ClientHttpRequestFactory());
        //restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }
    @Bean
    public OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory(){
        return new OkHttp3ClientHttpRequestFactory(okHttpClient());
    }
    @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。


总结


1、介绍了RestTemplate的常规调用方法和注意事项

2、RestTemplate结合Ribbon的负载均衡调用

3、RestTemplate底层连接的设置

4、RestTemplate的转换器设置

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
API 对象存储 网络架构
OSS restful API 调用 put,上传文件,python发http request示例
发送put 请求,向bucket中写入文件,代码中*** 的部分改成实际内容。rest请求主要问题在拼header时authorization可能会有问题,注意生成signature时的入参。#tested env: python version v3.9.6 #author: Fred #2022-1-11 import hmac import hashlib import base64 im
1161 0
|
自然语言处理 人机交互 API
阿里云智能语音交互中长文本语音合成服务的restful api 中python3调用
智能语音交互产品基于语音识别、语音合成、自然语言理解等技术,实现“能听、会说、懂你”式的智能人机交互体验,适用于智能客服、质检、会议纪要、实时字幕等多个企业应用场景,长文本语音合成功能提供了将超长文本(如千字或者万字)合成为语音二进制数据的功能,此篇文章简单介绍基于python的简单调用
1059 0
阿里云智能语音交互中长文本语音合成服务的restful api 中python3调用
|
API 对象存储 网络架构
OSS restful API 调用 Delete,删除文件,python发http request示例
发送delete 请求删除bucket中的文件,代码中*** 的部分改成实际内容。rest请求主要问题在拼header时authorization可能会有问题。#tested env: python version v3.9.6 #author: Fred #2022-1-11 import hmac import hashlib import base64 import datetime im
432 0
|
API 对象存储 网络架构
OSS restful API 调用 get,遍历目录中的文件,python发http request示例
发送get 请求,遍历目录下的所有文件,代码中*** 的部分改成实际内容,这个API说明文档在bucket操作里面。rest请求主要问题在拼header时authorization可能会有问题,注意计算签名时的入参。#tested env: python version v3.9.6 #author: Fred #2022-1-11 import hmac import hashlib impo
638 0
|
Java 网络架构 Spring
Spring之路(27)–使用RestTemplate访问Restful接口
本文目录 1. 背景 2. 编写测试类
194 0
|
Web App开发 Java 测试技术
如何使用 JMeter 调用你的 Restful Web Service?进行简单的压力测试和自动化测试
如何使用 JMeter 调用你的 Restful Web Service?进行简单的压力测试和自动化测试 表述性状态传输(REST)作为对基于 SOAP 和 Web 服务描述语言(WSDL)的 Web 服务的简单替代,在 Web 开发上得到了广泛的接受。
2173 0
|
前端开发 Java API
Java调用Restful API接口的几种方式–HTTPS
最近有一个需求,为客户提供一些Restful API接口,QA使用postman进行测试,但是postman的测试接口与java调用的相似但并不相同,于是想自己写一个程序去测试Restful API接口,由于使用的是HTTPS,所以还要考虑到对于HTTPS的处理。
12374 0
|
JSON 前端开发 数据格式