Spring MVC Rest 客户端 RestTemplate 详解

目录

RestTemplate Rest 模板概述

Http GET 请求

Http Post 请求

RestTemplate 超时时间

切换 OkHttpClient 客户端


RestTemplate Rest 模板概述

带负载均衡(@LoadBalanced)的 RestTemplate 必须使用微服务名称发起请求,不能使用 ip:port
不带负载均衡(@LoadBalanced)的 RestTemplate 不能使用微服务名称发起请求,只能使用 ip:port

1、org.springframework.web.client.RestTemplate 类是 spring-web-x.x.x.RELEASE.jar 包下进行 HTTP 访问的 REST 客户端核心类。

2、Java 应用后台代码中如果想要向另外一个 Java 应用发起 Http 请求,通常使用 Apache 的 HttpClient 库来做。而 spring cloud 微服务架构中,各个微服务之间会频繁的发起 http 请求进行通信,所以 spring 对 http 请求进行了封装,以便请求使用起来更加方便。

3、RestTemplate 类能够以 rest 风格的方式,以 GET, POST, PUT, DELETE, HEAD, OPTIONS 等不同的方式向服务器发起HTTP 请求。

4、RestTemplate 对 http 的封装类似 JdbcTemplate 对 jdbc 的封装。本文环境:Java jdk 8 + Spring boot 2.1.3(spring 5.1.5)。

构造函数 描述
RestTemplate() 默认使用 org.springframework.http.client.SimpleClientHttpRequestFactory 客户端,底层使用 jdk 标准的 API,即 java.net 包下的 API,如 java.net.HttpURLConnection 等。
RestTemplate(ClientHttpRequestFactory requestFactory) ClientHttpRequestFactory 接口的实现类给出底层实现的第三方 HTTP 客户端软件。如 OkHttp3Client、以及 RibbonClient 等
RestTemplate(List<HttpMessageConverter<?>> messageConverters) HttpMessageConverter 接口的实现对象能够在HTTP消息与Java POJO之间进行数据转换。

5、使用 RestTemplate 非常简单,对于 Spring boot、Spring Cloud 项目,spring-boot-starter-web 组件内部已经包含了 spring-web-x.x.x.RELEASE.jar ,所以只需要将 RestTemplate 实例交由 Spring 容器管理,然后在需要的地方获取使用即可。

//可以直接在启动类(@SpringBootApplication) 中进行添加,或者在 @Configuration 标注的类中添加
@Bean
public RestTemplate restTemplate() {
	//将 RestTemplate 实例交由 Spring 容器管理
	return new RestTemplate();
}
//在使用 RestTemplate 的 Controller 或者其它地方自动注入,然后使用
@Resource
private RestTemplate restTemplate;

Http GET 请求

方法 描述
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) url:请求的地址,responseType:返回的数据类型,ResponseEntity 对象包含了http响应头信息、响应正文、响应状态等信息
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) uriVariables:用于设置查询参数的可变值,map 的 key 值对于url中设置的可变参数值.
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) uriVariables:专门用于处理 url 路径中的参数 @PathVariable
<T> T getForObject(URI url, Class<T> responseType) getForObject 方法是重载的方法,区别在于返回值直接是响应正文,不含响应头信息以及响应状态.
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)  
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables)  

getForEntity(URI url, Class<T> responseType)

     /** 接口提供方
     * http://localhost:8080/getInfo?uid=3030&agencyId=20807
     * @param uid
     * @param agencyId
     * @return
     */
    @GetMapping("getInfo")
    public String getInfo(@RequestParam String uid, String agencyId) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 200);
        jsonObject.put("uid", uid);
        jsonObject.put("agencyId", agencyId);
        return jsonObject.toString();
    }
//接口调用方
@GetMapping("test1")
public String test1() {
	String body = null;
	try {
		URI uri = new URI("http://localhost:8080/getInfo?uid=3030&agencyId=20807");
		ResponseEntity<String> forEntity = restTemplate.getForEntity(uri, String.class);
		//http响应状态码,如成功时返回 200
		int statusCodeValue = forEntity.getStatusCodeValue();
		//http响应的正文内容,即对方返回的数据,如 {"uid":"3030","code":200,"agencyId":"20807"}
		body = forEntity.getBody();
		//http响应的头信息,如 Content-Type=[text/plain;charset=UTF-8], Content-Length=[44], Date=[Tue, 24 Mar 2020 12:45:43 GMT]}
		HttpHeaders headers = forEntity.getHeaders();
		System.out.println("headers=" + headers + "\nstatusCodeValue=" + statusCodeValue + "\nbody=" + body);
	} catch (URISyntaxException e) {
		e.printStackTrace();
	}
	return body;
}

getForEntity(String url, Class<T> responseType, Object... uriVariables)

 /**
 * 接口提供方
 * http://localhost:8080/getData
 * @param uid
 * @param agencyId
 * @return
 */
@GetMapping("getData/{agencyId}/{uid}")
public List<Map<String, Object>> getData(@PathVariable String uid, @PathVariable String agencyId, String info) {
	List<Map<String, Object>> dataMapList = new ArrayList<>(8);
	Map<String, Object> map = new HashMap<>(8);
	map.put("code", 200);
	map.put("uid", uid);
	map.put("info", info);
	map.put("agencyId", agencyId);
	dataMapList.add(map);
	return dataMapList;
}
//接口调用方
@GetMapping("test2")
public List<Map<String, Object>> test2() {
	String uri = "http://localhost:8080/getData/{agency_id}/{u_id}?info=长沙";
	//返回数据类型为 List 类型
	ResponseEntity<List> forEntity = restTemplate.getForEntity(uri, List.class, 20792, "999UP000");
	//http响应状态码,如成功时返回 200
	int statusCodeValue = forEntity.getStatusCodeValue();
	//http响应的正文内容,即对方返回的数据,如 {"uid":"3030","code":200,"agencyId":"20807"}
	List<Map<String, Object>> body = forEntity.getBody();
	//http响应的头信息,如 Content-Type=[text/plain;charset=UTF-8], Content-Length=[44], Date=[Tue, 24 Mar 2020 12:45:43 GMT]}
	HttpHeaders headers = forEntity.getHeaders();
	System.out.println("headers=" + headers + "\nstatusCodeValue=" + statusCodeValue + "\nbody=" + body);
	return body;
}

getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

/**
 * 接口提供方
 * http://localhost:8080/getMessage
 *
 * @param uid
 * @param agencyId
 * @return
 */
@GetMapping("getMessage")
public JSONObject getMessage(String uid, String agencyId) {
	List<Map<String, Object>> dataMapList = new ArrayList<>(8);
	Map<String, Object> map = new HashMap<>(8);
	JSONObject jsonObject = new JSONObject();
	jsonObject.put("code", 200);
	jsonObject.put("uid", uid);
	jsonObject.put("agencyId", agencyId);
	return jsonObject;
}
//接口调用方
@GetMapping("test3")
public JSONObject test3() {
	String uri = "http://localhost:8080/getMessage?uid={uid}&agencyId={agency_id}";
	Map<String, Object> paramMap = new HashMap<>(4);
	paramMap.put("uid", "9998UYT9");
	paramMap.put("agency_id", 20802);
	//getForObject 返回数据类型为 JSONObject 类型的响应内容,即直接获取返回值.
	JSONObject body = restTemplate.getForObject(uri, JSONObject.class, paramMap);

	System.out.println(body);
	return body;
}

Http Post 请求

方法 描述
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)

url:请求的地址;

request:请求的正文(body)内容,即对方 @RequestBody 参数的值,可以为 null。

responseType:返回的数据类型,

ResponseEntity 对象包含了http响应头信息、响应正文、响应状态等信息

<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Map<String, ?> uriVariables) uriVariables:用于设置查询参数的可变值,map 的 key 值对于url中设置的可变参数值.
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Object... uriVariables) uriVariables:专门用于处理 url 路径中的参数 @PathVariable
T postForObject(URI url, @Nullable Object request, Class<T> responseType) getForObject 方法是重载的方法,区别在于返回值直接是响应正文,不含响应头信息以及响应状态.
T postForObject(String url, @Nullable Object request, Class<T> responseType,Map<String, ?> uriVariables)  
T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables)  

postForEntity(String url, @Nullable Object request,Class<T> responseType, Map<String, ?> uriVariables)

/**接口提供方
 * http://localhost:8080/postInfo?uid=44KKP990&agencyId=208071
 *
 * @param uid
 * @param agencyId
 * @return
 */
@PostMapping("postInfo")
public JSONObject postInfo(@RequestParam String uid, String agencyId) {
	JSONObject jsonObject = new JSONObject();
	jsonObject.put("code", 200);
	jsonObject.put("uid", uid);
	jsonObject.put("agencyId", agencyId);
	return jsonObject;
}
//接口调用方
@GetMapping("test11")
public JSONObject test11() {
	JSONObject body = null;
	String uri = "http://localhost:8080/postInfo?uid={uid}&agencyId=208071";
	Map<String, String> paramMap = new HashMap<>(4);
	paramMap.put("uid", "8899HG9");
	ResponseEntity<JSONObject> forEntity = restTemplate.postForEntity(uri, null, JSONObject.class, paramMap);
	//http响应状态码,如成功时返回 200
	int statusCodeValue = forEntity.getStatusCodeValue();
	//http响应的正文内容,即对方返回的数据,如 {"uid":"3030","code":200,"agencyId":"20807"}
	body = forEntity.getBody();
	//http响应的头信息,如 Content-Type=[text/plain;charset=UTF-8], Content-Length=[44], Date=[Tue, 24 Mar 2020 12:45:43 GMT]}
	HttpHeaders headers = forEntity.getHeaders();

	System.out.println("headers=" + headers + "\nstatusCodeValue=" + statusCodeValue + "\nbody=" + body);
	return body;
}

postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables)

/**
 * 接口提供方
 * http://localhost:8080/postData/{agencyId}
 */
@PostMapping("postData/{agencyId}")
public List<Map<String, Object>> postData(@PathVariable String agencyId, @RequestBody List<String> ids) {
	List<Map<String, Object>> dataMapList = new ArrayList<>(8);
	Map<String, Object> map = new HashMap<>(8);
	map.put("code", 200);
	map.put("uid", ids.toArray());
	map.put("agencyId", agencyId);
	dataMapList.add(map);
	return dataMapList;
}
//接口调用方
@GetMapping("test22")
public List<Map<String, Object>> test22() {
	String uri = "http://localhost:8080/postData/{agencyId}";
	List<String> ids = new ArrayList<>(4);
	ids.add("3000L");
	ids.add("99UH7");
	ids.add("99JHG");
	//返回数据类型为 List 类型
	List<Map<String,Object>> body = restTemplate.postForObject(uri, ids, List.class, 20792);
	return body;
}

//其余操作都是同理

RestTemplate 超时时间

1、new RestTemplate() 时默认使用 SimpleClientHttpRequestFactory 工厂,它底层默认使用 Java 原生的 HttpURLConnection API 做 Http 请求,默认 connectTimeout = -1(连接超时),readTimeout = -1(读取超时)都没有做限制,即一直等待。

2、显然无限期的等待是绝不允许的,必须设置超时时间,以 SimpleClientHttpRequestFactory 工厂为例:

    //设置 http 连接工厂,然后将 RestTemplate 实例交由 Spring 容器管理
    @Bean
    public RestTemplate restTemplate() {
        //先设置 http 连接工厂的连接超时时间为 30 秒,读取超时时间为 120 秒
        //请求发送后,对方数据量比较大,需要长时间处理数据时,读取超时时间需要设置的长一些,否则对方还未处理完,连接就超时了。
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(30 * 1000);
        requestFactory.setReadTimeout(120 * 1000);
        return new RestTemplate(requestFactory);
    }

3、验证非常简单,请求 youtube ,铁定连不上而会超时,然后抛异常: java.net.SocketTimeoutException: Read timed out

String body = restTemplate.getForObject("https://ca.youtube.com/", String.class);

切换 OkHttpClient 客户端

1、ClientHttpRequestFactory 有多个实现类,okhttp3 作为现在最热门 http 客户端,这里就以 OkHttpClient 为例。

2、第一步项目中引入 OkHttpClient 的依赖,否则会报:java.lang.NoClassDefFoundError: okhttp3/OkHttpClient

        <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.14.5</version>
        </dependency>

版本不能随意引,org.springframework.http.client.OkHttp3ClientHttpRequestFactory 类的源码上面通常有提示说明对应的OkHttp 版本,如本文 spring-web-5.0.7.RELEASE.jar 提示的 OkHttp 版本是 3.X,如果引入 4.X 则启动会失败。

3、接下来就是创建 RestTemplate 对象的时候,传入 OkHttp3ClientHttpRequestFactory 工厂即可:

    //设置 http 连接工厂,然后将 RestTemplate 实例交由 Spring 容器管理
    @Bean
    public RestTemplate restTemplate() {
        //先设置 http 连接工厂的连接超时时间为 30 秒,读取超时时间为 120 秒
        OkHttp3ClientHttpRequestFactory okRequestFactory = new OkHttp3ClientHttpRequestFactory();
        okRequestFactory.setConnectTimeout(30 * 1000);
        okRequestFactory.setReadTimeout(120 * 1000);
        return new RestTemplate(okRequestFactory);
    }

验证底层到底用的什么 http 客户端也很简单,仍然请求 youtube(https://ca.youtube.com) ,连接超时时控制层会打印异常,其中就可以看到  okhttp3.internal.xxxx 的错误信息。

4、其它如  Apache 的 HttpClient、以及 Netty、RibbonClient 等 http 客户端使用都是同理。

Ribbon 的负载均衡就是在 RestTemplate 上加上 @LoadBalanced,可以参考 Netflix Ribbon 负载均衡

扩展内容可以继续参考:https://blog.csdn.net/f641385712/article/details/100713622

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章