SpringBoot访问外部接口的7种方式
在日常的开发工作中,我们不可避免的会需要访问外部模块的接口,或者外部的url链接,来实现某些操作。这个时候,就需要有一些机制来支持我们功能的开发,例如我们熟悉的FeignClient、RestTemplate、HttpClient不同的机制,都可以实现。但是这些实现方式又不仅限于这三种方式,还存在其他的处理方式,这些方式各有不同的优点和适用场景,了解他们可以有助于在我们开发中提供更多的选择。接下来
原创 Hey 技术小白逆袭指南 2023-11-07 09:00 发表于北京
前言
在日常的开发工作中,我们不可避免的会需要访问外部模块的接口,或者外部的url链接,来实现某些操作。
这个时候,就需要有一些机制来支持我们功能的开发,例如我们熟悉的FeignClient、RestTemplate、HttpClient不同的机制,都可以实现。
但是这些实现方式又不仅限于这三种方式,还存在其他的处理方式,这些方式各有不同的优点和适用场景,了解他们可以有助于在我们开发中提供更多的选择。
接下来,我们逐一介绍下。
1.使用FeignClient调用
Feign是一款轻量级的HTTP服务客户端远程调用框架,相比较于传统上的HTTP封装接口,它以Java接⼝注解的⽅式调⽤HTTP请求,使服务间的调用变的简单。
示例代码:
1、在使用方引入依赖:
<!-- Feign注解 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<!-- <version>4.0.1</version> -->
</dependency>
注意:这里openFeign的版本要和自己使用的SpringBoot匹配
2、在启动类上加上@EnableFeignClients注解
//启动类
@SpringBootApplication
@EnableFeignClients
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3、编写Feign客户端
@FeignClient(name = "masterdata",url = "${masterdata-service-url}")
public interface ISysUserClient {
@GetMapping(value = "/masterdata/getSysUserById")
public Map getSysUserById(@RequestParam("userId") String userId);
}
@FeignClient注解的常用属性说明:
-
name:指定Feign的名称,如果使用了注册中心,name属性会作为微服务的名称,用于服务发现
-
url:Feign调用的跳转路径,可以在配置文件中设置,多用于代码调试
具体实现细节,感兴趣的小伙伴,可以详见《Feign服务的介绍和使用》
2.使用RestTemplate调用
对于有FeignClient客户端的接口,我们可以使用Feign调用,那如果没有呢?就提供一个url路径,我们如何访问呢?这是我在开发过程中遇到的问题,我们可以使用RestTemplate调用。
RestTemplate中几个常用的方法:getForObject()、getForEntity()、postForObject()、postForEntity()。其中,getForObject() 和 getForEntity() 方法可以用来发送 GET 请求。
示例代码:
1、引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
虽然SpringBoot会自动的装配很多常见的bean,但是RestTemplate,我们需要显示的配置它。
2、RestTemplateConfig配置类
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//单位为ms
factory.setConnectTimeout(5000);//单位为ms
return factory;
}
}
SimpleClientHttpRequestFactory类对应的HTTP库是JDK自带的HttpUrlConnection,当然我们可以根据自身的需求使用其他的HTTP库,例如HttpComponentsAsyncClientHttpRequestFactory。
3、测试代码:
@RestController
public class TestRestTemplate {
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/saveUser")
public void saveUser(String userId) {
String url = "http://127.0.0.1:8094/masterdata/sysUser/saveUser";
Map map = new HashMap<>();
map.put("name", "郭郭");
String results = restTemplate.postForObject(url, map, String.class);
}
}
服务端代码:
@RestController
public class SysUserController {
@PostMapping(value = "/sysUser/saveUser")
public String saveUser(@RequestBody Map map) {
return "人员新增成功";
}
}
这里我们使用map.put("name", "郭郭");直接给入参赋值,另外,我们还可以通过HttpHeader增加一些请求头的属性,例如请求头格式,或者一些需要用户认证的信息。
@GetMapping(value = "/saveUser")
public void saveUser(String userId) {
String url = "http://127.0.0.1:8094/masterdata/sysUser/saveUser";
Map map = new HashMap<>();
map.put("name", "郭郭");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity httpEntity = new HttpEntity(map, httpHeaders);
String results = restTemplate.postForObject(url, httpEntity, String.class);
}
postForObject方法中的第三个属性String.class是响应数据的类型,这个要根据自身代码的接口,正确书写,否则会报错。
3.使用WebClient调用
Spring3.0引入了RestTemplate,但是在后来的官方源码中介绍,RestTemplate有可能在未来的版本中被弃用,所谓替代RestTemplate,在Spring5中引入了WebClient作为异步的非阻塞、响应式的HTTP客户端。
我们拿GET、POST请求举例。
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
2、服务端代码:
@RestController
public class SysUserController {
@PostMapping(value = "/sysUser/saveUser")
public String saveUser(@RequestBody Map map) {
return "人员新增成功";
}
@GetMapping(value = "/sysUser/getSysUserById")
public String getSysUserById(String userId) {
return "郭郭";
}
}
3、WebClient示例代码:
public class TestWebClient {
@Test
public void doGet() {
String userId = "郭郭";
String url = "http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId={userId}";
Mono<String> mono = WebClient
//创建WebClient实例
.create()
//方法调用,WebClient中提供了多种方法
.get()
//请求url
.uri(url, userId)
//获取响应结果
.retrieve()
//将结果转换为指定类型
.bodyToMono(String.class);
//返回最终结果:block是阻塞的/subscribe()非阻塞式获取响应结果
System.out.println("响应结果:" + mono.block());
}
@Test
public void doPost() {
Map map = new HashMap<>();
map.put("name", "郭郭");
String requestBody = JSON.toJSONString(map);
String url = "http://127.0.0.1:8094/masterdata/sysUser/saveUser";
Mono<String> mono = WebClient
//创建WebClient实例
.create()
//方法调用,WebClient中提供了多种方法
.post()
//请求url
.uri(url)
//指定请求的Content-Type为JSON
.contentType(MediaType.APPLICATION_JSON)
//使用bodyValue方法传递请求体
.bodyValue(requestBody)
//获取响应结果
.retrieve()
//将结果转换为指定类型
.bodyToMono(String.class);
//返回最终结果:block是阻塞的/subscribe()非阻塞式获取响应结果
System.out.println("响应结果:" + mono.block());
}
}
在上述doPost请求中,我们的请求接口入参是一个Map,但是需要转换为JSON格式传递,这是因为WebClient默认是使用JSON序列化的。
4.使用Apache HttpClient调用
Apache HttpClient 是 Apache 提供的一个同步的 HTTP 客户端库,它提供了丰富的 API,可以很方便地发送 HTTP 请求和获取 HTTP 响应。
我们拿GET、POST请求举例。
示例代码:
public class TestHttpClient {
@Test
public void doGet() throws IOException {
//步骤一:创建httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
//步骤二:创建HTTP请求
HttpGet httpGet = new HttpGet("http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=郭郭");
//步骤三:发送请求并获取响应数据
CloseableHttpResponse response = httpClient.execute(httpGet);
//步骤四:处理响应数据
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
//步骤五:关闭httpClient和response
response.close();
httpClient.close();
}
@Test
public void doPost() throws IOException {
//步骤一:创建httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
//步骤二:创建HTTP请求
HttpPost httpPost = new HttpPost("http://127.0.0.1:8094/masterdata/sysUser/saveUser");
//步骤三:设置请求体数据,使用JSON格式
Map map = new HashMap<>();
map.put("name", "郭郭");
String requestBody = JSON.toJSONString(map);
StringEntity stringEntity = new StringEntity(requestBody, "UTF-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
//步骤四:发送请求并获取响应数据
CloseableHttpResponse response = httpClient.execute(httpPost);
//步骤五:处理响应数据
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
//步骤五:关闭httpClient和response
response.close();
httpClient.close();
}
}
服务端代码:
@RestController
public class SysUserController {
@PostMapping(value = "/sysUser/saveUser")
public String saveUser(@RequestBody Map map) {
return "人员新增成功";
}
@GetMapping(value = "/sysUser/getSysUserById")
public String getSysUserById(String userId) {
return "郭郭";
}
}
上述doPost方法中,我们使用了map转String,使用了JSON,这里引入JSON包即可。
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.25</version>
</dependency>
5.使用HttpURLConnection调用
HttpURLConnection 是 Java 自带的一个 HTTP 客户端工具,可以发送 HTTP 请求和接收 HTTP 响应。
我们拿GET、POST请求举例。
示例代码:
public class TestHttpURLConnection {
@Test
public void doGet() throws IOException {
String userId = "郭郭"; // 参数值
userId = URLEncoder.encode(userId, "UTF-8"); // 对参数值进行URL编码
//步骤一:创建URL对象
URL url = new URL("http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=" + userId);
//步骤二:打开连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//步骤三:设置请求方式
conn.setRequestMethod("GET");
//步骤四:读取响应内容
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
System.out.println(sb.toString());
}
@Test
public void doPost() throws IOException {
//创建URL对象
URL url = new URL("http://127.0.0.1:8094/masterdata/sysUser/saveUser");
//打开连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置请求方式
conn.setRequestMethod("POST");
// 设置请求头
conn.setRequestProperty("Content-Type", "application/json");
//启用输出流
conn.setDoOutput(true);
//设置请求体数据
Map map = new HashMap<>();
map.put("name", "郭郭");
String requestBody = JSON.toJSONString(map);
//发送请求体数据
try (DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream())) {
outputStream.write(requestBody.getBytes(StandardCharsets.UTF_8));
}
//读取响应内容
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
System.out.println(sb.toString());
}
}
服务端代码:
@RestController
public class SysUserController {
@PostMapping(value = "/sysUser/saveUser")
public String saveUser(@RequestBody Map map) {
return "人员新增成功";
}
@GetMapping(value = "/sysUser/getSysUserById")
public String getSysUserById(String userId) {
return "郭郭";
}
}
从上述简单的demo中,我们不难发现,doPost请求比doGet请求多了一些逻辑,比如我们设置了conn.setDoOutPut,这用于启动输出流。
为什么要设置conn.setDoOutPut呢?
当您发送HTTP POST请求时,通常需要将数据发送到服务器,这些数据包含在请求体中。通过调用 setDoOutput(true),您告诉连接对象您将使用输出流来发送数据,这样它会准备好接受输出流,并将数据发送到服务器。这是发送POST请求体数据所必需的步骤。
另一方面,对于HTTP GET请求,通常不需要发送请求体数据,因此不需要设置 setDoOutput(true)。
而post请求的入参,我们是放入DataOutputStream进行传递,这里我们使用try语句块来包裹DataOutputStream,是因为DataOutputStream实现了AutoCloseable接口,因此它会在try块结束的时候自动关闭。
我们介绍了HttpURLConnection,还有一种调用方式是URLConnection,它们是什么关系呢?
通过查看源码,我们不难发现,HttpURLConnection继承自URLConnection,是它的一个子类,而HttpURLConnection专门用于处理HTTP协议的连接,如果需要处理其他协议,我们可以考虑使用通用的URLConnection。
6.使用OkHttp调用
OkHttp是一款高效的HTTP客户端框架,经过优化,具有低内存占有和出色的性能。
1、引入依赖
<!--okhttp依赖-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.0.0</version>
</dependency>
2、示例代码:
public class TestOkHttp {
@Test
public void doGet() throws IOException {
OkHttpClient client = new OkHttpClient();
String url = "http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=郭郭";
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
ResponseBody body = response.body();
System.out.println(body.string());
}
}
@Test
public void doPost() throws IOException{
OkHttpClient client = new OkHttpClient();
String url = "http://127.0.0.1:8094/masterdata/sysUser/saveUser";
MediaType mediaType = MediaType.get("application/json; charset=utf-8");
//requestBody请求入参
Map map = new HashMap<>();
map.put("name", "郭郭");
RequestBody requestBody = RequestBody.create(mediaType, JSON.toJSONString(map));
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
try (Response response = client.newCall(request).execute()) {
ResponseBody body = response.body();
System.out.println(body.string());
}
}
}
服务端给上述WebClient使用的一样,此处省略。。。
doGet方法中,我们没有指定请求方式,因为Request默认就是get请求,所以构造的时候,可以省略。
7.使用AsyncHttpClient调用
AsyncHttpClient是一个支持异步HTTP请求的开源库,用于非阻塞I/O操作,适用于需要高并发,非阻塞的应用。
1、引入依赖
<dependency>
<groupId>org.asynchttpclient</groupId>
<artifactId>async-http-client</artifactId>
<version>2.12.3</version>
</dependency>
2、示例代码:
public class TestAsyncHttpClient {
@Test
public void doGet() throws IOException {
try (AsyncHttpClient client = new DefaultAsyncHttpClient();) {
BoundRequestBuilder requestBuilder = client.prepareGet("http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=郭郭");
CompletableFuture<String> future = requestBuilder.execute()
.toCompletableFuture()
.thenApply(Response::getResponseBody);
//使用join等待响应完成
String responseBody = future.join();
System.out.println(responseBody);
}
}
@Test
public void doPost() throws IOException {
try (AsyncHttpClient client = new DefaultAsyncHttpClient();) {
BoundRequestBuilder requestBuilder = client.preparePost("http://127.0.0.1:8094/masterdata/sysUser/saveUser");
//requestBody请求入参
Map map = new HashMap<>();
map.put("name", "郭郭");
String requestBody = JSON.toJSONString(map);
requestBuilder.addHeader("Content-Type", "application/json");
requestBuilder.setBody(requestBody);
CompletableFuture<String> future = requestBuilder.execute()
.toCompletableFuture()
.thenApply(Response::getResponseBody);
//使用join等待响应完成
String responseBody = future.join();
System.out.println(responseBody);
}
}
}
服务端给上述WebClient使用的一样,此处省略。。。
我们看到doGet、doPost方法都使用了try代码块,对AsyncHttpClient进行包裹,同理因为继承了AutoCloseable,为了自动调用close关闭功能。
我们在上述文章中,提到过的WebClient,也是支持异步,适用于高并发,非阻塞的场景,那么它们二者有什么区别呢?
通过上网查阅资料,得知,AsyncHttpClient是一个独立的开源库,它不依赖于任何的框架或者技术栈,而WebClient是Spring Framework的一部分,如果你正在构建Spring应用程序,那么WebClient可能是更好的选择。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)