Spring之RestTemplate详解

Spring之RestTemplate详解

  • 1 RestTemplate
    • 1.1 引言
    • 1.2 环境配置
      • 1.2.1 非Spring环境下使用RestTemplate
      • 1.2.2 Spring环境下使用 RestTemplate
      • 1.2.3 Spring环境下增加线程号
    • 1.3 API 实践
      • 1.3.1 GET请求
        • 1.3.1.1 不带参请求
        • 1.3.1.2 带参的get请求(使用占位符号传参)
        • 1.3.1.3 带参的get请求(restful风格)
        • 1.3.1.4 getForEntity使用示例
        • 1.3.1.5 header设置参数
      • 1.3.2 POST请求
        • 1.3.2.1 模拟表单请求
        • 1.3.2.2 模拟表单请求(传递对象)
        • 1.3.2.3 模拟JSON请求
        • 1.3.2.4 模拟页面重定向
      • 1.3.3 PUT请求
      • 1.3.4 DELETE请求
      • 1.3.5 通用请求方法exchange方法
      • 1.3.6 文件上传与下载
        • 1.3.6.1 文件上传
        • 1.3.6.2 文件下载
        • 1.3.6.3 大文件下载
      • 1.3.7 通过服务名调用
        • 1.3.7.1 简介
        • 1.3.7.2 @LoadBalanced作用
    • 1.4 核心讲解
      • 1.4.1 excute
      • 1.4.2 RequestCallback
      • 1.4.3 ResponseExtractor

1 RestTemplate

1.1 引言

现如今的 IT 项目,由服务端向外发起网络请求的场景,基本上处处可见!
传统情况下,在服务端代码里访问 http 服务时,一般会使用 JDK 的 HttpURLConnection 或者 Apache 的 HttpClient,不过这种方法使用起来太过繁琐,而且 api 使用起来非常的复杂,还得操心资源回收。

以下载文件为例,通过 Apache 的 HttpClient方式进行下载文件,会很复杂
其实Spring已经为我们提供了一种简单便捷的模板类来进行操作,它就是RestTemplate

RestTemplate是一个执行HTTP请求的同步阻塞式工具类,它仅仅只是在 HTTP 客户端库(例如 JDK HttpURLConnection,Apache HttpComponents,okHttp 等)基础上,封装了更加简单易用的模板方法 API,方便程序员利用已提供的模板方法发起网络请求和处理,能很大程度上提升我们的开发效率

1.2 环境配置

1.2.1 非Spring环境下使用RestTemplate

如果当前项目不是Spring项目,加入spring-web包,即可引入RestTemplate类

<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.6.RELEASE</version>
</dependency>

编写一个单元测试类,使用RestTemplate发送一个GET请求,看看程序运行是否正常

@Test
public void simpleTest() {RestTemplate restTemplate = new RestTemplate();String url = "http://jsonplaceholder.typicode.com/posts/1";String str = restTemplate.getForObject(url, String.class);System.out.println(str);
}

1.2.2 Spring环境下使用 RestTemplate

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

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

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

注意,这种初始化方法,是使用了JDK自带的HttpURLConnection作为底层HTTP客户端实现。

当然,我们还可以修改RestTemplate默认的客户端,例如将其改成HttpClient客户端,方式如下:

@Configuration
public class RestTemplateConfig {@ConditionalOnMissingBean(RestTemplate.class)@Beanpublic RestTemplate restTemplate(){RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());return restTemplate;}/*** 使用HttpClient作为底层客户端* @return*/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的HttpClient、Apache的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);
}

1.2.3 Spring环境下增加线程号

使用RestTemplate调用远程接口时,有时需要在header中传递信息,比如:traceId,source等,便于在查询日志时能够串联一次完整的请求链路,快速定位问题。这种业务场景就能通过ClientHttpRequestInterceptor接口实现,具体做法如下

第一步,定义一个LogFilter拦截所有接口请求,在MDC中设置traceId:

public class LogFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {MDC.put("TRACE_ID",UUID.randomUUID().toString());System.out.println("记录请求日志");chain.doFilter(request, response);System.out.println("记录响应日志");}@Overridepublic void destroy() {}
}

第二步,实现ClientHttpRequestInterceptor接口,MDC中获取当前请求的traceId,然后设置到header中

public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {@Overridepublic ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {request.getHeaders().set("traceId", MDC.get("TRACE_ID"));ClientHttpResponse response = execution.execute(request, body);return response;}
}

第三步,定义配置类,配置上面定义的RestTemplateInterceptor类:

@Configuration
public class RestTemplateConfiguration {@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();restTemplate.setInterceptors(Collections.singletonList(restTemplateInterceptor()));return restTemplate;}@Beanpublic RestTemplateInterceptor restTemplateInterceptor() {return new RestTemplateInterceptor();}
}

能使用MDC保存traceId等参数的根本原因是,用户请求到应用服务器,Tomcat会从线程池中分配一个线程去处理该请求。那么该请求的整个过程中,保存到MDC的ThreadLocal中的参数,也是该线程独享的,所以不会有线程安全问题

1.3 API 实践

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

1.3.1 GET请求

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

getForObject():返回值是HTTP协议的响应体
getForEntity():返回的是ResponseEntity,ResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息
在Spring Boot环境下写一个单元测试用例,首先创建一个Api接口,然后编写单元测试进行服务测试。

1.3.1.1 不带参请求

不带参的get请求

@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());
}
1.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());
}
1.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());
}
1.3.1.4 getForEntity使用示例

上面的所有的getForObject请求传参方法,getForEntity都可以使用,使用方法上也几乎是一致的,只是在返回结果接收的时候略有差别。

使用ResponseEntity 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);
}
1.3.1.5 header设置参数
//请求头
HttpHeaders headers = new HttpHeaders();
headers.add("token", "123456789");//封装请求头
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(headers);ResponseEntity<Map> exchange = restTemplate.exchange('请求的url', HttpMethod.GET, formEntity, Map.class);

1.3.2 POST请求

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

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

1.3.2.1 模拟表单请求

模拟表单请求,post方法测试

@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());
}
1.3.2.2 模拟表单请求(传递对象)

模拟表单请求,post方法测试(对象接受)

@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());
}
1.3.2.3 模拟JSON请求

模拟JSON请求,post方法测试

@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());
}
1.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");//用于提交完成数据之后的页面跳转,返回跳转urlURI uri = restTemplate.postForLocation(url, request);System.out.println(uri.toString());
}输出结果如下:
http://localhost:8080/index.html

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

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

1.3.5 通用请求方法exchange方法

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

打开源码,我们可以很清晰的看到这一点。

在这里插入图片描述

1.3.6 文件上传与下载

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

案例如下,具体实现细节参考代码注释!

1.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());
}
1.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(), "未获取到下载文件"));
}

这种下载方法实际上是将下载文件一次性加载到客户端本地内存,然后从内存将文件写入磁盘。这种方式对于小文件的下载还比较适合,如果文件比较大或者文件下载并发量比较大,容易造成内存的大量占用,从而降低应用的运行效率

1.3.6.3 大文件下载
@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);
}

这种下载方式的区别在于:
设置了请求头APPLICATION_OCTET_STREAM,表示以流的形式进行数据加载
RequestCallback结合File.copy保证了接收到一部分文件内容,就向磁盘写入一部分内容。而不是全部加载到内存,最后再写入磁盘文件。
在下载大文件时,例如excel、pdf、zip等等文件,特别管用,

1.3.7 通过服务名调用

1.3.7.1 简介

在Spring Cloud中,通过服务名称访问其他服务通常需要使用@LoadBalanced的RestTemplate,可以使用服务名(service ID)来代替具体的URL。

例如:

@Configuration
public class Config 
{@LoadBalanced@Beanpublic RestTemplate restTemplate() {SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();// 设置超时requestFactory.setConnectTimeout(60 * 1000);requestFactory.setReadTimeout(60 * 1000);//利用复杂构造器可以实现超时设置,内部实际实现为 HttpClientRestTemplate restTemplate = new RestTemplate(requestFactory);return restTemplate ;}
}

上面的代码定义了一个 RestTemplate bean,并使用@LoadBalanced注解来开启负载均衡。这样,就可以在 应用中自动注入RestTemplate,然后使用服务名称来发送请求。

注意:这种方法需要服务被注册到服务发现组件(如Eureka,Nacos,Consul等)。这样,应用才能找到服务名对应的实际网络位置。

1.3.7.2 @LoadBalanced作用

@LoadBalanced 是 Netflix 的 ribbon 中的一个负载均衡的注解,并完成以下工作:

从负载均衡器中选一个对应的服务实例,所有的服务名实例都放在负载均衡器中的serverlist中;
从挑选的实例中去请求内容;
由服务名转为真正使用的ip地址;

restTemplate能通过服务名获取到具体的服务,是由LoadBalancerInterceptor这个拦截器实现的,而具体的工作是由RibbonLoadBalancerClient来完成的。
RibbonLoadBalancerClient将服务名通过负载均衡策略转为了实际的ip和端口后再apply给restTemplate。

1.4 核心讲解

1.4.1 excute

所有的get、post、delete、put、options、head、exchange(一部分)方法最终调用的都是excute方法

public T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor responseExtractor, Object… uriVariables)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从上面的Excute方法中我们可以看出Excute方法只是将String格式的URI转成了java.net.URI,之后调用了doExecute方法。
整个调用过程如下
在这里插入图片描述
doExecute方法如下:
在这里插入图片描述
doExecute 方法并没有暴露出来,只能通过继承调用
这里需要了解两个类: RequestCallback & ResponseExtractor

1.4.2 RequestCallback

RequestCallback用于在ClientHttpRequest上操作的代码的回调接口。允许操作请求头,并写入请求主体。

RequestCallback有两个实现类,都是内部类:

  • AcceptHeaderRequestCallback:只处理请求头,用于restTemplate.getXXX()方法
  • HttpEntityRequestCallback:继承于AcceptHeaderRequestCallback可以处理请求头和body,用于restTemplate.putXXX()、restTemplate.postXXX()和restTemplate.exchange()方法

1.4.3 ResponseExtractor

restTemplate对此接口ResponseExtractor的检索方法实现,使用的通用回调接口执行,从clienthttpresponse提取数据的实际工作(解析HTTP响应的数据),但不需要担心异常处理或关闭资源

RequestCallback有三个实现类:

  • HeadersExtractor:用于提取请求头
  • HttpMessageConverterExtractor:用于提取响应body
  • ResponseEntityResponseExtractor:使用HttpMessageConverterExtractor提取body(委托模式),然后将body和响应头、状态封装成ResponseEntity对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/192834.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis7--基础篇4(Redis事务)

Redis事务是什么 可以一次执行多个命令&#xff0c;本质是一组命令的集合&#xff0c;一个事务中的所有命令都会序列化&#xff0c;按顺序串行&#xff0c;而不会被其他命令插入。 其作用就是在一个队列中&#xff0c;一次性、顺序、排他的执行一系列命令。 Redis事务 VS 数据…

【每日一题】拼车+【差分数组】

文章目录 Tag题目来源解题思路方法一&#xff1a;差分 写在最后 Tag 【差分数组】【数组】【2023-12-02】 题目来源 1094. 拼车 解题思路 本题朴素的解题思路是统计题目中提到的每一个站点的车上人数&#xff0c;如果某个站点的车上人数大于车上的座位数直接返回 false&…

基于 Vue、Datav、Echart 框架的 “ 数据大屏项目 “,通过 Vue 组件实现数据动态刷新渲染,内部图表可实现自由替换

最近在研究大数据分析&#xff0c;基于 Vue、Datav、Echart 框架的 " 数据大屏项目 "&#xff0c;通过 Vue 组件实现数据动态刷新渲染&#xff0c;内部图表可实现自由替换。部分图表使用 DataV 自带组件&#xff0c;可进行更改&#xff0c;详情请点击下方 DataV 文档…

abapgit 安装及使用

abapgit 需求 SA[ BASIS 版本 702 及以上 版本查看路径如下&#xff1a; 安装步骤如下&#xff1a; 1. 下载abapgit 独立版本 程序 链接如下&#xff1a;raw.githubusercontent.com/abapGit/build/main/zabapgit_standalone.prog.abap 2.安装开发版本 2.1 在线安装 前置条…

【C++】类和对象——初始化列表和static修饰成员

首先我们来谈一下初始化列表&#xff0c;它其实是对于我们前边构造函数体内初始化的一种补充&#xff0c;换一种说法&#xff0c;它以后才是我们构造函数的主体部分。 我们先考虑一个问题&#xff0c;就是一个类里面有用引用或const初始化的成员变量&#xff0c;比如说&#xf…

HTML_web扩展标签

1.表格标签 2.增强表头表现 4.表格属性&#xff08;实际不常用&#xff09; 结构标签&#xff1a; 合并单元格&#xff1a; 更多请查看主页

从零开始,探索Spring框架的魅力与实践

Spring 1&#xff0c;介绍1.1 为什么要学?1.2 学什么? 2&#xff0c;Spring相关概念2.1 初识Spring2.1.1 Spring家族2.1.2 了解Spring发展史 2.2 Spring系统架构2.2.1 系统架构图2.2.2 spring主要内容 2.3 Spring核心概念2.3.1 目前项目中的问题2.3.2 IOC、IOC容器、Bean、DI…

影刀实例五,网页表单中多下拉框填写问题

一&#xff0c;背景 网页表单中&#xff0c;经常有这样一个场景&#xff0c;填写籍贯.并且是以三个下拉框表示&#xff0c;分别代表省&#xff0c;市&#xff0c;县.并且都是非标准的&#xff0c;不能直接使用影刀内置命令.常规思路是&#xff1a;分别处理省&#xff0c;市&…

万能的视频格式播放器

今天博主给大家带来一款“万能”的视频播放器——VLC Media Player&#xff0c;支持的文件格式非常多&#xff0c;大家快来一起看看吧&#xff01; VLC Media Player 是一款可播放大多数格式&#xff0c;而无需安装编解码器包的媒体播放器。可以播放 MPEG-1、MPEG-2、MPEG-4、D…

C语言-指针_02

指针-02 1. 指针的指针 概念&#xff1a;指针变量中存储的是指针的地址&#xff0c;又名 二维指针 语法&#xff1a; 数据类型 **p;示例&#xff1a; #include <stdio.h> int main(int argc, char const *argv[]) {int num 10;int *p1 &num;int **p2 &p1…

深度学习——第1章 深度学习的概念及神经网络的工作原理

1.1 序言——探索智能机器 千百年来&#xff0c;人类试图了解智能的机制&#xff0c;并将它复制到思维机器上。 人类从不满足于让机械或电子设备帮助做一些简单的任务&#xff0c;例如使用滑轮吊起沉重的岩石&#xff0c;使用计算器做算术。 人类希望计算机能够自动化执行更…

医美店会员管理系统预约小程序作用是什么

医美在美业中占据着一定地位&#xff0c;爱美使然和经济独立、悦己消费下&#xff0c;不少女性会前往医美机构做脸部整容、嫩肤补水等服务&#xff0c;如美容院一样都是具备本地外地属性的&#xff0c;因此在如今互联网盛行下&#xff0c;商家需要借势线上破解难题及增强生意效…

【预测工具】不须编码的预测和数据可视化工具

有一天&#xff0c;我的同事问我&#xff0c;他应该如何做一个快速预测模型而不是Excel&#xff0c;并产生比线性回归或Excel图中的那些简单方程更好的结果。这是我的答案。 TableCurve 2D (Image by author) Sigmaplot很早以前就推出了这个软件。它已被广泛用于在数据中寻找最…

Structured Streaming: Apache Spark的流处理引擎

欢迎来到我们的技术博客&#xff01;今天&#xff0c;我们要探讨的主题是Apache Spark的一个核心组件——Structured Streaming。作为一个可扩展且容错的流处理引擎&#xff0c;Structured Streaming使得处理实时数据流变得更加高效和简便。 什么是Structured Streaming&#…

【java+vue+微信小程序项目】从零开始搭建——健身房管理平台(2)后端跨域、登录模块、springboot分层架构、IDEA修改快捷键、vue代码风格

项目笔记为项目总结笔记,若有错误欢迎指出哟~ 【项目专栏】 【java+vue+微信小程序项目】从零开始搭建——健身房管理平台(1)spring boot项目搭建、vue项目搭建、微信小程序项目搭建 【java+vue+微信小程序项目】从零开始搭建——健身房管理平台(2)后端跨域、登录模块、sp…

python 图书馆选座小程序源码

开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索房间&#xff0c;轮播图&#xff0…

wordpress安装之Linux解压缩安装

本次教程是为了让大家少走弯路&#xff0c;可以更直观的去认识我们不懂的知识面。 首先我们安装解压缩的软件 命令如下&#xff1a; yum install -y unzip 上一篇我们讲到传输文件了 这篇我们把传输过来的压缩包解压并进行安装。follow me&#xff01; 我们输入命令 unzi…

FDM3D打印系列——天秤座黄金圣斗士模型制作全过程视频

FDM打印天秤座黄金圣斗士全过程视频 大家好&#xff0c;我是阿赵。   这次带来的是天秤座黄金圣斗士模型的3D打印加后期加工的全过程。其实打印的过程之前都已经介绍过了&#xff0c;不过有朋友说想实际看看整个制作过程是怎样&#xff0c;所以我录了这个视频。   上完色之…

测试Centos上用Gunicorn启动的Django-Web服务在Django源文件有改变的情况下能否自动重载最新源码下的web服务

01-先上传最新的源码文件 参考博文 https://blog.csdn.net/wenhao_ir/article/details/134762966 进行 02-先在Django直接开web服务下修改源码测试 这是没有问题的&#xff0c;会自己重置。 03-开启gunicorn服务 cd /djangoproject/mmdj01/ gunicorn -c /djangoproject/mm…

【开源】前后端分离的在线考试系统,支持多种部署方式

在线考试系统 https://download.csdn.net/download/mo3408/88593116 在线考试系统是一种利用网络技术&#xff0c;实现在线出题、答题、阅卷、成绩查询等一系列考试活动的系统。它不受地理位置限制&#xff0c;可以实现远程考试&#xff0c;大大提高了考试的效率和便利性。此…