Java 自带的 HttpUrlConnection 或者经典的网络访问框架 HttpClient可以实现其他接口的调用,而RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,一种简单的访问restful服务类,用于访问rest服务的客户端模板工具集,在微服务项目中,使用RestTemplate 更方便一些。
RestTemplate RestTemplate 提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。
配置类 1 2 3 4 5 6 7 8 9 10 @Configuration public class ApplicationConfig { @Bean public RestTemplate getRestTemplate () { return new RestTemplate (); } }
Get请求 getForObject 第一个参数是 url ,第二个参数是接口返回的数据类型。返回值就是服务提供者返回的数据,使用 getForObject 无法获取到响应头。
1 2 3 4 5 6 7 8 9 public static final String INVOKE_URL = "http://payment8906" ; @Resource private RestTemplate restTemplate;@GetMapping(value = "/consul") public String getConsulPayment () { return restTemplate.getForObject(INVOKE_URL + "/payment/consul" , String.class); }
另外两个重载方法,在下面的getForEntity 方法说明。
getForEntity RestTemplate 发送的是 HTTP 请求,那么在响应的数据中必然也有响应头,如果开发者需要获取响应头的话,那么就需要使用 getForEntity 来发送 HTTP 请求,此时返回的对象是一个 ResponseEntity 的实例。这个实例中包含了响应数据以及响应头。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @RestController public class HelloController { @GetMapping("/hello") public String sayHello (String name) { return "hello " + name + " !" ; } } @RestController public class UseHelloController { @Autowired DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @GetMapping("/hello") public String hello (String name) { List<ServiceInstance> list = discoveryClient.getInstances("provider" ); ServiceInstance instance = list.get(0 ); String host = instance.getHost(); int port = instance.getPort(); String url = "http://" + host + ":" + port + "/hello?name={1}" ; ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, name); StringBuffer sb = new StringBuffer (); HttpStatus statusCode = responseEntity.getStatusCode(); String body = responseEntity.getBody(); sb.append("statusCode:" ) .append(statusCode) .append("</br>" ) .append("body:" ) .append(body) .append("</br>" ); HttpHeaders headers = responseEntity.getHeaders(); Set<String> keySet = headers.keySet(); for (String s : keySet) { sb.append(s) .append(":" ) .append(headers.get(s)) .append("</br>" ); } return sb.toString(); } }
第一个参数是 url ,url 中有一个占位符 {1} ,如果有多个占位符分别用 {2} 、 {3} … 去表示,第二个参数是接口返回的数据类型,最后是一个可变长度的参数,用来给占位符填值。在返回的 ResponseEntity 中,可以获取响应头中的信息,其中getStatusCode方法用来获取响应状态码,getBody方法用来获取响应数据, getHeaders 方法用来获取响应头,在浏览器中访问该接口。
这里参数的传递除了这一种方式之外,还有另外两种方式,也就是 getForEntity 方法的另外两个重载方法。
第一个是占位符不使用数字,而是使用参数的 key,同时将参数放入到一个 map 中。map 中的 key 和占位符的 key 相对应,map 中的 value 就是参数的具体值,例如还是上面的请求,利用 map 来传递参数,请求方式如下:
1 2 3 4 Map<String, Object> map = new HashMap <>(); String url = "http://" + host + ":" + port + "/hello?name={name}" ;map.put("name" , name); ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, map);
第二个是使用 Uri 对象,使用 Uri 对象时,参数可以直接拼接在地址中,例如下面这样:
1 2 3 String url = "http://" + host + ":" + port + "/hello?name=" + URLEncoder.encode(name,"UTF-8" );URI uri = URI.create(url);ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
这种传参方式,参数如果是中文的话,需要对参数进行编码,使用 URLEncoder.encode 方法来实现。
POST请求 和 GET 请求相比,RestTemplate 中的 POST 请求多了一个类型的方法。
POST请求的方法类型除了 postForEntity 和 postForObject 之外,还有一个 postForLocation。这里的方法类型虽然有三种,但是这三种方法重载的参数基本是一样的,就以 postForEntity 方法为例,说明三个重载方法的用法,最后再重点说下 postForLocation 方法。
postForEntity 在 POST 请求中,参数的传递可以是 key/value 的形式,也可以是 JSON 数据。
传递key/value形式的参数 请求接口
1 2 3 4 @PostMapping("/hello2") public String sayHello2 (String name) { return "Hello " + name + " !" ; }
调用接口
1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("/hello5") public String hello5 (String name) { List<ServiceInstance> list = discoveryClient.getInstances("provider" ); ServiceInstance instance = list.get(0 ); String host = instance.getHost(); int port = instance.getPort(); String url = "http://" + host + ":" + port + "/hello2" ; MultiValueMap map = new LinkedMultiValueMap (); map.add("name" , name); ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, map, String.class); return responseEntity.getBody(); }
postForEntity 方法第一个参数是请求地址,第二个参数 map 对象中存放着请求参数 key/value,第三个参数则是返回的数据类型。当然这里的第一个参数 url 地址也可以换成一个 Uri 对象,效果是一样的。在 post 请求中,也可以按照 get 请求的方式去传递 key/value 形式的参数,传递方式和 get 请求的传参方式基本一致,此时第二个参数可以直接传一个 null。
1 2 3 4 5 6 7 8 9 10 @GetMapping("/hello6") public String hello6 (String name) { List<ServiceInstance> list = discoveryClient.getInstances("provider" ); ServiceInstance instance = list.get(0 ); String host = instance.getHost(); int port = instance.getPort(); String url = "http://" + host + ":" + port + "/hello2?name={1}" ; ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, null , String.class,name); return responseEntity.getBody(); }
传递JSON数据 在 post 请求中,可以自动将一个对象转换成 json 进行传输,数据到达 provider 之后,再被转换为一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Controller @ResponseBody public class UserController { @PostMapping("/user") public User hello (@RequestBody User user) { return user; } } @GetMapping("/hello7") public User hello7 () { List<ServiceInstance> list = discoveryClient.getInstances("provider" ); ServiceInstance instance = list.get(0 ); String host = instance.getHost(); int port = instance.getPort(); String url = "http://" + host + ":" + port + "/user" ; User u1 = new User (); u1.setUsername("牧码小子" ); u1.setAddress("深圳" ); ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, u1, User.class); return responseEntity.getBody(); }
postForLocation postForLocation 方法的返回值是一个 Uri 对象,因为 POST 请求一般用来添加数据,有的时候需要将刚刚添加成功的数据的 URL 返回来,此时就可以使用这个方法,一个常见的使用场景如用户注册功能,用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。
1 2 3 4 5 6 7 8 9 @RequestMapping("/register") public String register (User user) throws UnsupportedEncodingException { return "redirect:/loginPage?username=" + URLEncoder.encode(user.getUsername(),"UTF-8" ) + "&address=" + URLEncoder.encode(user.getAddress(),"UTF-8" ); } @GetMapping("/loginPage") @ResponseBody public String loginPage (User user) { return "loginPage:" + user.getUsername() + ":" + user.getAddress(); }
这里一个注册接口,一个是登录页面,不过这里的登录页面我就简单用一个字符串代替了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping("/hello8") public String hello8 () { List<ServiceInstance> list = discoveryClient.getInstances("provider" ); ServiceInstance instance = list.get(0 ); String host = instance.getHost(); int port = instance.getPort(); String url = "http://" + host + ":" + port + "/register" ; MultiValueMap map = new LinkedMultiValueMap (); map.add("username" , "牧码小子" ); map.add("address" , "深圳" ); URI uri = restTemplate.postForLocation(url, map); String s = restTemplate.getForObject(uri, String.class); return s; }
postForLocation 方法返回的 Uri 实际上是指响应头的 Location 字段,所以,provider 中 register 接口的响应头必须要有 Location 字段(即请求的接口实际上是一个重定向的接口),否则 postForLocation 方法的返回值为null。
1 2 3 4 5 6 7 8 9 10 11 12 13 JSONObject object = new JSONObject ();object.put("id" , id); object.put("name" , name); HttpHeaders headers = new HttpHeaders ();headers.add("token" , "3d40e41e9d764d30a9a4d72e61ad61b9" ); HttpEntity<JSONObject> httpEntity = new HttpEntity <>(object, headers); String url = "http://localhost:8080/testservice/test/get" ;ResponseEntity<JSONObject> result = restTemplate.exchange(requestUrl, HttpMethod.GET, httpEntity, JSONObject.class);
HttpClient Get请求 向指定URL发送GET方法的请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public static String sendGet (String url, String param) { StringBuilder result = new StringBuilder (); BufferedReader in = null ; try { String urlNameString = url + "?" + param; URL realUrl = new URL (urlNameString); URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept" , "*/*" ); connection.setRequestProperty("connection" , "Keep-Alive" ); connection.setRequestProperty("user-agent" , "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" ); connection.connect(); Map<String, List<String>> map = connection.getHeaderFields(); in = new BufferedReader (new InputStreamReader (connection.getInputStream(), StandardCharsets.UTF_8)); String line; while ((line = in.readLine()) != null ) { result.append(line); } } catch (Exception e) { logger.error("发送GET请求出现异常" , e); } finally { try { if (in != null ) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result.toString(); }
单参数数据访问 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public static String sendGet (String url) { StringBuilder result = new StringBuilder (); BufferedReader in = null ; try { URL realUrl = new URL (url); URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept" , "*/*" ); connection.setRequestProperty("connection" , "Keep-Alive" ); connection.setRequestProperty("user-agent" , "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)" ); connection.connect(); Map<String, List<String>> map = connection.getHeaderFields(); in = new BufferedReader (new InputStreamReader (connection.getInputStream(), StandardCharsets.UTF_8)); String line; while ((line = in.readLine()) != null ) { result.append(line); } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } finally { try { if (in != null ) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result.toString(); }
POST请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public static String sendPost (String url, String param) { PrintWriter out = null ; BufferedReader in = null ; StringBuilder result = new StringBuilder (); try { URL realUrl = new URL (url); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept" , "*/*" ); conn.setRequestProperty("connection" , "Keep-Alive" ); conn.setRequestProperty("user-agent" , "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" ); conn.setDoOutput(true ); conn.setDoInput(true ); out = new PrintWriter (conn.getOutputStream()); out.print(param); out.flush(); in = new BufferedReader (new InputStreamReader (conn.getInputStream())); String line; while ((line = in.readLine()) != null ) { result.append(line); } } catch (Exception e) { System.out.println("发送 POST 请求出现异常!" + e); e.printStackTrace(); } finally { try { if (out != null ) { out.close(); } if (in != null ) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result.toString(); }
对象参数数据访问 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public static String doPost (String postUrl, JSONObject obj) { String lines = "" ; StringBuffer sb = new StringBuffer ("" ); try { URL url = new URL (postUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true ); connection.setDoInput(true ); connection.setRequestMethod("POST" ); connection.setUseCaches(false ); connection.setInstanceFollowRedirects(true ); connection.setRequestProperty("Content-Type" , "application/json" ); connection.connect(); DataOutputStream out = new DataOutputStream (connection.getOutputStream()); out.write(obj.toString().getBytes()); out.flush(); out.close(); BufferedReader reader = new BufferedReader (new InputStreamReader (connection.getInputStream(), StandardCharsets.UTF_8)); while ((lines = reader.readLine()) != null ) { lines = new String (lines.getBytes()); sb.append(lines); } System.out.println(sb); reader.close(); connection.disconnect(); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); }