Spring 中 RestTemplate使用详解

Spring 中 RestTemplate使用详解

 

 

1、简介

 

  RestTemplate是一个执行HTTP请求的同步阻塞式工具类,它仅仅只是在 HTTP 客户端库(例如 JDK HttpURLConnectionApache HttpComponentsokHttp 等)基础上,封装了更加简单易用的模板方法 API,方便程序员利用已提供的模板方法发起网络请求和处理,能很大程度上提升我们的开发效率。RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute

 

2、SpringBoot 环境下使用 RestTemplate

2.1、maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

同时,将RestTemplate配置初始化为一个Bean

@Configuration
public class RestTemplateConfig {
    /**
     * 没有实例化RestTemplate时,初始化RestTemplate
     */
    @ConditionalOnMissingBean(RestTemplate.class)
    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }
}

注意,这种初始化方法,是使用了JDK自带的 HttpURLConnection 作为底层HTTP客户端实现。当然,我们还可以修改RestTemplate默认的客户端,例如将其改成HttpClient客户端,方式如下:

@Configuration
public class RestTemplateConfig {

    @ConditionalOnMissingBean(RestTemplate.class)
    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        return restTemplate;
    }

    /**
     * 使用HttpClient作为底层客户端
     */
    private ClientHttpRequestFactory getClientHttpRequestFactory() {
        int timeout = 5000;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout)
                .build();
        CloseableHttpClient client = HttpClientBuilder
                .create()
                .setDefaultRequestConfig(config)
                .build();
        return new HttpComponentsClientHttpRequestFactory(client);
    }

}

在需要使用RestTemplate的位置,注入并使用即可!

@Autowired
private RestTemplate restTemplate;

 网上的各种HTTP客户端性能以及易用程度评测来看,OkHttp 优于 Apache的HttpClientApache的HttpClient优于HttpURLConnection

因此,我们还可以通过如下方式,将底层的http客户端换成OkHttp

/**
 * 使用OkHttpClient作为底层客户端
 * @return
 */
private ClientHttpRequestFactory getClientHttpRequestFactory(){
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .writeTimeout(5, TimeUnit.SECONDS)
            .readTimeout(5, TimeUnit.SECONDS)
            .build();
    return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}

 

3、API 实践

RestTemplate最大的特色就是对各种网络请求方式做了包装,能极大的简化开发人员的工作量,下面我们以GET、POST、PUT、DELETE、文件上传与下载为例,分别介绍各个API的使用方式。

3.1 GET请求

通过RestTemplate发送HTTP GET协议请求,经常使用到的方法有两个:

  • getForObject():返回值是HTTP协议的响应体
  • getForEntity():返回的是ResponseEntityResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息

Spring Boot环境下写一个单元测试用例,首先创建一个Api接口,然后编写单元测试进行服务测试。

3.1.1 不带参请求

接口提供方:

@RestController
public class TestController {

    /**
     * 不带参的get请求
     * @return
     */
    @RequestMapping(value = "testGet", method = RequestMethod.GET)
    public ResponseBean testGet(){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("请求成功,方法:testGet");
        return result;
    }
}
public class ResponseBean {
    private String code;
    private String msg;
    省去getset方法 
}

调用方:

@Autowired
private RestTemplate restTemplate;
/**
 * 单元测试(不带参的get请求)
 */
@Test
public void testGet(){
    //请求地址
    String url = "http://localhost:8080/testGet";

    //发起请求,直接返回对象
    ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class);
    System.out.println(responseBean.toString());
}

 

3.1.2 带参的get请求(使用占位符号传参)

接口提供方:

@RestController
public class TestController {
    /**
     * 带参的get请求(restful风格)
     * @return
     */
    @RequestMapping(value = "testGetByRestFul/{id}/{name}", method = RequestMethod.GET)
    public ResponseBean testGetByRestFul(@PathVariable(value = "id") String id, @PathVariable(value = "name") String name){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("请求成功,方法:testGetByRestFul,请求参数id:" +  id + "请求参数name:" + name);
        return result;
    }
}

 

接口调用方:

@Autowired
private RestTemplate restTemplate;
 /**
 * 单元测试(带参的get请求)
 */
@Test
public void testGetByRestFul(){
    //请求地址
    String url = "http://localhost:8080/testGetByRestFul/{1}/{2}";

    //发起请求,直接返回对象(restful风格)
    ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, "001", "张三");
    System.out.println(responseBean.toString());
}

 

3.1.3 带参的get请求(restful风格)

接口提供方:

@RestController
public class TestController {
    /**
     * 带参的get请求(使用占位符号传参)
     * @return
     */
    @RequestMapping(value = "testGetByParam", method = RequestMethod.GET)
    public ResponseBean testGetByParam(@RequestParam("userName") String userName,  @RequestParam("userPwd") String userPwd){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("请求成功,方法:testGetByParam,请求参数userName:" +  userName + ",userPwd:" + userPwd);
        return result;
    }
}

 

接口调用方:

@Autowired
private RestTemplate restTemplate;

 /**
 * 单元测试(带参的get请求)
 */
@Test
public void testGetByParam(){
    //请求地址
    String url = "http://localhost:8080/testGetByParam?userName={userName}&userPwd={userPwd}";

    //请求参数
    Map<String, String> uriVariables = new HashMap<>();
    uriVariables.put("userName", "唐三藏");
    uriVariables.put("userPwd", "123456");

    //发起请求,直接返回对象(带参数请求)
    ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, uriVariables);
    System.out.println(responseBean.toString());
}

 

3.1.4 getForEntity使用示例

上面的所有的getForObject请求传参方法,getForEntity都可以使用,使用方法上也几乎是一致的,只是在返回结果接收的时候略有差别。使用ResponseEntity<T> responseEntity来接收响应结果。用responseEntity.getBody()获取响应体。

 /**
 * 单元测试
 */
@Test
public void testAllGet(){
    //请求地址
    String url = "http://localhost:8080/testGet";

    //发起请求,返回全部信息
    ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url, ResponseBean.class);

    // 获取响应体
    System.out.println("HTTP 响应body:" + response.getBody().toString());

    // 以下是getForEntity 比 getForObject多出来的内容
    HttpStatus statusCode = response.getStatusCode();
    int statusCodeValue = response.getStatusCodeValue();
    HttpHeaders headers = response.getHeaders();

    System.out.println("HTTP 响应状态:" + statusCode);
    System.out.println("HTTP 响应状态码:" + statusCodeValue);
    System.out.println("HTTP Headers信息:" + headers);
}

 

3.2、 POST请求

其实POST请求方法和GET请求方法上大同小异,RestTemplatePOST请求也包含两个主要方法:

  • postForObject():返回body对象
  • postForEntity():返回全部的信息

3.2.1 模拟表单请求

接口提供方:

@RestController
public class TestController {

    /**
     * 模拟表单请求,post方法测试
     * @return
     */
    @RequestMapping(value = "testPostByForm", method = RequestMethod.POST)
    public ResponseBean testPostByForm(@RequestParam("userName") String userName,
                                        @RequestParam("userPwd") String userPwd){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("请求成功,方法:testPostByForm,请求参数userName:" + userName + ",userPwd:" + userPwd);
        return result;
    }
}

接口调用方:

@Autowired
private RestTemplate restTemplate;

/**
 * 模拟表单提交,post请求
 */
@Test
public void testPostByForm(){
    //请求地址
    String url = "http://localhost:8080/testPostByForm";

    // 请求头设置,x-www-form-urlencoded格式的数据
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    //提交参数设置
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("userName", "唐三藏");
    map.add("userPwd", "123456");

    // 组装请求体
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

    //发起请求
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

 

3.2.2 模拟表单请求(传递对象)

接口提供方:

@RestController
public class TestController {
    /**
     * 模拟表单请求,post方法测试
     * @param request
     * @return
     */
    @RequestMapping(value = "testPostByFormAndObj", method = RequestMethod.POST)
    public ResponseBean testPostByForm(RequestBean request){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("请求成功,方法:testPostByFormAndObj,请求参数:" + JSON.toJSONString(request));
        return result;
    }
}

public class RequestBean {
   private String userName;
   private String userPwd;
   省去getset方法
}

接口调用方:

@Autowired
private RestTemplate restTemplate;

/**
 * 模拟表单提交,post请求
 */
@Test
public void testPostByForm(){
    //请求地址
    String url = "http://localhost:8080/testPostByFormAndObj";
    // 请求头设置,x-www-form-urlencoded格式的数据
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    //提交参数设置
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("userName", "唐三藏");
    map.add("userPwd", "123456");

    // 组装请求体
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

    //发起请求
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

 

3.2.3 、模拟JSON请求

接口提供方:

@RestController
public class TestController {
    /**
     * 模拟JSON请求,post方法测试
     * @param request
     * @return
     */
    @RequestMapping(value = "testPostByJson", method = RequestMethod.POST)
    public ResponseBean testPostByJson(@RequestBody RequestBean request){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("请求成功,方法:testPostByJson,请求参数:" + JSON.toJSONString(request));
        return result;
    }
}

 

接口调用方:

@Autowired
private RestTemplate restTemplate;
/**
 * 模拟JSON提交,post请求
 */
@Test
public void testPostByJson(){
    //请求地址
    String url = "http://localhost:8080/testPostByJson";

    //入参
    RequestBean request = new RequestBean();
    request.setUserName("唐三藏");
    request.setUserPwd("123456789");

    //发送post请求,并打印结果,以String类型接收响应结果JSON字符串
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

 

3.2.4 模拟页面重定向

模拟页面重定向,post请求
服务提供方:
@Controller
public class LoginController {
    /**
     * 重定向
     * @param request
     * @return
     */
    @RequestMapping(value = "testPostByLocation", method = RequestMethod.POST)
    public String testPostByLocation(@RequestBody RequestBean request){
        return "redirect:index.html";
    }
}

服务调用方:

@Autowired
private RestTemplate restTemplate;
/**
 * 重定向,post请求
 */
@Test
public void testPostByLocation(){
    //请求地址
    String url = "http://localhost:8080/testPostByLocation";
    //入参
    RequestBean request = new RequestBean();
    request.setUserName("唐三藏");
    request.setUserPwd("123456789");

    //用于提交完成数据之后的页面跳转,返回跳转url
    URI uri = restTemplate.postForLocation(url, request);
    System.out.println(uri.toString());
}

输出结果如下:
http://localhost:8080/index.html

 

3.3、 PUT请求

put请求方法,可能很多人都没用过,它指的是修改一个已经存在的资源或者插入资源,该方法会向URL代表的资源发送一个HTTP PUT方法请求,示例如下:

@RestController
public class TestController {
    /**
     * 模拟JSON请求,put方法测试
     * @param request
     * @return
     */
    @RequestMapping(value = "testPutByJson", method = RequestMethod.PUT)
    public void testPutByJson(@RequestBody RequestBean request){
        System.out.println("请求成功,方法:testPutByJson,请求参数:" + JSON.toJSONString(request));
    }
}

调用方:

@Autowired
private RestTemplate restTemplate;
/**
 * 模拟JSON提交,put请求
 */
@Test
public void testPutByJson(){
    //请求地址
    String url = "http://localhost:8080/testPutByJson";
    //入参
    RequestBean request = new RequestBean();
    request.setUserName("唐三藏");
    request.setUserPwd("123456789");

    //模拟JSON提交,put请求
    restTemplate.put(url, request);
}

 

3.4 、DELETE请求

与之对应的还有delete方法协议,表示删除一个已经存在的资源,该方法会向URL代表的资源发送一个HTTP DELETE方法请求。

@RestController
public class TestController {
    /**
     * 模拟JSON请求,delete方法测试
     * @return
     */
    @RequestMapping(value = "testDeleteByJson", method = RequestMethod.DELETE)
    public void testDeleteByJson(){
        System.out.println("请求成功,方法:testDeleteByJson");
    }
}

调用方:

@Autowired
private RestTemplate restTemplate;
/**
 * 模拟JSON提交,delete请求
 */
@Test
public void testDeleteByJson(){
    //请求地址
    String url = "http://localhost:8080/testDeleteByJson";

    //模拟JSON提交,delete请求
    restTemplate.delete(url);
}

 

3.5 通用请求方法exchange方法

如果以上方法还不满足你的要求。在RestTemplate工具类里面,还有一个exchange通用协议请求方法,它可以发送GET、POST、DELETE、PUT、OPTIONS、PATCH等等HTTP方法请求。

有时候,我们需要在请求中的 Head 中添加值或者将某些值通过 cookie 传给服务端,那么上面这种调用形式就不太满足要求了。

 

UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("127.0.0.1:8080").
                path("/test").build(true);
 URI uri = uriComponents.toUri();

RequestEntity<JSONObject> requestEntity = RequestEntity.post(uri).
                // 添加 cookie(这边有个问题,假如我们要设置 cookie 的生命周期,作用域等参数我们要怎么操作)
                header(HttpHeaders.COOKIE,"key1=value1").
                // 添加 header
                header(("MyRequestHeader", "MyValue")
                accept(MediaType.APPLICATION_JSON).
                contentType(MediaType.APPLICATION_JSON).
                body(requestParam);
ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(requestEntity,JSONObject.class);
// 响应结果
JSONObject responseEntityBody = responseEntity.getBody();

 

3.6、文件上传与下载

除了经常用到的getpost请求以外,还有一个经常会碰到的场景,那就是文件的上传与下载,如果采用RestTemplate,该怎么使用呢?

3.6.1 、文件上传

提供方:
@RestController
public class FileUploadController {

    private static final String UPLOAD_PATH = "/springboot-frame-example/springboot-example-resttemplate/";

    /**
     * 文件上传
     * @param uploadFile
     * @return
     */
    @RequestMapping(value = "upload", method = RequestMethod.POST)
    public ResponseBean upload(@RequestParam("uploadFile") MultipartFile uploadFile,
                               @RequestParam("userName") String userName) {
        // 在 uploadPath 文件夹中通过用户名对上传的文件归类保存
        File folder = new File(UPLOAD_PATH + userName);
        if (!folder.isDirectory()) {
            folder.mkdirs();
        }

        // 对上传的文件重命名,避免文件重名
        String oldName = uploadFile.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));

        //定义返回视图
        ResponseBean result = new ResponseBean();
        try {
            // 文件保存
            uploadFile.transferTo(new File(folder, newName));
            result.setCode("200");
            result.setMsg("文件上传成功,方法:upload,文件名:" + newName);
        } catch (IOException e) {
            e.printStackTrace();
            result.setCode("500");
            result.setMsg("文件上传失败,方法:upload,请求文件:" + oldName);
        }
        return result;
    }
}

调用方:

@Autowired
private RestTemplate restTemplate;

/**
 * 文件上传,post请求
 */
@Test
public void upload(){
    //需要上传的文件
    String filePath = "/Users/panzhi/Desktop/Jietu20220205-194655.jpg";

    //请求地址
    String url = "http://localhost:8080/upload";

    // 请求头设置,multipart/form-data格式的数据
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);

    //提交参数设置
    MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
    param.add("uploadFile", new FileSystemResource(new File(filePath)));
    //服务端如果接受额外参数,可以传递
    param.add("userName", "张三");

    // 组装请求体
    HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(param, headers);

    //发起请求
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

但是我们发现上面的上传代码中,上传文件的类必须使用 FileSystemResource。有时我们会碰到这种情况:文件我们会从文件服务下载到内存中一个 InputStream 的形式存在,那此时在使用 FileSystemResource 就不行了。

// 自己实现了一个 Resource
public class InMemoryResource extends ByteArrayResource {
    private final String filename;
    private final long lastModified;

    public InMemoryResource(String filename, String description, byte[] content, long lastModified) {
        super(content, description);
        this.lastModified = lastModified;
        this.filename = filename;
    }

    @Override
    public long lastModified() throws IOException {
        return this.lastModified;
    }

    @Override
    public String getFilename() {
        return this.filename;
    }
}

调整后的:

// 设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        // 设置请求体,注意是 LinkedMultiValueMap
        // 下面两个流从文件服务下载,这边省略(注意最后关闭流)
        InputStream fis1 =
        InMemoryResource resource = new InMemoryResource("file1.jpg","description1", FileCopyUtils.copyToByteArray(fis1),
System.currentTimeMillis());
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>(); form.add("file", resource); form.add("param1","value1"); HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers); JSONObject s = restTemplate.postForObject(url, files, JSONObject.class);

 

3.6.2、 文件下载

提供方:
@RestController
public class FileUploadController {

    private static final String UPLOAD_PATH = "springboot-frame-example/springboot-example-resttemplate/";

    /**
     * 带参的get请求(restful风格)
     * @return
     */
    @RequestMapping(value = "downloadFile/{userName}/{fileName}", method = RequestMethod.GET)
    public void downloadFile(@PathVariable(value = "userName") String userName,
                             @PathVariable(value = "fileName") String fileName,
                             HttpServletRequest request,
                             HttpServletResponse response) throws Exception {

        File file = new File(UPLOAD_PATH + userName + File.separator + fileName);
        if (file.exists()) {
            //获取文件流
            FileInputStream fis = new FileInputStream(file);
            //获取文件后缀(.png)
            String extendFileName = fileName.substring(fileName.lastIndexOf('.'));
            //动态设置响应类型,根据前台传递文件类型设置响应类型
            response.setContentType(request.getSession().getServletContext().getMimeType(extendFileName));
            //设置响应头,attachment表示以附件的形式下载,inline表示在线打开
            response.setHeader("content-disposition","attachment;fileName=" + URLEncoder.encode(fileName,"UTF-8"));
            //获取输出流对象(用于写文件)
            OutputStream os = response.getOutputStream();
            //下载文件,使用spring框架中的FileCopyUtils工具
            FileCopyUtils.copy(fis,os);
        }
    }
}

 

调用方:

@Autowired
private RestTemplate restTemplate;

/**
 * 小文件下载
 * @throws IOException
 */
@Test
public void downloadFile() throws IOException {
    String userName = "张三";
    String fileName = "c98b677c-0948-46ef-84d2-3742a2b821b0.jpg";
    //请求地址
    String url = "http://localhost:8080/downloadFile/{1}/{2}";

    //发起请求,直接返回对象(restful风格)
    ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class, userName, fileName);
    System.out.println("文件下载请求结果状态码:" + rsp.getStatusCode());

    // 将下载下来的文件内容保存到本地
    String targetPath = "/Users/panzhi/Desktop/"  + fileName;
    Files.write(Paths.get(targetPath), Objects.requireNonNull(rsp.getBody(), "未获取到下载文件"));
}
这种下载方法实际上是将下载文件一次性加载到客户端本地内存,然后从内存将文件写入磁盘。这种方式对于小文件的下载还比较适合,如果文件比较大或者文件下载并发量比较大,容易造成内存的大量占用,从而降低应用的运行效率
@Autowired
private RestTemplate restTemplate;
/**
 * 大文件下载
 * @throws IOException
 */
@Test
public void downloadBigFile() throws IOException {
    String userName = "张三";
    String fileName = "c98b677c-0948-46ef-84d2-3742a2b821b0.jpg";
    //请求地址
    String url = "http://localhost:8080/downloadFile/{1}/{2}";

    //定义请求头的接收类型
    RequestCallback requestCallback = request -> request.getHeaders()
    .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));

    //对响应进行流式处理而不是将其全部加载到内存中
    String targetPath = "/Users/panzhi/Desktop/"  + fileName;
    restTemplate.execute(url, HttpMethod.GET, requestCallback, clientHttpResponse -> {
        Files.copy(clientHttpResponse.getBody(), Paths.get(targetPath));
        return null;
    }, userName, fileName);
}

 

4、其他设置

1. 拦截器配置

RestTemplate 也可以设置拦截器做一些统一处理。这个功能感觉和 Spring MVC 的拦截器类似。配置也很简单:

class MyInterceptor implements ClientHttpRequestInterceptor{

        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            logger.info("enter interceptor...");
            return execution.execute(request,body);
        }
    }
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate(factory);
    MyInterceptor myInterceptor = new MyInterceptor();
    List<ClientHttpRequestInterceptor> list = new ArrayList<>();
    list.add(myInterceptor);
    restTemplate.setInterceptors(list);
    return restTemplate;
}

 

例如: 我们可以设置统一的请求头 :

//初始化RestTemplate,并加入spring的Bean工厂,由spring统一管理
    @Bean(name = "restTemplate", value = "restTemplate")
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate getRestTemplate() {
        RestTemplate restTemplate = new RestTemplate(this.createFactory());
          // 借助拦截器的方式来实现塞统一的请求头
        ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, execution) -> {
            httpRequest.getHeaders().set("user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/83.0.4103.97 Safari/537.36"); return execution.execute(httpRequest, bytes); }; restTemplate.getInterceptors().add(interceptor); List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters(); //重新设置StringHttpMessageConverter字符集为UTF-8,解决中文乱码问题 HttpMessageConverter<?> converterTarget = null; for (HttpMessageConverter<?> item : converterList) { if (StringHttpMessageConverter.class == item.getClass()) { converterTarget = item; break; } } if (null != converterTarget) { converterList.remove(converterTarget); } converterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); //加入FastJson转换器 converterList.add(new FastJsonHttpMessageConverter()); return restTemplate; }

 

2、ErrorHandler 配置

ErrorHandler 用来对调用错误对统一处理。

public class MyResponseErrorHandler extends DefaultResponseErrorHandler {

        @Override
        public boolean hasError(ClientHttpResponse response) throws IOException {
            return super.hasError(response);
        }

        @Override
        public void handleError(ClientHttpResponse response) throws IOException {
            HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
            if (statusCode == null) {
                throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
            }
            handleError(response, statusCode);
        }
        @Override
        protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
            switch (statusCode.series()) {
                case CLIENT_ERROR:
                    HttpClientErrorException exp1 = new HttpClientErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response)); logger.error(
"客户端调用异常",exp1); throw exp1; case SERVER_ERROR: HttpServerErrorException exp2 = new HttpServerErrorException(statusCode, response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); logger.error("服务端调用异常",exp2); throw exp2; default: UnknownHttpStatusCodeException exp3 = new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); logger.error("网络调用未知异常"); throw exp3; } } } @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { RestTemplate restTemplate = new RestTemplate(factory); MyResponseErrorHandler errorHandler = new MyResponseErrorHandler(); restTemplate.setErrorHandler(errorHandler); List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters(); // 通过下面代码可以添加新的 HttpMessageConverter //messageConverters.add(new ); return restTemplate; }

 

3、RestTemplate调用请求中文乱码解决办法

出现问题的原因:

    RestTemplate在请求调用过程中,字符串利用的StringHttpMessageConverter类来进行报文转换。该Converter默认的字符集是ISO8859-1, 所以在包含中文请求时会出现乱码问题。

解决办法

在项目中注入RestTemplate的地方获取到要修改的转换器,并设置中文字符集即可,如

@Bean("restTemplate")
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplateHasNoLoadBalanced() {
    return createRestTemplate();
}

private RestTemplate createRestTemplate() {
    RestTemplate restTemplate = new RestTemplateBuilder()
        .setConnectTimeout(Duration.ofSeconds(5)).setReadTimeout(Duration.ofSeconds(30)).build();
    List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
    Iterator<HttpMessageConverter<?>> iterator = converters.iterator();
    while (iterator.hasNext()) {
        HttpMessageConverter<?> converter = iterator.next();
        if (converter instanceof MappingJackson2XmlHttpMessageConverter){
            // 删除xml序列化,内部微服务之间用json交互
            iterator.remove();
        }else if (converter instanceof StringHttpMessageConverter){
            // 解决使用restTemplate中文乱码的问题
            ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
        }
    }
    return restTemplate;
}

 

 

 

 

 





posted @ 2022-06-10 13:20  邓维-java  阅读(4708)  评论(0编辑  收藏  举报