使用 OkHttp 与 FastJSON 发送 POST 请求

目录

  • 前言
    • 一、依赖配置
      • Maven 依赖配置
      • Gradle 依赖配置
    • 二、常量定义
    • 三、学生对象定义
    • 四、常规情况:发送单个学生信息
      • 代码实现
      • 代码解释
    • 五、多线程情况:发送多个学生信息
      • 线程池基础知识
      • 代码实现
      • 代码解释
    • 六、注意事项和优化建议
      • 常规情况
      • 异步请求处理
      • 多线程情况

前言

在现代 Java 开发中,与外部服务进行数据交互是极为常见的需求。其中,发送 HTTP POST 请求以提交数据更是屡见不鲜。OkHttp 作为一款高效且功能强大的 HTTP 客户端库,以及 FastJSON 作为阿里巴巴开源的高性能 JSON 处理库,二者的结合能为我们提供便捷、高效的数据交互解决方案。

一、依赖配置

在开始编写代码之前,我们需要为项目添加必要的依赖。以下分别展示 Maven 和 Gradle 两种构建工具的配置方法。

Maven 依赖配置

若使用 Maven 项目,在 pom.xml 文件中添加以下依赖:

<dependencies>
    
    <dependency>
        <groupId>com.squareup.okhttp3groupId>
        <artifactId>okhttpartifactId>
        <version>4.10.0version>
    dependency>
    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>2.0.33version>
    dependency>
dependencies>

Gradle 依赖配置

若使用 Gradle 项目,在 build.gradle 文件中添加如下依赖:

dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.10.0'
    implementation 'com.alibaba:fastjson:2.0.33'
}

二、常量定义

为提高代码的可维护性,将一些常量单独定义在一个类中。

public class Constants {
    // 定义 API 的 URL,方便后续修改和维护
    public static final String API_URL = "https://example.com/api/students";
    // 定义 JSON 数据的媒体类型,指定字符编码为 UTF - 8
    public static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
}

三、学生对象定义

定义 Student 类来表示学生信息。

public class Student {
    // 学生姓名
    private String name;
    // 学生年龄
    private int age;
    // 学生学号
    private String studentId;

    // 构造函数,用于初始化学生对象
    public Student(String name, int age, String studentId) {
        this.name = name;
        this.age = age;
        this.studentId = studentId;
    }

    // 获取学生姓名
    public String getName() {
        return name;
    }

    // 设置学生姓名
    public void setName(String name) {
        this.name = name;
    }

    // 获取学生年龄
    public int getAge() {
        return age;
    }

    // 设置学生年龄
    public void setAge(int age) {
        this.age = age;
    }

    // 获取学生学号
    public String getStudentId() {
        return studentId;
    }

    // 设置学生学号
    public void setStudentId(String studentId) {
        this.studentId = studentId;
    }
}

四、常规情况:发送单个学生信息

代码实现

import com.alibaba.fastjson.JSON;
import okhttp3.*;

import java.io.IOException;

public class OkHttpSinglePostExample {
    // 创建 OkHttpClient 实例,用于发送 HTTP 请求
    private static final OkHttpClient client = new OkHttpClient();

    public static void main(String[] args) {
        // 创建一个学生对象,包含学生的基本信息
        Student student = new Student("Alice", 20, "S12345");
        // 调用发送学生数据的方法
        sendStudentData(student);
    }

    private static void sendStudentData(Student student) {
        // 使用 FastJSON 将学生对象转换为 JSON 字符串,方便作为请求体发送
        String jsonBody = JSON.toJSONString(student);
        // 创建请求体,使用预先定义的 JSON 媒体类型和转换后的 JSON 字符串
        RequestBody body = RequestBody.create(Constants.JSON_MEDIA_TYPE, jsonBody);
        // 创建请求对象,设置请求的 URL 和请求体,并指定请求方法为 POST
        Request request = new Request.Builder()
               .url(Constants.API_URL)
               .post(body)
               .build();

        try (Response response = client.newCall(request).execute()) {
            // 判断请求是否成功,状态码在 200 - 299 之间表示成功
            if (response.isSuccessful()) {
                // 读取响应体的内容并转换为字符串
                String responseData = response.body().string();
                System.out.println("Student " + student.getName() + " data sent successfully. Response: " + responseData);
            } else {
                // 打印请求失败的错误码
                System.out.println("Error sending data for student " + student.getName() + ". Code: " + response.code());
            }
        } catch (IOException e) {
            // 捕获并打印发送请求过程中可能出现的 IO 异常
            System.err.println("Exception occurred while sending data for student " + student.getName() + ": " + e.getMessage());
        }
    }
}

代码解释

  1. 创建学生对象:在 main 方法中,我们创建了一个 Student 对象,包含学生的姓名、年龄和学号信息。
  2. 对象转 JSON:使用 FastJSON 的 toJSONString 方法将 Student 对象转换为 JSON 字符串。
  3. 创建请求体:利用 RequestBody.create 方法,结合之前定义的常量 JSON_MEDIA_TYPE,将 JSON 字符串封装成请求体。
  4. 创建请求对象:使用 Request.Builder 构建请求对象,设置请求的 URL 和请求体,并指定请求方法为 POST。
  5. 发送请求并处理响应:通过 client.newCall(request).execute() 发送请求,根据响应的状态码判断请求是否成功,成功则打印响应数据,失败则打印错误码。若发生 IOException,捕获并打印错误信息。

五、多线程情况:发送多个学生信息

线程池基础知识

在 Java 中,线程池是一种用于管理和复用线程的机制。它可以避免频繁创建和销毁线程所带来的性能开销,提高系统的响应速度和资源利用率。ExecutorService 是 Java 提供的用于管理线程池的接口,Executors 是一个工具类,提供了多种创建线程池的方法。

例如,Executors.newFixedThreadPool(int nThreads) 方法会创建一个固定大小的线程池,线程池中的线程数量始终保持为 nThreads。当有新的任务提交时,如果线程池中有空闲线程,则会立即执行该任务;如果没有空闲线程,则任务会被放入任务队列中等待执行。

代码实现

import com.alibaba.fastjson.JSON;
import okhttp3.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class OkHttpMultiThreadedPostExample {
    // 创建 OkHttpClient 实例,用于发送 HTTP 请求
    private static final OkHttpClient client = new OkHttpClient();
    // 创建一个固定大小为 10 的线程池,用于并发处理多个请求
    private static final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        // 模拟多个学生信息,创建一个学生列表
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 20, "S12345"));
        students.add(new Student("Bob", 21, "S67890"));
        students.add(new Student("Charlie", 22, "S13579"));

        // 用于存储每个任务的执行结果
        List<Future<?>> futures = new ArrayList<>();
        // 遍历学生列表,为每个学生信息创建一个任务并提交给线程池执行
        for (Student student : students) {
            /*
             * executorService.submit() 方法用于将一个任务提交给线程池执行。
             * 这里使用了 Lambda 表达式 () -> sendStudentData(student) 来创建一个 Runnable 任务。
             * 该任务会调用 sendStudentData 方法,将学生信息发送到服务器。
             * submit 方法会返回一个 Future 对象,用于跟踪任务的执行状态和获取执行结果。
             * 将 Future 对象添加到 futures 列表中,方便后续等待所有任务完成。
             */
            futures.add(executorService.submit(() -> sendStudentData(student)));
        }

        // 等待所有任务完成
        for (Future<?> future : futures) {
            try {
                // future.get() 方法会阻塞当前线程,直到任务执行完成
                future.get();
            } catch (Exception e) {
                // 捕获并打印任务执行过程中可能出现的异常
                e.printStackTrace();
            }
        }

        // 关闭线程池,不再接受新的任务,但会等待已提交的任务执行完成
        executorService.shutdown();
    }

    private static void sendStudentData(Student student) {
        // 使用 FastJSON 将学生对象转换为 JSON 字符串,方便作为请求体发送
        String jsonBody = JSON.toJSONString(student);
        // 创建请求体,使用预先定义的 JSON 媒体类型和转换后的 JSON 字符串
        RequestBody body = RequestBody.create(Constants.JSON_MEDIA_TYPE, jsonBody);
        // 创建请求对象,设置请求的 URL 和请求体,并指定请求方法为 POST
        Request request = new Request.Builder()
               .url(Constants.API_URL)
               .post(body)
               .build();

        try (Response response = client.newCall(request).execute()) {
            // 判断请求是否成功,状态码在 200 - 299 之间表示成功
            if (response.isSuccessful()) {
                // 读取响应体的内容并转换为字符串
                String responseData = response.body().string();
                System.out.println("Student " + student.getName() + " data sent successfully. Response: " + responseData);
            } else {
                // 打印请求失败的错误码
                System.out.println("Error sending data for student " + student.getName() + ". Code: " + response.code());
            }
        } catch (IOException e) {
            // 捕获并打印发送请求过程中可能出现的 IO 异常
            System.err.println("Exception occurred while sending data for student " + student.getName() + ": " + e.getMessage());
        }
    }
}

代码解释

  1. 创建线程池:使用 Executors.newFixedThreadPool(10) 创建一个固定大小为 10 的线程池,用于并发处理多个请求。
  2. 模拟多个学生信息:创建一个 List 列表,添加多个 Student 对象。
  3. 提交任务到线程池:遍历学生列表,将每个学生信息的发送任务提交给线程池执行。使用 executorService.submit(() -> sendStudentData(student)) 方法,其中 () -> sendStudentData(student) 是一个 Lambda 表达式,它实现了 Runnable 接口,表示一个要执行的任务。submit 方法会返回一个 Future 对象,用于跟踪任务的执行状态。将这些 Future 对象添加到 futures 列表中。
  4. 等待任务完成:遍历 Future 列表,调用 future.get() 方法等待所有任务完成。get 方法会阻塞当前线程,直到任务执行完成。若任务执行过程中发生异常,捕获并打印异常信息。
  5. 关闭线程池:所有任务完成后,调用 executorService.shutdown() 关闭线程池。shutdown 方法会停止接受新的任务,但会等待已提交的任务执行完成。
  6. 发送请求方法sendStudentData 方法与常规情况的实现相同,负责将单个学生信息转换为 JSON 字符串,创建请求体和请求对象,并发送 POST 请求,同时处理响应和异常。

六、注意事项和优化建议

常规情况

  • 异常处理:虽然代码中对 IOException 进行了捕获和简单处理,但在实际项目中,可根据不同的业务需求,对异常进行更细致的分类处理,例如重试机制、记录日志等。
  • 超时设置:可在创建 OkHttpClient 时设置连接超时、读写超时等参数,避免请求长时间挂起。示例代码如下:
OkHttpClient client = new OkHttpClient.Builder()
       .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
       .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
       .writeTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
       .build();

异步请求处理

OkHttp 支持异步请求,可以使用 enqueue 方法代替 execute 方法,避免阻塞主线程。

private static void sendStudentDataAsync(Student student) {
    String jsonBody = JSON.toJSONString(student);
    RequestBody body = RequestBody.create(Constants.JSON_MEDIA_TYPE, jsonBody);
    Request request = new Request.Builder()
           .url(Constants.API_URL)
           .post(body)
           .build();

    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.err.println("Exception occurred while sending data for student " + student.getName() + ": " + e.getMessage());
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (response.isSuccessful()) {
                String responseData = response.body().string();
                System.out.println("Student " + student.getName() + " data sent successfully. Response: " + responseData);
            } else {
                System.out.println("Error sending data for student " + student.getName() + ". Code: " + response.code());
            }
        }
    });
}

多线程情况

  • 线程池大小:线程池的大小需要根据实际情况进行调整。若线程池过大,可能会导致系统资源耗尽;若线程池过小,可能无法充分利用系统资源。可根据服务器的硬件配置和业务需求进行合理设置。
        // 优化线程池大小,根据学生集合大小和系统核心数动态调整
        int corePoolSize = Math.min(Runtime.getRuntime().availableProcessors() * 2, students.size());
        ExecutorService executorService = new ThreadPoolExecutor(
                corePoolSize,
                corePoolSize,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>()
        );
- 线程池大小的选择需要综合考虑多个因素,单纯使用学生集合的大小并不一定是最合适的。如果学生集合非常大,创建过多的线程可能会导致系统资源耗尽,甚至引发性能问题。
- 这里我们根据系统的核心数和学生集合的大小动态调整线程池的大小。`Runtime.getRuntime().availableProcessors()` 可以获取当前系统的核心数,通常将线程池的核心线程数设置为核心数的 2 倍是一个比较合理的选择,但同时不能超过学生集合的大小。
- 使用 `ThreadPoolExecutor` 手动创建线程池,这样可以更灵活地控制线程池的参数。
  • 并发控制:当并发请求过多时,可能会对服务器造成较大压力,甚至导致服务器崩溃。可考虑添加限流机制,例如使用信号量或令牌桶算法来控制并发请求的数量。
  • 连接池配置:OkHttp 提供了连接池的配置选项,可根据实际需求调整连接池的大小和保持时间,以提高连接的复用率,减少连接建立和销毁的开销。示例代码如下:
OkHttpClient client = new OkHttpClient.Builder()
       .connectionPool(new ConnectionPool(5, 5, java.util.concurrent.TimeUnit.MINUTES))
       .build();

你可能感兴趣的:(Java,okhttp,java)