在现代 Java 开发中,与外部服务进行数据交互是极为常见的需求。其中,发送 HTTP POST 请求以提交数据更是屡见不鲜。OkHttp 作为一款高效且功能强大的 HTTP 客户端库,以及 FastJSON 作为阿里巴巴开源的高性能 JSON 处理库,二者的结合能为我们提供便捷、高效的数据交互解决方案。
在开始编写代码之前,我们需要为项目添加必要的依赖。以下分别展示 Maven 和 Gradle 两种构建工具的配置方法。
若使用 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 项目,在 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());
}
}
}
main
方法中,我们创建了一个 Student
对象,包含学生的姓名、年龄和学号信息。toJSONString
方法将 Student
对象转换为 JSON 字符串。RequestBody.create
方法,结合之前定义的常量 JSON_MEDIA_TYPE
,将 JSON 字符串封装成请求体。Request.Builder
构建请求对象,设置请求的 URL 和请求体,并指定请求方法为 POST。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());
}
}
}
Executors.newFixedThreadPool(10)
创建一个固定大小为 10 的线程池,用于并发处理多个请求。List
列表,添加多个 Student
对象。executorService.submit(() -> sendStudentData(student))
方法,其中 () -> sendStudentData(student)
是一个 Lambda 表达式,它实现了 Runnable
接口,表示一个要执行的任务。submit
方法会返回一个 Future
对象,用于跟踪任务的执行状态。将这些 Future
对象添加到 futures
列表中。Future
列表,调用 future.get()
方法等待所有任务完成。get
方法会阻塞当前线程,直到任务执行完成。若任务执行过程中发生异常,捕获并打印异常信息。executorService.shutdown()
关闭线程池。shutdown
方法会停止接受新的任务,但会等待已提交的任务执行完成。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` 手动创建线程池,这样可以更灵活地控制线程池的参数。
OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(5, 5, java.util.concurrent.TimeUnit.MINUTES))
.build();