HTTP远程调用

本文介绍了一种在开发中实现服务间通过RESTful接口进行远程调用的方法,包括使用Apache HttpClient库的Maven依赖及具体代码实现,适用于Java开发者理解和实现跨服务通信。

一、基于httpclient实现的远程调用

开发中遇到服务间通过restful接口进行远程调用的情况,可以使用如下的方式进行实现:

 maven依赖如下:

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>

代码实现如下(POST和GET请求): 

package com.shencai.xf.common.util;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;


/**
 * @Description: 描述
 * @Author: xuyahui
 * @Date: 2019/8/13 17:17
 * @Version 1.0
 */
public class HttpUtil {

    private static Logger logger = LoggerFactory.getLogger(HttpUtil.class.getName());

    private static HttpClient httpClient = HttpClientBuilder.create().build();

    public static String httpPost(String url, String json) {
        HttpPost post = new HttpPost(url);
        post.addHeader(HTTP.CONTENT_TYPE, "application/json");
        StringEntity entity = new StringEntity(json, "UTF-8");
        post.setEntity(entity);
        try {
            HttpResponse response = httpClient.execute(post);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                if (response.getEntity() != null) {
                    return EntityUtils.toString(response.getEntity(), "UTF-8");
                }
            }
            throw new IOException("请求出错");
        } catch (IOException e) {
            e.printStackTrace();
            logger.error(e.getMessage());
        }
        return null;
    }

    public static JSONObject httpGet(String url){
        JSONObject jsonObject = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet get = new HttpGet(url);
        try {
            HttpResponse response = httpClient.execute(get);
            HttpEntity entity = response.getEntity();
            String returnString= EntityUtils.toString(entity, "UTF-8");
            EntityUtils.consume(entity);
            jsonObject = JSONObject.parseObject(returnString);
        } catch (IOException e) {
            LOG.error("http请求失败", e);
        }
        return jsonObject;
    }

}

二、基于spring 的 RestTemplate 实现

1. service 类注入 

    @Autowired
    private RestTemplate restTemplate;

2. url 和 参数拼接与调用

String url = String.format("%s?page_size=%d&page_num=%d", UPSTREAM_URL, PAGE_SIZE, pageNum);

ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);

备注:使用的spring版本是  org.springframework:spring-web:5.3.13

三、基于cn.hutool.http实现的远程调用

版本:cn.hutool:hutool-all:5.7.16

1.GET请求

分别是参数在url和参数在body

public static String sendGetRequest(String url, Map<String, String> param){
        log.info("[请求参数]: {}",JSON.toJSONString(param));
        HttpHeaders headers = new HttpHeaders();
        String body = null;

        StringBuffer sb = new StringBuffer(url+"?");
        if(CollectionUtil.isNotEmpty(param)){
            param.entrySet().stream().forEach(v -> sb.append(v.getKey()).append("=").append(v.getValue()).append("&"));
        }
        url = sb.substring(0,sb.toString().length()-1);

        try{
            body = HttpRequest.get(url)
                    .header("Accept", "text/html;charset=UTF-8;")
                    .contentType("application/json")
                    .execute().body();
        }catch (Exception e){
            log.error("sendGetRequest 请求失败!");
        }
        log.info("sendGetRequest结果:{}", body);
//        return JSONObject.parseObject(body,DingTalkTokenVo.class);
        return body;
    }

    public static String sendGetBodyRequest(String url,  Object dto){
        log.info("[sendPostRequest参数入参]: {}", JSON.toJSONString(dto));
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
//        log.info("sendPostRequest请求参数下发]url: {}, header:{}", url, JSON.toJSONString(headers));
        String body = null;
        try {
            body = HttpRequest.get(url)
                    .header(headers)
                    .body(JSON.toJSONString(dto))
                    .execute().body();
        } catch (Exception e) {
            log.error("[POST请求]", e);
        }
        log.info("[sendPostRequest返回值]: {}", body);
        return body;
    }

2.POST请求

public static String sendPostRequest(String url , Object dto){
        log.info("[sendPostRequest参数入参]: {}", JSON.toJSONString(dto));
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
//        log.info("sendPostRequest请求参数下发]url: {}, header:{}", url, JSON.toJSONString(headers));
        String body = null;
        try {
            body = HttpRequest.post(url)
                    .header(headers)
                    .body(JSON.toJSONString(dto))
                    .execute().body();
        } catch (Exception e) {
            log.error("[POST请求]", e);
        }
        log.info("[sendPostRequest返回值]: {}", body);
//        return JSONObject.parseObject(body, DingTalkRespVo.class);
        return body;
    }

3.PUT请求

public String sendPutRequest(String url, Object paramVo) {
        log.info("[sendPutRequest]: {}", JSON.toJSONString(paramVo));
        HttpHeaders headers = new HttpHeaders();
//        String url = mesUpdateProductAttributeUrl + paramVo.getId();
        headers.setContentType(MediaType.APPLICATION_JSON);
        log.info("sendPutRequest]url: {}, header:{}", url, JSON.toJSONString(headers));
        String body = null;
        try {
            body = HttpRequest.put(url)
                    .header(headers)
                    .body(JSON.toJSONString(paramVo))
                    .execute().body();
        } catch (Exception e) {
            log.error("[sendPutRequest]", e);
        }
        log.info("[sendPutRequest]: {}", body);
        return body;
    }

4.DELETE请求

public String sendDeleteRequest(String url, Object data) {
        log.info("[sendDeleteRequest]: {}", JSON.toJSONString(data));
        HttpHeaders headers = new HttpHeaders();
//        String url = mesUpdateProductAttributeUrl + id;
        headers.setContentType(MediaType.APPLICATION_JSON);
        log.info("sendDeleteRequest]url: {}, header:{}", url, JSON.toJSONString(headers));
        String body = null;
        try {
            body = HttpRequest.delete(url)
                    .header(headers)
                    .execute().body();
        } catch (Exception e) {
            log.error("[sendDeleteRequest]", e);
        }
        log.info("[sendDeleteRequest]: {}", body);
//        return JSONObject.parseObject(body, DingTalkRespVo.class);
        return body;
    }

这里远程调用的方法默认返回值都是String类型,具体调用的Service类里面根据需要把数据进行转换。具体转换如下:

// 转为对象
JSONObject.parseObject(body, DingTalkRespVo.class);

// 转为对象集合
List<ExtProjectCompletePlan> plans = JSONArray.parseArray(dataJson, ExtProjectCompletePlan.class);

// 解析响应
DigitalTwinApiResponse apiResponse = JSON.parseObject(response.getBody(), DigitalTwinApiResponse.class);

四、大数据量情况下的分页远程调用和存储实现方案

整体思路:首先被调用的接口要支持分页查询,然后传入分页参数基于for循环调用,查询到数据之后就进行存储或进行缓存,退出循环的条件就是当前调用获取到数据为空。

具体实现代码如下:

public void syncDataFromHWCloud(String apiUrl) {
        int totalSynced = 0;
        int pageNum = 1;
        int offsetValue = 0;
        try {
            
            // 1. 清空旧数据(根据业务需求决定是否保留)
            // planService.truncateTable();
            this.removeAll();

            // 2. 循环获取所有分页数据
            boolean hasMoreData = true;
            List<ExtProjectCompletePlan> plans;
            while (hasMoreData) {
                offsetValue = PageUtils.calculateOffset(pageNum,PAGE_SIZE);
                String url = String.format("%s?limitValue=%d&offsetValue=%d", apiUrl, PAGE_SIZE, offsetValue);
                ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);

                // 3. 解析响应
                UpstreamApiResponse apiResponse = JSON.parseObject(response.getBody(), UpstreamApiResponse.class);

                if (apiResponse.getData() == null || !apiResponse.getData().getSuccess()) {
                    hasMoreData = false;
                    break;
                }

                // 4. 解析实际数据
                String dataJson = apiResponse.getData().getData();
                plans = JSONArray.parseArray(dataJson, ExtProjectCompletePlan.class);

                if (plans == null || plans.isEmpty()) {
                    hasMoreData = false;
                    break;
                }

                this.saveBatch(plans);

                // 5. 保存当前页数据
                totalSynced += plans.size();
                pageNum++;

                // 6. 简单限流,避免请求过快(可根据实际情况调整)
                Thread.sleep(200);
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

        

### 将HTTP远程调用转换为本地RPC调用的实现方式 在分布式系统中,HTTP远程调用和本地RPC调用是两种常见的服务间通信方式。HTTP调用通常基于RESTful API或GraphQL等协议,而本地RPC调用则通过序列化和反序列化技术直接调用远程方法。为了将HTTP远程调用转换为本地RPC调用,需要对以下关键点进行设计和实现: #### 1. **序列化与反序列化** RPC调用的核心在于将方法调用及其参数序列化为字节流,通过网络传输到服务提供者,并在接收端反序列化为可执行的对象。常用的序列化框架包括Hessian、Protobuf、Thrift等[^2]。 #### 2. **代理模式** 使用动态代理技术可以屏蔽底层的网络通信细节,使得服务消费者像调用本地方法一样调用远程服务。例如,Java中的`InvocationHandler`接口可以用于创建代理对象[^3]。 #### 3. **注册中心** 在本地RPC调用中,服务提供者需要向注册中心注册其地址和服务信息,服务消费者通过注册中心获取服务提供者的地址并发起调用。这一步骤类似于HTTP调用中的服务发现机制[^1]。 #### 4. **示例代码** 下面是一个简单的本地RPC调用实现示例,使用Java语言和动态代理技术: ```java // 定义服务接口 public interface HelloService { String sayHello(String name); } // 服务提供者实现类 public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "Hello, " + name; } } // 动态代理实现类 public class RpcProxy<T> implements InvocationHandler { private T target; public RpcProxy(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取方法名和参数类型 String methodName = method.getName(); Class<?>[] parameterTypes = method.getParameterTypes(); // 序列化参数 String serializedArgs = JSON.toJSONString(args); // 模拟网络调用(实际场景中需要替换为真实的网络通信逻辑) Result result = callRemoteService(target.getClass().getName(), methodName, serializedArgs); // 反序列化返回值 if (result.isSuccess()) { return JSON.parseObject(result.getResultValue(), Class.forName(result.getResultType())); } else { throw new Exception("远程调用异常:" + result.getMessage()); } } private Result callRemoteService(String className, String methodName, String args) { // 模拟远程调用结果 Result result = new Result(); result.setResultValue("{\"result\":\"模拟返回值\"}"); result.setResultType("java.lang.String"); result.setSuccess(true); return result; } } // 测试代码 public class Main { public static void main(String[] args) { // 创建服务提供者实例 HelloService helloService = new HelloServiceImpl(); // 创建动态代理 HelloService proxyService = (HelloService) Proxy.newProxyInstance( HelloService.class.getClassLoader(), new Class<?>[]{HelloService.class}, new RpcProxy<>(helloService) ); // 调用远程方法 String response = proxyService.sayHello("World"); System.out.println(response); } } ``` #### 5. **实现方案总结** - **序列化与反序列化**:选择合适的序列化框架以提高性能和兼容性。 - **动态代理**:通过`InvocationHandler`屏蔽底层通信细节,使调用方无需关心网络通信逻辑。 - **注册中心**:引入如Zookeeper、Eureka等注册中心以支持服务发现和负载均衡。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值