SpringBoot使用RestTemplate发送HTTP请求
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。我之前的HTTP开发是用apache的HttpClient开发,代码复杂,还得操心资源回收等。代码很复杂,冗余代码多。
本教程将带领大家实现Spring生态内RestTemplate的GET请求和POST请求还有exchange指定请求类型的实践和RestTemplate核心方法源码的分析,看完你就会用优雅的方式来发HTTP请求。
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。我之前的HTTP开发是用apache的HttpClient开发,代码复杂,还得操心资源回收等。代码很复杂,冗余代码多。
本教程将带领大家实现Spring生态内RestTemplate的GET请求和POST请求还有exchange指定请求类型的实践和RestTemplate核心方法源码的分析,看完你就会用优雅的方式来发HTTP请求。
一、简述RestTemplate
RestTemplate是Spring Boot用于同步client端的核心类,简化了与http服务的通信,并满足RestFul原则,程序代码可以给它提供URL,并提取结果。默认情况下,RestTemplate默认依赖jdk的HTTP连接工具。当然你也可以 通过setRequestFactory属性切换到不同的HTTP源,比如Apache HttpComponents、Netty和OkHttp。
RestTemplate能大幅简化了提交表单数据的难度,并且附带了自动转换JSON数据的功能,但只有理解了HttpEntity的组成结构(header与body),且理解了与uriVariables之间的差异,才能真正掌握其用法。这一点在Post请求更加突出,该类的入口主要是根据HTTP的六个方法制定:
在内部,RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,但也可以通过setMessageConverters注册其他的转换器。其实这点在使用的时候是察觉不到的,很多方法有一个responseType 参数,它让你传入一个响应体所映射成的对象,然后底层用HttpMessageConverter将其做映射
在内部,RestTemplate默认使用SimpleClientHttpRequestFactory和DefaultResponseErrorHandler来分别处理HTTP的创建和错误,但也可以通过setRequestFactory和setErrorHandler来覆盖。
二、get请求实践
1. 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的信息。
@SpringBootTest class RestTemplateApplicationTests { @Test public void restTemplateGetTest () { RestTemplate restTemplate = new RestTemplate(); Notice notice = restTemplate.getForObject("http://xxx.top/notice/list/1/5" , Notice.class ) ; System.out.println(notice); } @Test public void restTemplateGetTest () { RestTemplate restTemplate = new RestTemplate(); Notice notice = restTemplate.getForObject("http://fantj.top/notice/list/{1}/{2}" , Notice.class , 1, 5) ; System.out.println(notice); } @Test public void restTemplateGetTest () { RestTemplate restTemplate = new RestTemplate(); Map<String,String> map = new HashMap(); map.put("start" ,"1" ); map.put("page" ,"5" ); Notice notice = restTemplate.getForObject("http://fantj.top/notice/list/{start}/{page}" , Notice.class , map ) ; System.out.println(notice); } }
2. 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) {}
与getForObject()方法不同的是返回的是ResponseEntity对象,如果需要转换成pojo,还需要json工具类的引入,这个按个人喜好用。
@SpringBootTest class RestTemplateApplicationTests { @Test public void restTemplateGetTest () { RestTemplate restTemplate = new RestTemplate(); ResponseEntity<Notice> entity = restTemplate.getForEntity("http://xxx.top/notice/list/1/5" , Notice.class ) ; System.out.println("statusCode.is2xxSuccessful(): " +entity.getStatusCode().is2xxSuccessful()); System.out.println("entity.getStatusCode(): " +entity.getStatusCode()); System.out.println("entity.getStatusCodeValue(): " +entity.getStatusCodeValue()); System.out.println("entity.getHeaders(): " +entity.getHeaders().toString()); System.out.println("entity.getBody(): " +entity.getBody()); } @Test public void restTemplateGetTest () { RestTemplate restTemplate = new RestTemplate(); ResponseEntity<Notice> entity = restTemplate.getForEntity("http://fantj.top/notice/list/{1}/{2}" , Notice.class , 1, 5) ; System.out.println("statusCode.is2xxSuccessful(): " +entity.getStatusCode().is2xxSuccessful()); System.out.println("entity.getStatusCode(): " +entity.getStatusCode()); System.out.println("entity.getStatusCodeValue(): " +entity.getStatusCodeValue()); System.out.println("entity.getHeaders(): " +entity.getHeaders().toString()); System.out.println("entity.getBody(): " +entity.getBody()); } @Test public void restTemplateGetTest () { RestTemplate restTemplate = new RestTemplate(); Map<String,String> map = new HashMap(); map.put("start" ,"1" ); map.put("page" ,"5" ); ResponseEntity<Notice> entity = restTemplate.getForEntity("http://fantj.top/notice/list/{start}/{page}" , Notice.class , map ) ; System.out.println("statusCode.is2xxSuccessful(): " +entity.getStatusCode().is2xxSuccessful()); System.out.println("entity.getStatusCode(): " +entity.getStatusCode()); System.out.println("entity.getStatusCodeValue(): " +entity.getStatusCodeValue()); System.out.println("entity.getHeaders(): " +entity.getHeaders().toString()); System.out.println("entity.getBody(): " +entity.getBody()); } }
三、POST请求实践
1. postForObject()方法
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 {}
2. postForEntity()方法
public <T> T postForEntity (String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {}public <T> T postForEntity (String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {}public <T> T postForEntity (URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}
3. 事例
private static void createEmployee () { final String uri = "http://localhost:8080/springrestexample/employees" ; EmployeeVO newEmployee = new EmployeeVO(-1 , "Adam" , "Gilly" , "test@email.com" ); RestTemplate restTemplate = new RestTemplate(); EmployeeVO result = restTemplate.postForObject( uri, newEmployee, EmployeeVO.class ) ; System.out.println(result); } private static void postEmployee () { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap<String, String> map= new LinkedMultiValueMap<>(); map.add("id" , "1" ); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers); RestTemplate restTemplate = new RestTemplate(); EmployeeVO result = restTemplate.postForObject( uri, request, EmployeeVO.class ) ; System.out.println(result); }
4. 发送 @RequestBody JSONObject 请求
public static void main (String[] args) { JSONObject jsonObject = new JSONObject(); jsonObject.put("deviceId" , "1345" ); jsonObject.put("commandCode" , "553339299121" ); try { RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new FastJsonHttpMessageConverter()); restTemplate.postForObject("http://localhost:8080/Lock/master/sendCmd" , jsonObject, void .class ) ; } catch (RestClientException e) { e.printStackTrace(); } } @RequestMapping (value = "/sendCmd" , method = RequestMethod.POST)@ResponseBody public void sendCommnand (@RequestBody JSONObject json) { logger.info(json.toJSONString()); }
如果不需要返回值,两边可以直接配成 void。
两边的参数的结果要相同,否则会报415错误,即不支持的类型。
如果服务端有返回值,但是请求端是用void接收,会报错。(返回值怎么可能转化成void)
如果服务端没有返回值,请求端如果用其他类型接收,都是null。
即便 RestTemplate 中的返回值和服务端的返回值不同,也可以请求到,只是客户端会报异常:org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type ……
服务端的参数要加 @RequestBody 的注解
四、PUT请求实践
private static void updateEmployee () { final String uri = "http://localhost:8080/springrestexample/employees/{id}" ; Map<String, String> params = new HashMap<String, String>(); params.put("id" , "2" ); EmployeeVO updatedEmployee = new EmployeeVO(2 , "New Name" , "Gilly" , "test@email.com" ); RestTemplate restTemplate = new RestTemplate(); restTemplate.put ( uri, updatedEmployee, params); }
五、DELETE请求实践
private static void deleteEmployee () { final String uri = "http://localhost:8080/springrestexample/employees/{id}" ; Map<String, String> params = new HashMap<String, String>(); params.put("id" , "2" ); RestTemplate restTemplate = new RestTemplate(); restTemplate.delete ( uri, params ); }
六、使用exchange指定调用方式
exchange()方法跟上面的getForObject()、getForEntity()、postForObject()、postForEntity()等方法不同之处在于它可以指定请求的HTTP类型。但是你会发现exchange的方法中似乎都有@Nullable HttpEntity requestEntity这个参数,这就意味着我们至少要用HttpEntity来传递这个请求体,之前说过源码所以建议就使用HttpEntity提高性能。
@Test public void rtExchangeTest () throws JSONException { RestTemplate restTemplate = new RestTemplate(); String url = "http://xxx.top/notice/list" ; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); JSONObject jsonObj = new JSONObject(); jsonObj.put("start" ,1 ); jsonObj.put("page" ,5 ); HttpEntity<String> entity = new HttpEntity<>(jsonObj.toString(), headers); ResponseEntity<JSONObject> exchange = restTemplate.exchange(url, HttpMethod.GET, entity, JSONObject.class ) ; System.out.println(exchange.getBody()); }
七、excute()指定调用方式
excute()的用法与exchange()大同小异了,它同样可以指定不同的HttpMethod,不同的是它返回的对象是响应体所映射成的对象,而不是ResponseEntity。需要强调的是,execute()方法是以上所有方法的底层调用。
@Override @Nullable public <T> T postForObject (String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); }
八、上传文件
public static void transFile () { String url = "http://localhost:8080/Lock/nbdc/remoteUpdate" ; RestTemplate restTemplate = new RestTemplate(); FileSystemResource resource = new FileSystemResource(new File("C:\\Users\\Administrator\\Desktop\\40.txt" )); MultiValueMap<String, Object> param = new LinkedMultiValueMap<>(); param.add("deviceId" , "123424" ); param.add("file" , resource); Boolean recv = restTemplate.postForObject(url, param, Boolean.class ) ; System.out.println(recv); }
如果有 @RequestParam 注解,MultiValueMap中的名字必须与它对应,否则请求不到。
如果没有 @RequestParam 注解,MultiValueMap中的名字与它不对应,可以请求到,但是file为null。