1. 介绍
微服务应用之间访问分为HTTP,RPC两种方式,这里讨论HTTP即应用服务端间后台访问。 其中常用的访问工具包括早期的HttpClient,后续的RestTemplate、Feign、WebClient等,本篇文章主要讲述RestTemplate。
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种可以便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率。
RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客户端。在 Spring 5 中引入了 WebClient 作为非阻塞式 Reactive HTTP 客户端。
(2)RestTemplate 定义了 36 个与 REST 资源交互的方法,其中的大多数都对应于 HTTP 的方法。
注意: 严格来说只有 11 个独立的方法,其中有 10 个有三种重载形式,而第 11 个则重载了 6 次,这样一共形成了 36 个方法。
实际上,由于 Post 操作的非幂等性,它几乎可以代替其他的 CRUD 操作。
delete():这个方法是在特定的 URL 上对资源执行 HTTP DELETE 操作 exchange():在 URL 上执行特定的 HTTP 方法,返回包含对象的 ResponseEntity,这个对象是从响应体中映射得到的 execute():在 URL 上执行特定的 HTTP 方法,返回一个从响应体映射得到的对象 getForEntity():发送一个 HTTP GET 请求,返回的 ResponseEntity 包含了响应体所映射成的对象 getForObject():发送一个 HTTP GET 请求,返回的请求体将映射为一个对象 postForEntity():POST 数据到一个 URL,返回包含一个对象的 ResponseEntity,这个对象是从响应体中映射得到的 postForObject():POST 数据到一个 URL,返回根据响应体匹配形成的对象 headForHeaders():发送 HTTP HEAD 请求,返回包含特定资源 URL 的 HTTP 头 optionsForAllow():发送 HTTP OPTIONS 请求,返回对特定 URL 的 Allow 头信息 postForLocation():POST 数据到一个 URL,返回新创建资源的 URL put():PUT 资源到特定的 URL
2. 在项目中使用RestTemplate
2.1. build.gradle中增加rest
implementation('org.yunchen.gb:gb-core:x.x.x')
2.2. 配置application.yml
gb: rest: prefix: (1) unifiedJsonResult: true (2)
1 | GbRestController或RestController注解的类访问前增加的前缀,默认为/api,置空后为/ |
2 | 启用统一json格式,GbRestController或RestController注解的类的返回结果将统一按照UnifiedResult类进行处理 |
2.3. 配置restTemplate
2.3.1. 增加GbRestTemplateErrorHandler类
public class GbRestTemplateErrorHandler extends DefaultResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { // 返回false表示不管response的status是多少都返回没有错 return false; } }
2.3.2. 添加restTemplate类的bean
在conf类或application类中增加如下的代码:
@Bean public RestTemplate getRestTemplate(ClientHttpRequestFactory simleClientHttpRequestFactory) { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(new GbRestTemplateErrorHandler()); restTemplate.setRequestFactory(simleClientHttpRequestFactory); return restTemplate; } @Bean public ClientHttpRequestFactory getSimpleClientHttpRequestFactory() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(5000);//单位为ms factory.setConnectTimeout(5000);//单位为ms return factory; }
2.4. 增加GbRestController或RestController注解类的controller
@GbRestController class RestDataController { //网关位置 private String BASEURI = "http://localhost:8021/"; @Autowired private RestTemplate restTemplate; public Map simple(HttpServletResponse response) { String url = BASEURI; //示例参数 Map params=[paramval:"666"] //示例header HttpHeaders headers = new HttpHeaders(); headers.add("VISITKEY","UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"); HttpEntity<String> entity = new HttpEntity<String>(null,headers); //GET方式采用品街方法,可以不写最后的参数 //ResponseEntity<String> resp = restTemplate.exchange(url+"gjjsch/"+"666", HttpMethod.GET, entity, String.class); //参数替代方法,即大括号{key},map中包含对应key即value,最终使用value替换对应变量位置 ResponseEntity<String> resp = restTemplate.exchange(url+"gjjsch/{paramval}", HttpMethod.GET, entity, String.class,params); // 判断请求是否发生异常 if(!resp.getStatusCode().is2xxSuccessful()){ //response.setStatus(HttpStatus.CONFLICT.value()) //409 状态码 response.setStatus(resp.getStatusCodeValue()) //同步状态码 return [message:resp.getStatusCode().name(),status:resp.getStatusCodeValue(),header:resp.getHeaders(),body:new ObjectMapper().readValue(resp.getBody(),Map.class)] } return [result:new ObjectMapper().readValue(resp.getBody(),Map.class)] }
2.5. 访问应用地址查看效果
浏览器访问应用地址 http://localhost:8080/restData/simple ,应用访问网关返回正常结果
{"status":200,"errorCode":"","errorMsg":"","resultBody":{"result":{total:0,rows:{}}}}
返回错误结果
{"status":404,"errorCode":"","errorMsg":"","resultBody":{"message":"NOT_FOUND","status":404,"header":{"Content-Type":["application/json;charset=UTF-8"],"Transfer-Encoding":["chunked"],"Date":["Sun, 12 Apr 2020 05:43:21 GMT"]},"body":{"timestamp":"2020-04-12T05:43:20.923+0000","status":404,"error":"Not Found","message":"No message available","path":"/gjjsch/666"}}}
3. 在springboot中RestTemplate的知识点
3.1. 初始化Bean
在springboot启动类或者配置类中声明
@Bean //@LoadBalanced (1) public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); return restTemplate; }
1 | 在微服务(SpringCloud)应用中注解@LoadBalanced可以让RestTemplate自动注入负载均衡处理,前提是应用也作为服务端注册到中心。 |
作为微服务客户端访问HTTP请求时,访问url格式为:http://服务名/映射链接,如果调用的服务存在上下文,那么则要通过http://服务名/服务上下文/映射链接
在使用ApiGateway网关模式的情况下,由网关负责负载均衡,访问url格式应为:http://网关地址、端口/映射链接,如果调用的服务存在上下文,那么则要通过http://网关地址、端口/服务上下文/映射链接
也就是按照IP端口方式访问。
3.2. 设置超时参数
@Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(5000);//单位为ms factory.setConnectTimeout(5000);//单位为ms return factory; }
3.3. 设置错误返回处理
当使用 RestTemplate 发送请求时, 如果接口返回的不是 200 状态(而是 4xx、5xx 这样的异常状态),则会抛出异常报错 在实际接口对接中,需要获取接口返回的异常信息并返回(比如返回到前端)。 具体实现可以通过自定义 RestTemplate 异常的处理来实现。
注:上述问题不仅仅是在GetForObject,PostForObject存在,所有以execute为基础调用的方法都存在,简单说不仅仅因为是GetForEntity,或者PostForEntity就能避免404异常的抛出。
(1)首先我们需要创建一个自己的异常处理控制器(ExceptionHandler 类),该类实现 ResponseErrorHandler 接口。
public class RestThrowErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { // 返回false表示不管response的status是多少都返回没有错 // 这里可以自己定义那些status code你认为是可以抛Error return false; } @Override public void handleError(ClientHttpResponse response) throws IOException { // 这里面可以实现你自己遇到了Error进行合理的处理 } }
(2)在 RestTemplate 配置类中,指定使用自定义的异常处理控制。
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ RestTemplate restTemplate = new RestTemplate(factory); //Response status code 4XX or 5XX to the client. restTemplate.setErrorHandler(new RestThrowErrorHandler()); return restTemplate; } }
(3)最后写是一个简单的请求样例。经过上面设置后,无论请求成功或者失败都会返回(不会抛异常),所以需要通过状态码来判断请求是否成功。
@RestController public class HelloController { @Autowired private RestTemplate restTemplate; @GetMapping("/test") public String test() { String url = "http://localhost:8080/xxxxxx"; ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class); // 判断请求是否发生异常 if(!responseEntity.getStatusCode().is2xxSuccessful()){ // 返回异常信息 return "请求失败,异常信息:" + responseEntity.getBody(); } // 没有异常的话则返回正常的响应结果 return responseEntity.getBody(); } }
3.4. 错误的全局统一处理
本部分仅为参考,可根据实际需要进行运用。
(1)通常不会直接把业务代码写在 Controller 里,而是通过 Service 实现。首先创建一个 Service,里面调用 RestTemplate 进行网络请求,当请求异常时直接抛出异常。
@Service public class UserService { @Autowired private RestTemplate restTemplate; public String getInfo() { String url = "http://localhost:8080/xxxxxx"; ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class); // 判断请求是否发生异常 if(!responseEntity.getStatusCode().is2xxSuccessful()){ // 抛出异常 throw new RestClientException(responseEntity.getBody()); } // 没有异常的话则返回正常的响应结果 return responseEntity.getBody(); } }
当然,可以直接对非200状态的返回进行返回处理,这里的示例主要是采用抛出异常,最终统一封装非200状态异常返回对象
(2)编写测试Controller,调用 Service 发起请求,然后返回结果。
@RestController public class HelloController { @Autowired private UserService userService; @GetMapping("/test") public String test() { return userService.getInfo(); } }
(3)封装返回统一结果,由于前面 Service 中将异常抛出了,所以要定义一个全局的异常处理类,捕获这个异常,并返回给前端处理的结果。
@ControllerAdvice public class CustomExceptionHandler { @ExceptionHandler(RestClientException.class) public ResponseEntity<String> throwRestException(RestClientException restClientException){ return new ResponseEntity<String>(restClientException.getMessage(), HttpStatus.BAD_REQUEST); } }
测试一下,可以看到异常信息已经返回到前端页面
3.5. 微服务中RestTemplate使用(精读)
RestTemplate为HTTP访问封装了多种方法(例如getForOjbect,getForEntity,postForObject,postForEntity,exchange,excutte,delete,put…),但为了便于进行微服务访问,最应该掌握的方法时exchange.
Exchange优势在于:
(1)允许调用者指定HTTP请求的方法(GET,POST,DELETE等) (2)可以在请求中增加body以及头信息,其内容通过参数‘HttpEntity<?>requestEntity’描述 (3)exchange支持‘含参数的类型’(即泛型类)作为返回类型,该特性通过‘ParameterizedTypeReference<T>responseType’描述。
3.5.1. RestTemplate Exchange方法
exchange能够配置访问的url参数,requestEntity(header及body),返回体,返回状态status,及返回响应header
ResponseEntity<T> results = restTemplate.exchange(url,HttpMethod, requestEntity, T.class, params);
说明: 1)url: 请求地址; 2)method: 请求类型(如:POST,PUT,DELETE,GET); 3)requestEntity: 请求实体,封装请求头,请求内容 4)responseType: 响应类型,根据服务接口的返回类型决定(String.class,Map.class) 5)uriVariables: url中参数变量值,包括路径变量
3.5.2. Springboot常见请求Request注解说明(Request简单/复杂类型参数传递)
为了能够更好的使用RestTemplate访问,需要理解在常见的Controller中常见的前台到后台的参数传递,或者说在访问不同类型需求时,如何正确传递变量,具体说有如下:
@PathVariable @RequestParam @RequestBody
PathVariable
路径参数变量
@RequestMapping(value = "/info/{userId}", method = RequestMethod.GET) public User info(@PathVariable("userId") String userId) throws IOException {
RequestParam
url中形参变量,或者说明参变量,常见如?varxxx=…&varyyy=…&varzzz=…
@GetMapping("/gjjschbyparam") public GjjInfo gjjschbyParam(@RequestParam String idcard) {
RequestBody
复杂类型变量传递
@RequestMapping(value = "/getbook2", method = RequestMethod.POST) public Book book2(@RequestBody Book book) {
3.5.3. GET请求
在微服务中,因为经常在HEADER中设置访问控制及授权等内容,建议使用Exchange替代GetForOjbect及getForEntity。
举例如下:
(1)路径及参数变量访问
String url = BASEURI; Map params=[paramval:"666"] HttpHeaders headers = new HttpHeaders(); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); HttpEntity<String> entity = new HttpEntity<String>(null,headers); //GET方式采用品街方法,可以不写最后的参数 //ResponseEntity<String> resp = restTemplate.exchange(url+"gjjsch/"+"666", HttpMethod.GET, entity, String.class); //参数替代方法,即大括号{key},map中包含对应key即value,最终使用value替换对应变量位置 ResponseEntity<String> resp = restTemplate.exchange(url+"gjjsch/{paramval}", HttpMethod.GET, entity, String.class,params); println resp.getHeaders() println resp.getStatusCodeValue() if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp.getBody(); }
(2)不同返回结果,根据实际情况自行选择设定
ResponseEntity<String> resp = restTemplate.exchange(url+"gjjsch/{paramval}", HttpMethod.GET, entity, String.class,params); ResponseEntity<Map> resp = restTemplate.exchange(url+"gjjschapiresult/{paramval}", HttpMethod.GET, entity, Map.class,params); ResponseEntity<ApiResult> resp = restTemplate.exchange(url+"gjjschapiresult/{paramval}", HttpMethod.GET, entity, ApiResult.class,params); //复杂返回数据类型可以使用ParameterizedTypeReference定义 ResponseEntity<ApiResult<GjjInfo>> resp = restTemplate.exchange(url+"gjjschapiresult/{paramval}", HttpMethod.GET, entity, new ParameterizedTypeReference<ApiResult<GjjInfo>>(){},params);
3.5.4. POST
同样,因为经常在HEADER中设置访问控制及授权等内容,建议使用postForEntity作为常用POST访问方式
无参数传递
void exchangePostNoParam(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); HttpEntity<String> entity = new HttpEntity<String>(HttpEntity.EMPTY,headers); //参数直接拼接 ResponseEntity<GjjInfo> resp = restTemplate.exchange(url+"testpost?idcard=666", HttpMethod.POST, entity, GjjInfo.class); println resp; if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp } println resp.getHeaders() println resp.getStatusCodeValue() }
返回结果开发时可先使用String,输出正常后可转化为对应类型GjjInfo ===== 占位符参数
void exchangePostReplaceParam(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); HttpEntity<String> entity = new HttpEntity<String>(HttpEntity.EMPTY,headers); //采用占位符,参数可包括多个 ResponseEntity<GjjInfo> resp = restTemplate.exchange(url+"testpost?idcard={1}", HttpMethod.POST, entity, GjjInfo.class,"777"); println resp; if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp } println resp.getHeaders() println resp.getStatusCodeValue() }
Map参数传递
void exchangePostMapParam(){ String url = BASEURI; Map params=[paramval:"666"] HttpHeaders headers = new HttpHeaders(); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); HttpEntity<String> entity = new HttpEntity<String>(HttpEntity.EMPTY,headers); ResponseEntity<GjjInfo> resp = restTemplate.exchange(url+"testpost?idcard={paramval}", HttpMethod.POST, entity, GjjInfo.class,params); println resp; if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp } println resp.getHeaders() println resp.getStatusCodeValue() }
3.5.5. 返回为数组类型
返回类型可以为Strin,Map等通用类型,也可以返回具体Pojo对象,也可以针对list返回类型使用对象数组接收返回内容
void exchangeRespArray(){ String url = BASEURI; Map params=[paramval:"666"] HttpHeaders headers = new HttpHeaders(); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); HttpEntity<String> entity = new HttpEntity<String>(HttpEntity.EMPTY,headers); ResponseEntity<GjjInfo[]> resp = restTemplate.exchange(url+"testgetall?idcard={paramval}", HttpMethod.POST, entity, GjjInfo[].class,params); //可以使用String先观察结果 //ResponseEntity<String> resp = restTemplate.exchange(url+"testgetall?idcard={paramval}", HttpMethod.POST, entity, String.class,params); if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp.getBody() } println resp.getHeaders() println resp.getStatusCodeValue() }
@RequestBody方式参数对象传递一 Pojo
void exchangeRequestBodyByString(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); //AJAX请求必须添加 headers.setContentType(MediaType.APPLICATION_JSON); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); GjjInfo requestobj = new GjjInfo(); requestobj.setIdcard("888"); String json = JSON.toJSONString(requestobj); HttpEntity<String> requestEntity = new HttpEntity<>(json, headers); ResponseEntity<String> resp = restTemplate.exchange(url+"testpostobj", HttpMethod.POST, requestEntity, String.class); if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp.getBody() } println resp.getHeaders() println resp.getStatusCodeValue() }
上述是把对象转换成json字符串,以字符串方式传递,也可以直接将对象传递,如下:
void exchangeRequestBodyByPojo(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); GjjInfo requestobj = new GjjInfo(); requestobj.setIdcard("888"); //String json = JSON.toJSONString(requestobj); HttpEntity<GjjInfo> requestEntity = new HttpEntity<>(requestobj, headers); ResponseEntity<String> resp = restTemplate.exchange(url+"testpostobj", HttpMethod.POST, requestEntity, String.class); if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp.getBody() } println resp.getHeaders() println resp.getStatusCodeValue() }
@RequestBody方式参数对象传递一 Map
void exchangeRequestBodyByMap(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); //AJAX请求必须添加 headers.setContentType(MediaType.APPLICATION_JSON); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); Map<String, String> params= new HashMap<String, String>(); params.put("idcard","999"); HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(params, headers); ResponseEntity<String> resp = restTemplate.exchange(url+"testpostobj", HttpMethod.POST, requestEntity, String.class); if(resp.getStatusCode().is2xxSuccessful()){ println "返回结果:" println resp.getBody() } println resp.getHeaders() println resp.getStatusCodeValue() }
3.5.6. PUT请求
在微服务中,因为经常在HEADER中设置访问控制及授权等内容,建议使用Exchange实现PUT方法。
void exchangeRequestBodyPut(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); GjjInfo requestobj = new GjjInfo(); requestobj.setIdcard("888"); String jsonstr = JSON.toJSONString(requestobj); HttpEntity<String> requestEntity = new HttpEntity<>(jsonstr, headers); ResponseEntity<Map> resp = restTemplate.exchange(url+"testpostobjnoajax",HttpMethod.PUT, requestEntity, Map.class); print resp.getBody() }
3.5.7. DELETE请求
在微服务中,因为经常在HEADER中设置访问控制及授权等内容,建议使用Exchange实现DELETE方法。
void exchangeRequestBodyDelte(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); HttpEntity<String> requestEntity = new HttpEntity<String>(HttpEntity.EMPTY, headers); ResponseEntity<Map> resp = restTemplate.exchange(url+"testdelete?id={id}",HttpMethod.DELETE, requestEntity, Map.class,277); print resp.getBody() }
3.5.8. 上传文件
void exchangeUploadFile(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); // 请求头设置属性 headers.setContentType(MediaType.parseMediaType("multipart/form-data; charset=UTF-8")); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); File files = new File("C:/test/工行测试报文05221713.txt"); FileSystemResource resource = new FileSystemResource(files); MultiValueMap<String, Object> form = new LinkedMultiValueMap<>(); form.add("file", resource); HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(form, headers); ResponseEntity<Map> resp = restTemplate.exchange(url+"uploadFile",HttpMethod.POST, httpEntity, Map.class); print resp.getBody() }
3.5.9. 上传多个文件
void exchangeUploadFiles(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); //headers.setContentType(MediaType.APPLICATION_JSON_UTF8); // 请求头设置属性 //headers.setContentType(MediaType.parseMediaType("multipart/form-data; charset=UTF-8")); headers.setContentType(MediaType.MULTIPART_FORM_DATA) headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); File files = new File("C:/test/工行测试报文05221713.txt"); File files2 = new File("C:/test/招行BJC205未到账原因_2018041203021561.txt"); List<Object> upfiles = new ArrayList<>(); FileSystemResource resource = new FileSystemResource(files); FileSystemResource resource2 = new FileSystemResource(files2); MultiValueMap<String, Object> form = new LinkedMultiValueMap<>(); upfiles.add(resource); upfiles.add(resource2); form.put("files",upfiles) HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(form, headers); ResponseEntity<String> resp = restTemplate.exchange(url+"uploadMultipleFiles",HttpMethod.POST, httpEntity, String.class); print resp.getBody() }
3.5.10. 下载文件
void exchangeDownloadFiles(){ String url = BASEURI; HttpHeaders headers = new HttpHeaders(); headers.add("VISITKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXX"); HttpEntity<Resource> httpEntity = new HttpEntity<Resource>(headers); ResponseEntity<byte[]> response = restTemplate.exchange(url+"downloadFile/工行测试报文05221713.txt",HttpMethod.GET, httpEntity, byte[].class); println response.getStatusCodeValue() println response.getHeaders().getContentType() println response.getHeaders().getContentType().getSubtype() try { File file = File.createTempFile("ess-", "." + response.getHeaders().getContentType().getSubtype()); println "文件名:"+file.getAbsolutePath()+" "+file.getName() FileOutputStream fos = new FileOutputStream(file); fos.write(response.getBody()); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } }
4. RestTemplate使用详解(略读)
此章节仅供巩固理解上一章节中Exchange的参数/对象请求的封装,不同类型返回对象封装
4.1. GET请求
4.1.1. getForEntity
getForEntity()方法有如下集中形式:
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){} public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){} public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}
ResponseEntity、HttpStatus、BodyBuilder结构
ResponseEntity.java
public HttpStatus getStatusCode(){} public int getStatusCodeValue(){} public boolean equals(@Nullable Object other) {} public String toString() {} public static BodyBuilder status(HttpStatus status) {} public static BodyBuilder ok() {} public static <T> ResponseEntity<T> ok(T body) {} public static BodyBuilder created(URI location) {} ...
HttpStatus.java
public enum HttpStatus { public boolean is1xxInformational() {} public boolean is2xxSuccessful() {} public boolean is3xxRedirection() {} public boolean is4xxClientError() {} public boolean is5xxServerError() {} public boolean isError() {} }
BodyBuilder.java
public interface BodyBuilder extends HeadersBuilder<BodyBuilder> { //设置正文的长度,以字节为单位,由Content-Length标头 BodyBuilder contentLength(long contentLength); //设置body的MediaType 类型 BodyBuilder contentType(MediaType contentType); //设置响应实体的主体并返回它。 <T> ResponseEntity<T> body(@Nullable T body); }
ResponseEntity包含了HttpStatus和BodyBuilder的这些信息,这更方便处理response原生的东西。
示例:
public void rtGetEntity(){ RestTemplate restTemplate = new RestTemplate(); ResponseEntity<Notice> entity = restTemplate.getForEntity("http://fantj.top/notice/list/1/5", Notice.class); HttpStatus statusCode = entity.getStatusCode(); System.out.println("statusCode.is2xxSuccessful()"+statusCode.is2xxSuccessful()); Notice body = entity.getBody(); System.out.println("entity.getBody()"+body); ResponseEntity.BodyBuilder status = ResponseEntity.status(statusCode); status.contentLength(100); status.body("我在这里添加一句话"); ResponseEntity<Class<Notice>> body1 = status.body(Notice.class); Class<Notice> body2 = body1.getBody(); System.out.println("body1.toString()"+body1.toString()); }
getForEntity方法的返回值是一个ResponseEntity<T>,ResponseEntity<T>是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。比如下面一个例子:
@RequestMapping("/gethello") public String getHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class); String body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); StringBuffer result = new StringBuffer(); result.append("responseEntity.getBody():").append(body).append("<hr>") .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>") .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>") .append("responseEntity.getHeaders():").append(headers).append("<hr>"); return result.toString(); }
关于这段代码,关注如下几点:
getForEntity的第一个参数为我要调用的服务的地址,这里我调用了服务提供者提供的/hello接口,注意这里是通过服务名调用而不是服务地址,如果写成服务地址就没法实现客户端负载均衡了。 getForEntity第二个参数String.class表示我希望返回的body类型是String 拿到返回结果之后,将返回结果遍历打印出来
在调用服务提供者提供的接口时,可能需要传递参数,有两种不同的方式,如下:
占位符参数传递
@RequestMapping("/sayhello") public String sayHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "张三"); return responseEntity.getBody(); }
可以用一个数字做占位符,最后是一个可变长度的参数,来一一替换前面的占位符
Map参数传递
@RequestMapping("/sayhello2") public String sayHello2() { Map<String, String> map = new HashMap<>(); map.put("name", "李四"); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map); return responseEntity.getBody(); }
也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值
URI方式传递参数
第一个调用地址也可以是一个URI而不是字符串,这个时候我们构建一个URI即可,参数神马的都包含在URI中了,如下:
@RequestMapping("/sayhello3") public String sayHello3() { UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode(); URI uri = uriComponents.toUri(); ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class); return responseEntity.getBody(); }
通过Spring中提供的UriComponents来构建Uri即可。
返回对象Pojo
服务提供者不仅可以返回String,也可以返回一个自定义类型的对象,比如我的服务提供者中有如下方法:
@RequestMapping(value = "/getbook1", method = RequestMethod.GET) public Book book1() { return new Book("三国演义", 90, "罗贯中", "花城出版社"); }
对于该方法我可以在服务消费者中通过如下方式来调用:
@RequestMapping("/book1") public Book book1() { ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/getbook1", Book.class); return responseEntity.getBody(); }
4.1.2. getForObject
getForObject函数实际上是对getForEntity函数的进一步封装,如果只关注返回的消息体的内容,对其他信息都不关注
getForObject也有几个重载方法,如下:
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables){} public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) public <T> T getForObject(URI url, Class<T> responseType) getForObject() 其实比 getForEntity() 多包含了将HTTP转成POJO的功能,但是 getForObject 没有处理 response 的能力。因为它拿到手的就是成型的 pojo 。省略了很多 response 的信息。
示例POJO对象
public class Notice { private int status; private Object msg; private List<DataBean> data; } public class DataBean { private int noticeId; private String noticeTitle; private Object noticeImg; private long noticeCreateTime; private long noticeUpdateTime; private String noticeContent; }
无参数访问
public void restTemplateGetTest(){ RestTemplate restTemplate = new RestTemplate(); Notice notice = restTemplate.getForObject("http://xxx.top/notice/list/1/5", Notice.class); System.out.println(notice); }
4.1.3. 占位符参数访问
Notice notice = restTemplate.getForObject("http://fantj.top/notice/list/{1}/{2}", Notice.class,1,5);
4.1.4. Map参数访问
Map<String,String> map = new HashMap(); map.put("start","1"); map.put("page","5"); Notice notice = restTemplate.getForObject("http://fantj.top/notice/list/", Notice.class,map);
利用map装载参数,应对@PathVariable,@requestparam 参数的url形式。
4.2. POST请求
在RestTemplate中,POST请求可以通过如下三个方法来发起:
postForEntity postForObject postForLocation
4.2.1. postForEntity(带Header参数)
该方法和get请求中的getForEntity方法类似,如下例子:
@RequestMapping("/book3") public Book book3() { Book book = new Book(); book.setName("红楼梦"); ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class); return responseEntity.getBody(); }
方法的第一参数表示要调用的服务的地址,第二个参数表示上传的参数,第三个参数表示返回的消息体的数据类型
如果需要带有header参数示例如下:
public void rtPostObject(){ RestTemplate restTemplate = new RestTemplate(); String url = "http://47.xxx.xxx.96/register/checkEmail"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap<String, String> map= new LinkedMultiValueMap<>(); map.add("email", "844072586@qq.com"); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers); ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.class ); System.out.println(response.getBody()); }
MultiValueMap,Map需要灵活掌握,根据传递参数的形式不同,需要探索。
4.3. postForObject
如果只关注返回的消息体,可以直接使用postForObject。用法和getForObject一致,三种封装形式如下所示:
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {} public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {} public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}
4.3.1. postForLocation
postForLocation也是提交新资源,提交成功之后,返回新资源的URI,postForLocation的参数和前面两种的参数基本一致,只不过该方法的返回值为Uri, 这个只需要服务提供者返回一个Uri即可,该Uri表示新资源的位置。
4.3.2. 关于MultiValueMap说明
public interface MultiValueMap<K, V> extends Map<K, List<V>> {…} 为什么用 MultiValueMap ?因为 HttpEntity 接受的request类型是它。
public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers){}
这里只展示它的一个construct,从它可以看到我们传入的map是请求体,headers是请求头。
为什么用 HttpEntity 是因为 restTemplate.postForEntity 方法虽然表面上接收的request是 @Nullable Object request 类型, 但是你追踪下去会发现,这个 request 是用 HttpEntity 来解析。核心代码如下:
if (requestBody instanceof HttpEntity) { this.requestEntity = (HttpEntity<?>) requestBody; }else if (requestBody != null) { this.requestEntity = new HttpEntity<>(requestBody); }else { this.requestEntity = HttpEntity.EMPTY; }
曾尝试用map来传递参数,编译不会报错,但是执行不了,是无效的url request请求(400 ERROR)。
-
很难说,我遇到的是map可以应对@requestbody, 如果是@requestparam multifile,需要使用MultiValueMap
4.4. PUT请求
在RestTemplate中,PUT请求可以通过put方法调用,put方法的参数和前面介绍的postForEntity方法的参数基本一致,只是put方法没有返回值而已。举一个简单的例子,如下:
@RequestMapping("/put") public void put() { Book book = new Book(); book.setName("红楼梦"); restTemplate.put("http://HELLO-SERVICE/getbook3/{1}", book, 99); }
book对象是要提交的参数,最后的99用来替换前面的占位符{1}
4.5. DELETE请求
delete请求可以通过delete方法调用来实现,如下例子:
@RequestMapping("/delete") public void delete() { restTemplate.delete("http://HELLO-SERVICE/getbook4/{1}", 100); }
delete方法也有几个重载的方法,不过重载的参数和前面基本一致,不赘述。
4.6. exchange
exchange直接调用execute,返回ResponseEntity对象,它接收HttpMethod参数,可以从外部定义请求方式,例如post请求或者get请求。
RestTemplate暴露的exchange与其它接口的不同:
(1)允许调用者指定HTTP请求的方法(GET,POST,DELETE等) (2)可以在请求中增加body以及头信息,其内容通过参数‘HttpEntity<?>requestEntity’描述 (3)exchange支持‘含参数的类型’(即泛型类)作为返回类型,该特性通过‘ParameterizedTypeReference<T>responseType’描述。
4.6.1. 使用exchange指定调用方式
//设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //调用参数 MultiValueMap<String, String> params= new LinkedMultiValueMap<>(); params.add("companyId",companyId.toString()); params.add("addGoodsList",new Gson().toJson(wmsGoodsDtos)); params.add("editGoodsList",new Gson().toJson(editWmsGoodsDtos)); params.add("deleteGoodsIdList",deleteList); params.add("platformId",platformId.toString()); HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers); // 执行HTTP请求 // 最后的参数需要用String.class 使用其他的会报错 ResponseEntity<String> response = restTemplate.exchange("请求的接口地址", HttpMethod.POST, requestEntity, String.class); String result = response.getBody();
具体内容,章节二中描述很多,这里不详细说明