SpringBoot26 RestTemplateWebClient

Posted 寻渝记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot26 RestTemplateWebClient相关的知识,希望对你有一定的参考价值。

 

1 RestTemplate

  RestTemplate是在客户端访问 Restful 服务的一个核心类;RestTemplate通过提供回调方法和允许配置信息转换器来实现个性化定制RestTemplate的功能,通过RestTemplate可以封装请求对象,也可以对响应对象进行解析。

  技巧01:RestTemplate默认使用JDK提供的包去建立HTTP连接,当然,开发者也可以使用诸如 Apache HttpComponents, Netty, and OkHttp 去建立HTTP连接。

  技巧02:RestTemplate内部默认使用HttpMessageConverter来实现HTTTP messages 和 POJO 之间的转换,可以通过RestTemplate的成员方法 setMessageConverters(java.util.List<org.springframework.http.converter.HttpMessageConverter<?>>). 去修改默认的转换器。

  技巧03:RestTemplate内部默认使用SimpleClientHttpRequestFactory and DefaultResponseErrorHandler 去创建HTTP连接和处理HTTP错误,可以通过HttpAccessor.setRequestFactory(org.springframework.http.client.ClientHttpRequestFactory) and setErrorHandler(org.springframework.web.client.ResponseErrorHandler)去做相应的修改。.

  1.1 RestTemplate中方法概览

    RestTemplate为每种HTTP请求都实现了相关的请求封装方法

    技巧01:这些方法的命名是有讲究的,方法名的第一部分表示HTTP请求类型,方法名的第二部分表示响应类型

      例如:getForObject 表示执行GET请求并将响应转化成一个Object类型的对象  

    技巧02:利用RestTemplate封装客户端发送HTTP请求时,如果出现异常就会抛出 RestClientException 类型的异常;可以通过在创建RestTemplate对象的时候指定一个ResponseErrorHandler类型的异常处理类来处理这个异常

    技巧02:exchange 和 excute 这两个方法是通用的HTTP请求方法,而且这两个方法还支持额外的HTTP请求类型【PS: 前提是使用的HTTP连接包也支持这些额外的HTTP请求类型】

     技巧03:每种方法都有3个重载方法,其中两个接收String类型的请求路径和响应类型、参数;另外一个接收URI类型的请求路径和响应类型。

    技巧04:使用String类型的请求路径时,RestTemplate会自动进行一次编码,所以为了避免重复编码问题最好使用URI类型的请求路径

      例如:restTemplate.getForObject("http://example.com/hotel list") becomes"http://example.com/hotel%20list"

    技巧05:URI 和URL 知识点扫盲

      参考博文01  参考博文02

    技巧06:利用接收URI参数的RestTemplate.getForObject方法发送Get请求

   1.2 常用构造器

    技巧01:利用无参构造器创建RestTemplate实例时,什么都是使用默认的【即:使用HttpMessageConverter来实现HTTTP messages 和 POJO 之间的转换、使用SimpleClientHttpRequestFactory and DefaultResponseErrorHandler 去创建HTTP连接和处理HTTP错误

    技巧02:利用 RestTemplate(ClientHttpRequestFactory requestFactory) 创建RestTemplate实例时使用自定义的requestFactory去创建HTTP连接

    技巧03:利用 RestTemplate(java.util.List<HttpMessageConverter<?>> messageConverters)  创建RestTemplate实例时使用自定义的转换器列表实现HTTTP messages 和 POJO 之间的转换

  1.3 GET相关方法

    技巧01:本博文使用的是SpringBoot项目,利用了一个配置文件来将RestTemplate注入的容器中

package cn.xiangxu.test_demo.common.config;

import cn.xiangxu.test_demo.domain.domain_do.Student;
import cn.xiangxu.test_demo.utils.proxy.JdkProxy;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 王杨帅
 * @create 2018-08-18 13:07
 * @desc 创建Bean的配置类
 **/
@Configuration
public class BeanConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }


}
BeanConfig.java

    模拟后台代码:点击获取

package cn.xiangxu.rest_server.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author 王杨帅
 * @create 2018-08-18 21:56
 * @desc 模拟get请求的服务端
 **/
@RestController
@RequestMapping(value = "/get")
@Slf4j
public class GetServerConroller {

    /**
     * 不带参数的get请求
     * @return
     */
    @GetMapping(value = "/get01")
    public String get01() {
        String result = "【get01】连接模拟的get服务端成功";
        log.info(result);
        return result;
    }

    /**
     * 带有请求参数的get请求
     * 笔记:
     *  1 get请求参数类型:
     *      》 url中?后面的请求参数,格式以 key=value 的形式传递;后台需要用@RequestParam注解
     *          如果前端的 key 和 后台方法的参数名称一致时可以不用@RequestParam注解【因为@RequestParam注解时默认的参数注解】
     *      》 url中的路径参数
     *          需要配合@RequestMapping和@PathVariable一起使用
     * @param username 请求参数
     * @return
     */
    @GetMapping(value = "/get02")
    public String get02(  @RequestParam(value = "name", required = false, defaultValue = "王杨帅") String username) {
        String result = "【get02】获取到的请求参数为:name = " + username;
        log.info(result);
        return result;
    }

    /**
     * 带有路径参数的get请求
     * @param userId
     * @return
     */
    @GetMapping(value = "/get03/{id}")
    public String get03(
            @PathVariable(value = "id") Integer userId
    ) {
        String result = "【get03】获取到的路径参数为:userId = " + userId;
        log.info(result);
        return result;
    }

    /**
     * 既有路径参数又有请求参数的get请求
     * 笔记:
     *  1 @PathVariable和@RequestParam都可以设定是否必传【默认必传】
     *  2 @PathVariable不可以设定默认值,@RequestParam可以设定默认值【默认值就是不传入的时候代替的值】
     *  3 @PathVariable如果设置必传为true,前端不传入时就会报错【技巧:开启必传】
     *  4 @RequestParam如果设置必传为true,前端不传入还是也会报错【技巧:关闭必传,开启默认值】
     *  5 @PathVariable可以设置正则表达式【详情参见:https://www.cnblogs.com/NeverCtrl-C/p/8185576.html】
     * @param userId
     * @param username
     * @return
     */
    @GetMapping(value = "/get04/{id}")
    public String get04(
            @PathVariable(value = "id") Integer userId,
            @RequestParam(value = "name", required = false, defaultValue = "王杨帅") String username
    ) {
        String result = "【get04】获取到的路径参数为:userId = " + userId + " 获取到的请求参数为:" + username;
        log.info(result);
        return result;
    }

}
远程Restful服务

    模拟前端请求:点击获取

package cn.xiangxu.test_demo.controller;

import cn.xiangxu.test_demo.TestDemoApplicationTests;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class GetClientControllerTest extends TestDemoApplicationTests {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testDemo() throws Exception {
        System.out.println("Hello Boy");


    }


    /**
     * 请求无参数的get请求
     * @return
     */
    @Test
    public void get01() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get01",
                String.class
        );
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求有请求参数的get请求:利用占位符进行请求参数传递
     * @return
     */
    @Test
    public void get0201() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get02?name={1}",
                String.class,
                "三少"
        );
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求有请求参数的get请求:利用Map进行请求参数传递
     * @return
     */
    @Test
    public void get0202() throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("name", "warrior");
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get02?name={name}",
                String.class,
                params
        );
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求有路径参数的get请求:
     * @return
     */
    @Test
    public void get03() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get03/88888888",
                String.class);
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求既有路径参数又有请求参数逇get请求
     * @return
     */
    @Test
    public void get04() throws Exception {
        String forObject = restTemplate.getForObject(
                "http://127.0.0.1:8080/get/get04/99999?name={1}",
                String.class,
                "fury"
        );
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求无参数的get请求
     * @return
     */
    @Test
    public void get01_e() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get01",
                String.class
        );
        log.info("状态码:" + forObject.getStatusCode());
        log.info("状态值:" + forObject.getStatusCodeValue());
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求有请求参数的get请求:利用占位符进行请求参数传递
     * @return
     */
    @Test
    public void get0201_e() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get02?name={1}",
                String.class,
                "三少"
        );
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求有请求参数的get请求:利用Map进行请求参数传递
     * @return
     */
    @Test
    public void get0202_e() throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("name", "warrior");
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get02?name={name}",
                String.class,
                (Map<String, ?>) params);
        log.info("响应数据为:" + forObject);
    }

    /**
     * 请求有路径参数的get请求:
     * @return
     */
    @Test
    public void get03_e() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get03/888888",
                String.class
        );
        log.info("响应数据为:" + forObject);
    }

    @Test
    public void get04_e() throws Exception {
    }

    /**
     * 请求既有路径参数又有请求参数逇get请求
     * @return
     */
    @Test
    public void test01() throws Exception {
        ResponseEntity<String> forObject = restTemplate.getForEntity(
                "http://127.0.0.1:8080/get/get04/99999?name={1}",
                String.class,
                "fury"
        );
        log.info("响应数据为:" + forObject);
    }

}
模拟客户端

    1.3.1 public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)

      1》远程服务代码【不带请求参数的】

      1》模拟客户端代码【不带请求参数的】

      2》远程服务代码【带请求参数的】

        技巧01:HTTP请求中url路径?后面的参数就是请求参数格式以 key=value 的形式传递;后台需要用@RequestParam注解,如果前端的 key 和 后台方法的参数名称一致时可以不用@RequestParam注解【因为@RequestParam注解时默认的参数注解】

        技巧02:对于请求参数,最好在服务端利用@RequestParam注解设置该请求参数为非必传参数并设定默认值

      2》模拟客户端代码【带请求参数的】

 

      3》远程服务代码【带路径参数的】

        技巧01:HTTP请求的路径可以成为路径参数,前提是服务端进行路径配置【即:需要配合@RequestMapping和@PathVariable一起使用】

        技巧02:由于路径参数不能设置默认是,所以在后台通过@PathVariable将路径参数设置成必传可以减少出错率

        技巧03:@PathVariable可以设置正则表达式【详情参见:https://www.cnblogs.com/NeverCtrl-C/p/8185576.html

      3》模拟客户端代码【带路径参数的】

      4》远程服务代码【带路径参数和请求参数的】

        技巧01: @PathVariable和@RequestParam都可以设定是否必传【默认必传】
        技巧02:@PathVariable不可以设定默认值,@RequestParam可以设定默认值【默认值就是不传入的时候代替的值】
        技巧03: @PathVariable如果设置必传为true,前端不传入时就会报错【技巧:开启必传】
        技巧04:@RequestParam如果设置必传为true,前端不传入还是也会报错【技巧:关闭必传,开启默认值】
        技巧05:@PathVariable可以设置正则表达式【详情参见:https://www.cnblogs.com/NeverCtrl-C/p/8185576.html】

      4》模拟客户端代码【带路径参数和请求参数的】

 

     1.3.2 public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

       1》远程服务代码【带请求参数的】

       1》模拟客户端代码【带请求参数的】

 

    1.3.3 public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)

       技巧01:getForObject 和 getForEntity 的区别:后者可以获取到更多的响应信息,前者这可以获取到响应体的数据

      1》远程服务代码【不带请求参数的】

      1》模拟客户端代码【不带请求参数的】

      2》远程服务代码【带请求参数的】

      2》模拟客户端代码【带请求参数的】

      3》远程服务代码【带路径参数的】

      3》模拟客户端代码【带路径参数的】

      4》远程服务代码【带路径参数和请求参数的】

      4》模拟客户端代码【带路径参数和请求参数的】

     1.3.4 public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)

      1》远程服务代码【带请求参数的】

      1》模拟客户端代码【带请求参数的】

  1.4 POST

    服务端源代码:点击前往

package cn.xiangxu.rest_server.controller;

import cn.xiangxu.rest_server.domain.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author 王杨帅
 * @create 2018-08-19 9:41
 * @desc 模拟post服务端接口
 **/
@RestController
@RequestMapping(value = "/post")
@Slf4j
public class PostServerController {

    /**
     * 模拟只有请求体参数的post请求
     * @param student 请求体参数
     * @return
     */
    @PostMapping(value = "/post01")
    public Student post01(
            @RequestBody Student student
    ) {
        log.info("【post01】获取到的请求体参数为:" + student);
        student.setName("服务端修改");
        return student;

    }

    /**
     * 模拟既有请求体参数又有请求参数的post请求
     * @param student 请求体参数
     * @param infoMsg 请求参数
     * @return
     */
    @PostMapping(value = "/post02")
    public String post02(
            @RequestBody Student student,
            @RequestParam(value = "info", required = false, defaultValue = "默认请求参数") String infoMsg
    ) {
        String result = "【post02】-方法体参数为:student -> " + student + " 请求参数为:infoMsg -> " + infoMsg;
        log.info(result);
        return result;
    }

    /**
     * 模拟既有请求体参数又有请求参数和路径参数的post请求
     * @param student 请求体参数
     * @param infoMsg 请求参数
     * @param userId 路径参数
     * @return
     */
    @PostMapping(value = "/post03/{id}")
    public String post03(
            @RequestBody Student student,
            @RequestParam(value = "info", required = false, defaultValue = "默认请求参数") String infoMsg,
            @PathVariable(value = "id", required = false) Integer userId
    ) {

        String result = "【psot03】-获取到的请求体参数为:student -> " + student + "\\n" +
                "请求参数为:infoMsg -> " + infoMsg + "\\n" +
                "路径参数为:userId -> " + userId;

        log.info(result);

        return result;

    }

    @GetMapping(value = "/connect")
    public String connect() {
        String result = "【post】-前后端连接测试成功";
        log.info(result);
        return result;
    }

}
View Code

    模拟客户端代码:点击前往

package cn.xiangxu.test_demo.controller;

import cn.xiangxu.test_demo.TestDemoApplicationTests;
import cn.xiangxu.test_demo.domain.domain_do.Student;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

@Component
@Slf4j
public class PostClientControllerTest extends TestDemoApplicationTests {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void connect() throws Exception {
    }

    /**
     * 模拟只有请求体参数的post请求
     * @return
     */
    @Test
    public void post01() throws Exception {
        Student student = Student.builder()
                .name("warrior")
                .age(24)
                .build();

        Student postForObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post01",
                student,
                Student.class
        );

        System.out.println(postForObject);
    }

    /**
     * 模拟既有请求体参数又有请求参数的POST请求【方式01】
     * @return
     */
    @Test
    public void post0201() throws Exception {
        Student student = Student.builder()
                .name("王杨帅")
                .address("渝足")
                .build();
        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post02?info={1}",
                student,
                String.class,
                "模拟前端"
        );
        log.info("响应数据㘝:" + forObject);
    }

    /**
     * 模拟既有请求体参数又有请求参数的POST请求【方式02】
     * @return
     */
    @Test
    public void post0202() throws Exception {
        Student student = Student.builder()
                .name("warrior")
                .address("大足")
                .build();

        Map<String, Object> param = new HashMap<>();
        param.put("info", "模拟前端");

        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post02?info={info}",
                student,
                String.class,
                param
        );

        log.info("响应数据㘝:" + forObject);
    }

    /**
     * 模拟既有请求体参数又有请求参数和路径参数的POST请求
     * @return
     */
    @Test
    public void post03() throws Exception {
        Student student = Student.builder()
                .name("fury")
                .address("合川")
                .build();

        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post03/8888?info={1}",
                student,
                String.class,
                "模拟请求参数"

        );

        log.info("响应数据为:" + forObject);
    }

        /*
    下面是利用另外一个方法实现get请求:
        笔记:
            1 getForObject 和 getForEntity 的区别:后者可以获取到更多的响应信息,前者这可以获取到响应体的数据
     */

    /**
     * 模拟只有请求体参数的post请求
     * @return
     */
    @Test
    public void post01_e() throws Exception {
        Student student = Student.builder()
                .name("王杨帅")
                .address("渝足")
                .build();

        ResponseEntity<Student> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post01",
                student,
                Student.class
        );

        log.info("响应数据为:" + forObject.getBody());
    }

    /**
     * 模拟既有请求体参数又有请求参数的POST请求【方式01】
     * @return
     */
    @Test
    public void post0201_e() throws Exception {
        Student student = Student.builder()
                .name("王杨帅")
                .address("渝足")
                .build();
        ResponseEntity<String> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post02?info={1}",
                student,
                String.class,
                "模拟前端"
        );

        log.info("响应数据为:" + forObject.getBody());
    }

    /**
     * 模拟既有请求体参数又有请求参数的POST请求【方式02】
     * @return
     */
    @Test
    public void post0202_e() throws Exception {
        Student student = Student.builder()
                .name("warrior")
                .address("大足")
                .build();

        Map<String, Object> param = new HashMap<>();
        param.put("info", "模拟前端");

        ResponseEntity<String> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post02?info={info}",
                student,
                String.class,
                param
        );

        log.info("响应数据为:" + forObject.getBody());
    }

    /**
     * 模拟既有请求体参数又有请求参数和路径参数的POST请求
     * @return
     */
    @Test
    public void post03_e() throws Exception {
        Student student = Student.builder()
                .name("fury")
                .address("合川")
                .build();

        ResponseEntity<String> forObject = restTemplate.postForEntity(
                "http://127.0.0.1:8080/post/post03/8888?info={1}",
                student,
                String.class,
                "模拟请求参数"

        );

        log.info("响应数据为:" + forObject.getBody());
    }

    /**
     * 请求体封装【利用HttpEntity可以自定义请求体和请求头】
     */
    @Test
    public void test01() throws Exception {
        Student student = Student.builder()
                .name("bob")
                .address("成都")
                .build();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

        HttpEntity<Student> studentHttpEntity = new HttpEntity<>(student, headers);

        String forObject = restTemplate.postForObject(
                "http://127.0.0.1:8080/post/post02?info={1}",
                studentHttpEntity,
                String.class,
                "模拟前端"
        );

        log.info(forObject);

    }

}
View Code

    1.4.1 public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)

      参数解释:

        url -> String类型的请求路径

        request -> 请求体对象

        responseType -> 响应数据类型

        uriVariables -> 请求参数

    1.4.2 public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)

      参数解释:

        url -> String类型的请求路径

        request -> 请求体对象

        responseType -> 响应数据类型

        uriVariables -> 请求参数

    1.4.3 public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)

      参数解释:

        url -> URI类型的请求路径

        request -> 请求体对象

        responseType -> 响应数据类型

    1.4.4 请求体对象说明

      技巧01:请求体对象(@Nullable Object request)可以直接传一个实体,服务端利用@RequestBody接收这个实体即可

      技巧02:请求体对象(@Nullable Object request)也可以传入一个  HttpEntity 的实例,服务端的代码不变;创建 HttpEntity 实例时可以设定请求体数据和请求头数据(详情请参见 HttpEntity 的相关构造函数)

  1.5 其他请求和GET、POST类似

    待更新......

 

2 WebClient

  WebClient 是一个非阻塞、响应式的HTTP客户端,它以响应式被压流的方式执行HTTP请求;WebClient默认使用 Reactor Netty 作为HTTP连接器,当然也可以通过ClientHttpConnector修改其它的HTTP连接器。

  技巧01:使用WebClient需要进入Spring5的相关依赖,如果使用的是SpringBoot项目的话直接引入下面的依赖就可以啦

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.reactive.function.client;

import java.net.URI;
SpringBoot 使用 RestTemplate 调用exchange方法 显示错误信息

SpringBoot系列之RestTemplate使用示例

SpringBoot使用RestTemplate

重学springboot系列番外篇之RestTemplate

springboot2.0集成RestTemplate

springboot使用restTemplate post提交值 restTemplate post值