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

微信公众号:跟着老万学java
欢迎关注,了解更多编程技巧,一起交流,一起成长。

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 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的转换器设置

如果觉得有用,记得帮忙点赞哦。
在这里插入图片描述

参考:
https://blog.csdn.net/u014692324/article/details/98876094
https://blog.csdn.net/LDY1016/article/details/80002126

更多精彩,关注我吧。
图注:跟着老万学java

原创文章 83 获赞 117 访问量 98万+

猜你喜欢

转载自blog.csdn.net/w1014074794/article/details/105889199