125. Java 发送 GET、POST 请求

一、HTTP 请求基础概念

HTTP 协议简介

基本概念

HTTP(HyperText Transfer Protocol,超文本传输协议)是一种应用层协议,用于在客户端和服务器之间传输超文本(如HTML)。它是Web数据通信的基础,采用请求-响应模型。

核心特点
  1. 无状态:每个请求独立,服务器不保留客户端状态(需借助Cookie/Session实现状态管理)。
  2. 明文传输:默认不加密(HTTPS是加密版本)。
  3. 灵活可扩展:支持自定义头部和多种数据格式(JSON/XML等)。
请求方法
方法 用途
GET 获取资源(参数在URL中)
POST 提交数据(参数在请求体)
PUT 更新资源
DELETE 删除资源
状态码示例
  • 200 OK:请求成功
  • 404 Not Found:资源不存在
  • 500 Internal Server Error:服务器错误
报文结构
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0

HTTP/1.1 200 OK
Content-Type: text/html
...
Java中的典型应用
// 使用HttpURLConnection发送GET请求示例
URL url = new URL("http://example.com/api");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
注意事项
  1. GET请求参数需进行URL编码
  2. POST请求需正确设置Content-Type头部
  3. 连接超时需处理SocketTimeoutException

GET 与 POST 请求的区别

1. 数据传输方式
  • GET:数据通过 URL 的查询字符串(Query String)传递,如 http://example.com?name=value
  • POST:数据通过请求体(Request Body)传递,不会直接暴露在 URL 中。
2. 数据长度限制
  • GET:受 URL 长度限制(通常为 2048 个字符,具体由浏览器和服务器决定)。
  • POST:理论上无限制,但实际受服务器配置影响。
3. 安全性
  • GET:数据在 URL 中可见,不适合传输敏感信息(如密码)。
  • POST:数据在请求体中,相对更安全(但仍需 HTTPS 加密)。
4. 缓存与历史记录
  • GET:请求可被缓存、保留在浏览器历史记录中。
  • POST:默认不会被缓存或保留在历史记录中。
5. 幂等性
  • GET:幂等(多次请求结果相同),适用于数据查询。
  • POST:非幂等(多次请求可能产生不同结果),适用于数据提交(如创建资源)。
6. 使用场景
  • GET:获取数据(如搜索、分页查询)。
  • POST:提交数据(如登录、文件上传、表单提交)。
7. 示例代码(Java)
// GET 请求示例(HttpURLConnection)
URL url = new URL("http://example.com?param1=value1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

// POST 请求示例(HttpURLConnection)
URL url = new URL("http://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
    os.write("param1=value1".getBytes());
}
8. 注意事项
  • GET 不应用于修改数据的操作(如删除、更新)。
  • POST 需显式设置 Content-Type(如 application/x-www-form-urlencoded)。
  • 敏感数据无论用哪种方法,都应通过 HTTPS 传输。

请求头(Request Headers)

概念定义

请求头是 HTTP 请求的一部分,用于传递客户端(如浏览器或 Java 程序)向服务器发送的附加信息。它由一系列键值对组成,每个键值对提供特定的元数据。

常见请求头字段
  1. User-Agent:标识客户端类型(如浏览器或 Java 程序)。
  2. Content-Type:指定请求体的格式(如 application/jsonapplication/x-www-form-urlencoded)。
  3. Accept:声明客户端能接收的响应格式。
  4. Authorization:用于身份验证(如 Bearer token)。
  5. Cookie:传递会话信息。
使用场景
  • 身份验证(如 JWT 令牌)。
  • 控制缓存行为(如 Cache-Control)。
  • 指定数据格式(如 Content-Type)。
注意事项
  • 大小写不敏感,但建议统一风格(如首字母大写)。
  • 避免自定义非标准头,除非服务端明确支持。

请求体(Request Body)

概念定义

请求体是 HTTP 请求中携带实际数据的部分,通常用于 POST、PUT 等请求方法。GET 请求一般没有请求体。

常见数据格式
  1. 表单数据application/x-www-form-urlencoded(如 key1=value1&key2=value2)。
  2. JSONapplication/json(如 {"key": "value"})。
  3. 文件上传multipart/form-data
使用场景
  • 提交表单数据(如用户登录)。
  • 发送结构化数据(如 API 调用)。
  • 上传文件。
注意事项
  • GET 请求不应使用请求体(语义不符合 HTTP 规范)。
  • 确保 Content-Type 与请求体格式匹配。

示例代码(Java 11+ HttpClient)

发送带请求头和请求体的 POST 请求
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class HttpExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建 HttpClient
        HttpClient client = HttpClient.newHttpClient();

        // 2. 构建请求体(JSON 格式)
        String requestBody = "{\"username\":\"admin\",\"password\":\"123\"}";

        // 3. 创建 HttpRequest,设置请求头和请求体
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com/api/login"))
                .header("Content-Type", "application/json")
                .header("Authorization", "Bearer abc123")
                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                .build();

        // 4. 发送请求并获取响应
        HttpResponse<String> response = 
                client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println("响应状态码: " + response.statusCode());
        System.out.println("响应体: " + response.body());
    }
}
关键点说明
  • 通过 .header() 方法设置请求头。
  • 通过 BodyPublishers.ofString() 设置请求体。
  • 必须为 POST 请求指定 Content-Type 头。

URL 编码

什么是 URL 编码

URL 编码(Percent-Encoding)是一种将特殊字符转换为 % 后跟两位十六进制数的格式的机制。例如,空格会被编码为 %20

为什么需要 URL 编码

URL 中只能包含 ASCII 字符集中的合法字符(如字母、数字和部分符号)。如果 URL 包含非 ASCII 字符(如中文)或保留字符(如 ?, &, =),必须进行编码以避免歧义或解析错误。

常见需要编码的字符
  • 空格:%20
  • /%2F
  • ?%3F
  • &%26
  • =%3D
  • 中文或其他 Unicode 字符:如 编码为 %E4%B8%AD

Java 中的 URL 编码

URLEncoder

Java 提供 java.net.URLEncoder 类用于编码 URL 参数:

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

String encodedValue = URLEncoder.encode("参数值", StandardCharsets.UTF_8);
// 输出:%E5%8F%82%E6%95%B0%E5%80%BC
注意事项
  1. 字符集选择:必须指定字符集(如 UTF-8),否则可能因平台默认编码导致乱码。
  2. 不编码整个 URL:仅对参数部分编码,例如:
    String url = "http://example.com/search?q=" + URLEncoder.encode("Java 教程", "UTF-8");
    

GET 请求的参数传递

格式要求

GET 请求的参数直接拼接在 URL 后,格式为:

http://example.com/api?key1=value1&key2=value2

需对 keyvalue 分别编码。

示例代码
String baseUrl = "http://example.com/search";
String query = "Java 网络编程";
int page = 2;

String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8);
String urlWithParams = baseUrl + "?q=" + encodedQuery + "&page=" + page;

// 结果:http://example.com/search?q=Java%20%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B&page=2

POST 请求的参数传递

表单格式(x-www-form-urlencoded)

POST 请求的表单数据也需要编码,格式与 GET 的查询字符串相同,但通过请求体发送。

示例代码
import java.net.HttpURLConnection;
import java.io.OutputStream;

String param1 = "value1";
String param2 = "中文";

String encodedData = "key1=" + URLEncoder.encode(param1, StandardCharsets.UTF_8) +
                    "&key2=" + URLEncoder.encode(param2, StandardCharsets.UTF_8);

HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com/api").openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);

try (OutputStream os = conn.getOutputStream()) {
    os.write(encodedData.getBytes(StandardCharsets.UTF_8));
}
注意事项
  1. 设置 Content-Type:需添加请求头:
    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    
  2. 避免重复编码:确保参数值仅编码一次。

常见问题

1. 编码与解码不匹配
  • 服务端解码字符集需与客户端编码字符集一致(如均为 UTF-8)。
  • 错误示例:客户端用 UTF-8 编码,服务端用 ISO-8859-1 解码会导致乱码。
2. 保留字符未编码

& 未编码会错误分割参数:

// 错误:未编码 &
String badUrl = "http://example.com?name=A&B&age=20";
// 正确:
String goodUrl = "http://example.com?name=A%26B&age=20";

HTTP 状态码基础

HTTP 状态码是服务器对客户端请求的响应标识,由 3 位数字组成,分为 5 类:

  1. 1xx(信息性状态码):请求已被接收,继续处理(如 100 Continue)。
  2. 2xx(成功状态码):请求成功处理(如 200 OK)。
  3. 3xx(重定向状态码):需进一步操作以完成请求(如 302 Found)。
  4. 4xx(客户端错误状态码):请求包含错误(如 404 Not Found)。
  5. 5xx(服务器错误状态码):服务器处理请求失败(如 500 Internal Server Error)。

常见状态码详解

2xx 成功
  • 200 OK:请求成功,响应体包含请求结果。
  • 201 Created:资源创建成功(常见于 POST 请求)。
  • 204 No Content:请求成功,但无返回内容(如 DELETE 请求)。
3xx 重定向
  • 301 Moved Permanently:资源永久重定向到新 URL。
  • 302 Found:资源临时重定向。
  • 304 Not Modified:资源未修改(缓存有效)。
4xx 客户端错误
  • 400 Bad Request:请求语法错误。
  • 401 Unauthorized:未认证(需身份验证)。
  • 403 Forbidden:服务器拒绝请求(权限不足)。
  • 404 Not Found:资源不存在。
5xx 服务器错误
  • 500 Internal Server Error:服务器内部错误。
  • 502 Bad Gateway:网关或代理服务器错误。
  • 503 Service Unavailable:服务不可用(如过载)。

Java 中的响应处理

通过 HttpURLConnection 获取状态码
URL url = new URL("https://example.com/api");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

int statusCode = conn.getResponseCode(); // 获取状态码
if (statusCode == 200) {
    // 处理成功响应
    InputStream inputStream = conn.getInputStream();
    // 读取响应数据...
} else {
    // 处理错误响应
    InputStream errorStream = conn.getErrorStream();
    // 读取错误信息...
}
通过 HttpClient(Java 11+)处理响应
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/api"))
        .build();

HttpResponse<String> response = client.send(
        request, HttpResponse.BodyHandlers.ofString());

int statusCode = response.statusCode();
String body = response.body(); // 获取响应体

if (statusCode == 200) {
    System.out.println("响应数据: " + body);
} else {
    System.out.println("请求失败,状态码: " + statusCode);
}

注意事项

  1. 状态码非 200 不一定是错误:如 204 表示成功但无内容。
  2. 重定向需手动处理:默认 HttpURLConnection 会自动跟随重定向,可通过 setInstanceFollowRedirects(false) 禁用。
  3. 错误流与输入流分离getErrorStream() 用于 4xx/5xx 错误,getInputStream() 用于成功响应。
  4. 资源释放:务必关闭 InputStream 和连接(conn.disconnect())。

示例:完整 GET 请求与状态码处理

try {
    URL url = new URL("https://example.com/api");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");

    int statusCode = conn.getResponseCode();
    if (statusCode >= 200 && statusCode < 300) {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    } else {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getErrorStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.err.println(line);
            }
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}

二、Java 原生 API 实现

使用 java.net.URL 发送 GET 请求

概念定义

java.net.URL 是 Java 标准库中用于表示统一资源定位符的类,可用于打开连接并读取网络资源。通过 URL.openConnection() 方法可以获取 URLConnection 对象,进而发送 HTTP GET 请求。

使用场景
  • 从 REST API 获取数据
  • 读取远程文件内容
  • 简单的网页抓取
核心步骤
  1. 创建 URL 对象
  2. 打开连接获取 URLConnection
  3. 设置请求属性(可选)
  4. 获取输入流读取响应
示例代码
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

public class GetRequestExample {
    public static void main(String[] args) {
        try {
            // 1. 创建URL对象
            URL url = new URL("https://example.com/api?param1=value1");
            
            // 2. 打开连接
            URLConnection connection = url.openConnection();
            
            // 3. 设置请求属性(可选)
            connection.setRequestProperty("User-Agent", "Java Client");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            
            // 4. 获取响应
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(connection.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
注意事项
  1. 异常处理:必须处理 MalformedURLExceptionIOException
  2. 超时设置:建议设置连接和读取超时
  3. 资源释放:使用 try-with-resources 确保流正确关闭
  4. 编码问题:注意响应内容的字符编码
  5. HTTPS支持:默认支持 HTTPS,无需额外配置
进阶用法
  • 添加查询参数:直接在 URL 字符串中添加 ?key=value&...
  • 重定向处理:默认自动跟随重定向
  • 响应头读取:通过 connection.getHeaderFields()

使用 HttpURLConnection 发送 POST 请求

概念定义

HttpURLConnection 是 Java 标准库中用于发送 HTTP 请求的核心类,支持 GET、POST 等 HTTP 方法。通过它可以直接与服务器交互,发送请求并接收响应。

使用场景
  • 需要向服务器提交表单数据。
  • 调用 RESTful API 接口。
  • 上传文件或 JSON 数据。
核心步骤
  1. 创建连接对象:通过 URL.openConnection() 获取 HttpURLConnection 实例。
  2. 设置请求方法:调用 setRequestMethod("POST")
  3. 启用输出流setDoOutput(true) 允许向服务器写入数据。
  4. 设置请求头:如 Content-Type(表单或 JSON)。
  5. 写入请求体:通过输出流发送数据。
  6. 读取响应:从输入流获取服务器返回的数据。
示例代码(表单提交)
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class PostExample {
    public static void main(String[] args) throws IOException {
        String url = "https://example.com/api";
        String postData = "username=test&password=123456";

        // 1. 创建连接
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        
        // 2. 设置请求方法
        conn.setRequestMethod("POST");
        
        // 3. 启用输出流并设置请求头
        conn.setDoOutput(true);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        
        // 4. 发送数据
        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = postData.getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }
        
        // 5. 读取响应
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                response.append(line);
            }
            System.out.println("响应内容: " + response);
        }
        
        conn.disconnect(); // 关闭连接
    }
}
注意事项
  1. 异常处理:需捕获 IOException,处理网络或流错误。
  2. 资源释放:使用 try-with-resources 确保流自动关闭。
  3. 超时设置:建议通过 setConnectTimeout()setReadTimeout() 避免阻塞。
  4. 重定向:默认自动重定向,可通过 setInstanceFollowRedirects(false) 禁用。
  5. HTTPS:若为 HTTPS 地址,需确保证书受信任(或自定义 SSLContext)。

请求头参数

HTTP 请求头(Headers)是客户端和服务器之间传递的元数据,用于控制请求和响应的行为。在发送 GET 或 POST 请求时,设置合适的请求头参数可以优化通信,例如指定内容类型、认证信息或缓存策略。

常见请求头参数
  1. Content-Type:指定请求体的媒体类型(如 application/jsonapplication/x-www-form-urlencoded)。
  2. Accept:声明客户端能接收的响应类型(如 application/json)。
  3. Authorization:用于身份验证(如 Bearer token)。
  4. User-Agent:标识客户端类型(如浏览器或应用程序)。
  5. Cache-Control:控制缓存行为(如 no-cache)。
设置请求头的方法(Java)
1. 使用 HttpURLConnection
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpExample {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://example.com/api");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        
        // 设置请求方法
        connection.setRequestMethod("GET");
        
        // 设置请求头
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("Authorization", "Bearer your_token");
        
        // 发送请求并处理响应
        int responseCode = connection.getResponseCode();
        System.out.println("Response Code: " + responseCode);
    }
}
2. 使用 HttpClient(Java 11+)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class HttpClientExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com/api"))
                .header("Content-Type", "application/json")
                .header("Authorization", "Bearer your_token")
                .GET() // 或 .POST(HttpRequest.BodyPublishers.ofString("请求体"))
                .build();
        
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("Response Body: " + response.body());
    }
}
注意事项
  1. 区分大小写:请求头名称通常不区分大小写,但建议统一使用首字母大写(如 Content-Type)。
  2. 敏感信息:避免在请求头中直接暴露敏感数据(如密码),优先使用加密或令牌。
  3. 覆盖问题:重复调用 setRequestProperty 会覆盖同名头,而 addRequestProperty(部分库支持)可添加多个值。
  4. 服务端兼容性:某些服务器可能对请求头有严格校验,需参考 API 文档。

通过合理设置请求头,可以确保请求被正确处理,并实现身份验证、内容协商等功能。


处理响应数据流

概念定义

在Java中发送HTTP请求后,服务器返回的响应通常以数据流(InputStream)的形式传输。处理响应数据流是指从HTTP响应中读取、解析和转换这些原始字节数据为可用的格式(如字符串、JSON对象等)。

使用场景
  1. 读取文本响应(如HTML、JSON、XML)
  2. 下载文件(如图片、PDF等二进制数据)
  3. 处理大响应数据(流式处理避免内存溢出)
核心类和方法
  1. HttpURLConnection.getInputStream() - 获取原始输入流
  2. BufferedReader - 用于高效读取文本数据
  3. InputStream/OutputStream - 用于处理二进制数据
示例代码
文本数据读取
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try (InputStream inputStream = connection.getInputStream();
     BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
    StringBuilder response = new StringBuilder();
    String line;
    while ((line = reader.readLine()) != null) {
        response.append(line);
    }
    System.out.println(response.toString());
}
二进制数据保存
try (InputStream in = connection.getInputStream();
     FileOutputStream out = new FileOutputStream("output.file")) {
    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}
注意事项
  1. 资源释放:必须关闭InputStream和连接,推荐使用try-with-resources
  2. 编码处理:文本数据需指定正确编码(如UTF-8)
  3. 性能优化:对大文件使用缓冲(BufferedInputStream)
  4. 错误处理:检查HTTP状态码(如200表示成功)
高级处理

对于JSON响应,可结合Jackson/Gson:

InputStream input = connection.getInputStream();
MyData data = new ObjectMapper().readValue(input, MyData.class);

异常处理与资源释放

概念定义

在Java发送HTTP请求时,异常处理和资源释放是确保程序健壮性和资源高效利用的关键环节。异常处理指捕获并处理网络请求中可能出现的异常(如超时、连接失败等),资源释放指在使用完毕后关闭连接、流等系统资源,防止内存泄漏。

使用场景
  1. 网络请求不稳定:如服务器无响应、DNS解析失败等。
  2. 资源占用:如未关闭InputStreamHttpURLConnection导致连接泄漏。
  3. 数据完整性:如读取响应时发生异常,需确保已获取的数据被正确处理。
常见误区与注意事项
  1. 忽略异常:仅打印日志而不处理异常可能导致问题被掩盖。
  2. 资源未释放:未在finally块中释放资源,或在Java 7之前未手动关闭资源。
  3. 异常吞没:在catch块中直接捕获Exception而不区分具体异常类型。
示例代码(传统HttpURLConnection
HttpURLConnection connection = null;
InputStream inputStream = null;
try {
    URL url = new URL("https://example.com/api");
    connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("GET");
    
    inputStream = connection.getInputStream();
    // 处理输入流数据...
} catch (IOException e) {
    System.err.println("请求失败: " + e.getMessage());
} finally {
    if (inputStream != null) {
        try {
            inputStream.close(); // 关闭输入流
        } catch (IOException e) {
            System.err.println("关闭输入流失败: " + e.getMessage());
        }
    }
    if (connection != null) {
        connection.disconnect(); // 断开连接
    }
}
示例代码(Java 7+ try-with-resources)
try (InputStream inputStream = new URL("https://example.com/api").openStream()) {
    // 自动关闭资源
    // 处理输入流数据...
} catch (IOException e) {
    System.err.println("请求失败: " + e.getMessage());
}
关键点
  • try-with-resources:优先使用(需实现AutoCloseable接口),自动释放资源。
  • finally块:在旧版Java中手动释放资源。
  • 异常细分:根据业务需求捕获MalformedURLExceptionSocketTimeoutException等具体异常。

三、Apache HttpClient 库

HttpClient 核心组件

1. HttpClient
  • 定义:HTTP 协议客户端,负责发送 HTTP 请求并接收响应。
  • 作用
    • 管理连接池、线程池。
    • 支持同步/异步请求。
    • 处理重定向、超时等全局配置。
2. HttpRequest
  • 定义:封装 HTTP 请求信息(URL、方法、头、体)。
  • 关键属性
    • URI:目标地址(如 https://example.com/api)。
    • HttpMethod:GET/POST/PUT/DELETE 等。
    • Headers:Content-Type、Authorization 等。
    • BodyPublisher:请求体数据(如 JSON 字符串)。
3. HttpResponse
  • 定义:封装服务器返回的响应数据。
  • 关键方法
    • statusCode():获取状态码(200、404 等)。
    • body():获取响应体(字符串、字节数组等)。
    • headers():读取响应头(如 Content-Length)。
4. HttpClient.Builder
  • 作用:配置客户端行为。
  • 常用配置
    HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))  // 连接超时
        .followRedirects(HttpClient.Redirect.NORMAL)  // 重定向策略
        .version(HttpClient.Version.HTTP_2)      // HTTP 版本
        .build();
    
5. BodyHandlers
  • 作用:定义响应体的处理方式。
  • 常见类型
    • ofString():将响应体转为字符串。
    • ofByteArray():转为字节数组。
    • ofFile(Path):直接保存到文件。
示例代码(同步 GET 请求)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/api"))
        .GET()
        .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

创建 HttpClient 实例

概念定义

HttpClient 是 Java 中用于发送 HTTP 请求的核心类,属于 java.net.http 包(JDK 11+)或第三方库(如 Apache HttpClient)。它负责管理连接、发送请求并处理响应。

使用场景
  • 需要发送 HTTP GET/POST 请求与服务器交互时。
  • 适用于 REST API 调用、爬虫、微服务通信等场景。
常见实现方式
1. JDK 原生 HttpClient(JDK 11+)
// 创建实例(默认配置)
HttpClient client = HttpClient.newHttpClient();

// 自定义配置(超时、代理等)
HttpClient customClient = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(10))
    .followRedirects(HttpClient.Redirect.NORMAL)
    .build();
2. Apache HttpClient(需添加依赖)

<dependency>
    <groupId>org.apache.httpcomponentsgroupId>
    <artifactId>httpclientartifactId>
    <version>4.5.13version>
dependency>
// 创建实例
CloseableHttpClient client = HttpClients.createDefault();

// 自定义配置
CloseableHttpClient customClient = HttpClients.custom()
    .setConnectionTimeToLive(10, TimeUnit.SECONDS)
    .setDefaultRequestConfig(RequestConfig.custom()
        .setConnectTimeout(5000)
        .build())
    .build();
注意事项
  1. 线程安全HttpClient 实例通常设计为线程安全,建议复用而非频繁创建。
  2. 资源释放:使用 Apache HttpClient 时需调用 close() 释放连接。
  3. 性能调优:高并发场景建议配置连接池(如 Apache 的 PoolingHttpClientConnectionManager)。
  4. 版本差异:JDK 9 之前需使用第三方库,JDK 11+ 推荐使用原生实现。
示例代码(JDK 11+)
HttpClient client = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)  // 指定HTTP协议版本
    .connectTimeout(Duration.ofSeconds(5))
    .build();

构建 GET 请求对象

概念定义

GET 请求对象是用于向服务器请求数据的 HTTP 请求方式。它通过 URL 传递参数,参数以键值对的形式附加在 URL 后面,通常用于获取资源。

使用场景
  • 从服务器获取数据(如查询、搜索)
  • 请求静态资源(如 HTML、图片)
  • 幂等操作(多次请求结果相同)
常见实现方式
1. 使用 java.net.HttpURLConnection
URL url = new URL("https://example.com/api?param1=value1¶m2=value2");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");

int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    String inputLine;
    StringBuilder response = new StringBuilder();

    while ((inputLine = in.readLine()) != null) {
        response.append(inputLine);
    }
    in.close();
    System.out.println(response.toString());
}
2. 使用 java.net.http.HttpClient (Java 11+)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/api?param1=value1¶m2=value2"))
        .GET()
        .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
注意事项
  1. URL 长度限制:GET 请求参数附加在 URL 中,不同浏览器对 URL 长度有限制(通常 2048 字符)
  2. 参数编码:特殊字符需要 URL 编码,使用 URLEncoder.encode()
  3. 安全性:敏感数据不应通过 GET 请求传输(会出现在浏览器历史、服务器日志中)
  4. 幂等性:GET 请求应该是幂等的,不应修改服务器状态
参数构建示例
// 构建带参数的 GET 请求 URL
String baseUrl = "https://example.com/api";
Map<String, String> params = new HashMap<>();
params.put("name", "张三");
params.put("age", "25");

StringBuilder urlBuilder = new StringBuilder(baseUrl).append("?");
for (Map.Entry<String, String> entry : params.entrySet()) {
    urlBuilder.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
             .append("=")
             .append(URLEncoder.encode(entry.getValue(), "UTF-8"))
             .append("&");
}
// 删除最后一个"&"
String finalUrl = urlBuilder.substring(0, urlBuilder.length() - 1);

构建 POST 请求与实体

概念定义

POST 请求是 HTTP 协议中的一种请求方法,用于向服务器提交数据。与 GET 请求不同,POST 请求将数据放在请求体(Body)中发送,而不是附加在 URL 上。请求实体(Request Entity)是指 POST 请求中携带的数据部分,通常以 JSON、表单数据或二进制形式传输。

使用场景
  1. 提交表单数据:如用户注册、登录等场景。
  2. 上传文件:通过 multipart/form-data 格式传输文件。
  3. 调用 API:与 RESTful API 交互时,通常用 JSON 格式传递数据。
  4. 大数据传输:POST 请求无 URL 长度限制,适合传输较大数据。
常见注意事项
  1. Content-Type 必须正确:根据数据类型设置请求头,如 application/jsonapplication/x-www-form-urlencoded
  2. 数据编码:表单数据需进行 URL 编码,JSON 数据需序列化。
  3. 安全性:敏感数据应通过 HTTPS 传输,避免明文暴露。
  4. 幂等性:POST 请求通常是非幂等的,重复提交可能导致多次副作用。
示例代码(Java)
1. 使用 HttpURLConnection 发送 JSON 数据
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class PostJsonExample {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://api.example.com/data");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setDoOutput(true);

        String jsonInput = "{\"name\":\"John\", \"age\":30}";
        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = jsonInput.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        int responseCode = conn.getResponseCode();
        System.out.println("Response Code: " + responseCode);
    }
}
2. 使用 HttpClient(Java 11+)发送表单数据
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class PostFormExample {
    public static void main(String[] args) throws Exception {
        String formData = "username=test&password=123456";
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/login"))
                .header("Content-Type", "application/x-www-form-urlencoded")
                .POST(HttpRequest.BodyPublishers.ofString(formData))
                .build();

        HttpResponse<String> response = client.send(
            request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}
3. 使用第三方库(如 OkHttp)发送 Multipart 请求
import okhttp3.*;

public class MultipartExample {
    public static void main(String[] args) throws Exception {
        OkHttpClient client = new OkHttpClient();
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("file", "test.txt",
                    RequestBody.create(new File("test.txt"), MediaType.parse("text/plain")))
                .build();

        Request request = new Request.Builder()
                .url("https://api.example.com/upload")
                .post(requestBody)
                .build();

        try (Response response = client.newCall(request).execute()) {
            System.out.println(response.body().string());
        }
    }
}

执行请求与获取响应

在 Java 中,发送 HTTP 请求(GET、POST 等)后,需要执行请求并获取服务器返回的响应数据。以下是详细说明:

概念定义
  • 执行请求:将构建好的 HTTP 请求发送到目标服务器。
  • 获取响应:接收服务器返回的数据,包括状态码、响应头和响应体。
使用场景
  • 调用 RESTful API 接口。
  • 爬取网页数据。
  • 与其他服务进行数据交互。
常见方法
1. 使用 HttpURLConnection
// 执行 GET 请求并获取响应
URL url = new URL("https://example.com/api");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

// 获取响应状态码
int responseCode = conn.getResponseCode();
System.out.println("Response Code: " + responseCode);

// 读取响应内容
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
    response.append(inputLine);
}
in.close();

System.out.println("Response Body: " + response.toString());
2. 使用 HttpClient(Java 11+)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/api"))
        .GET()
        .build();

// 发送请求并获取响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

// 输出响应状态码和内容
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
注意事项
  1. 异常处理:网络请求可能失败,需捕获 IOException
  2. 资源释放:确保关闭 InputStreamBufferedReader 等资源。
  3. 响应编码:正确处理响应内容的字符编码(如 UTF-8)。
  4. 大响应处理:对于大响应数据,建议使用流式读取,避免内存溢出。
常见误区
  • 未检查响应状态码(如只处理 200,忽略 404 或 500)。
  • 未处理重定向(HttpURLConnection 默认自动处理,可通过 setInstanceFollowRedirects(false) 禁用)。
  • 忽略响应头信息(如 Content-TypeContent-Length)。

连接池管理与配置

概念定义

连接池是一种用于管理网络连接(如数据库连接、HTTP连接)的技术,它预先创建并维护一组可复用的连接对象。当应用程序需要连接时,从池中获取;使用完毕后归还,避免频繁创建和销毁连接的开销。

使用场景
  1. 数据库访问:如MySQL、PostgreSQL等。
  2. HTTP客户端:如Apache HttpClient、OkHttp等。
  3. 高性能服务:需要频繁建立网络连接的服务。
核心配置参数
  1. 最大连接数(maxTotal):池中允许的最大连接数。
  2. 最大空闲连接数(maxIdle):池中保持的最大空闲连接数。
  3. 最小空闲连接数(minIdle):池中保持的最小空闲连接数。
  4. 获取连接超时时间(maxWaitMillis):从池中获取连接的最大等待时间。
  5. 连接空闲超时(minEvictableIdleTimeMillis):空闲连接被回收的最小时间。
常见误区
  1. 过度配置最大连接数:可能导致资源浪费或服务端压力过大。
  2. 忽略连接泄漏:未正确关闭连接会导致池中连接耗尽。
  3. 不合理的超时设置:过短的超时可能导致频繁创建新连接。
示例代码(Apache HttpClient连接池)
// 创建连接池管理器
PoolingHttpClientConnectionManager connManager = 
    new PoolingHttpClientConnectionManager();
// 设置最大连接数
connManager.setMaxTotal(100);
// 设置每个路由的最大连接数
connManager.setDefaultMaxPerRoute(20);
// 设置空闲连接超时
connManager.setValidateAfterInactivity(30000);

// 创建HttpClient
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(connManager)
    .build();

// 使用示例
try (CloseableHttpResponse response = httpClient.execute(new HttpGet("http://example.com"))) {
    // 处理响应
} finally {
    httpClient.close();
}
最佳实践
  1. 根据实际负载测试调整连接池参数。
  2. 使用连接池监控工具(如JMX)实时观察连接状态。
  3. 确保正确释放连接(使用try-with-resources或finally块)。
  4. 为不同的目标地址配置不同的连接池策略。

四、OkHttp 框架

OkHttp 特性与优势

1. 高效的网络请求
  • HTTP/2 支持:支持多路复用,减少延迟,提高并发性能。
  • 连接池:复用 TCP 连接,减少握手开销,提升请求效率。
  • GZIP 压缩:自动压缩请求体,减少数据传输量。
2. 简洁易用的 API
  • 链式调用:通过 Request.BuilderResponse 提供流畅的 API 设计。
  • 同步/异步支持:支持同步阻塞调用和异步回调(Callback)。
3. 强大的功能扩展
  • 拦截器(Interceptor):支持自定义拦截器,用于日志、重试、认证等场景。
  • 缓存控制:支持 HTTP 缓存,可配置缓存策略(如 CacheControl)。
  • 自动重试:对失败请求提供自动重试机制(需配置)。
4. 高可靠性
  • TLS/SSL 支持:默认支持现代加密协议(如 TLS 1.3)。
  • 域名解析优化:内置 DNS 选择机制,支持自定义 DNS 实现。
  • 故障恢复:在网络切换时自动恢复连接。
5. 轻量级与兼容性
  • 体积小:库体积较小,适合移动端应用。
  • 广泛兼容:支持 Android 和 Java 应用,兼容主流 Java 版本。
示例代码(异步 GET 请求)
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
    }

    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
    }
});
常见注意事项
  • 资源释放ResponseResponseBody 需手动关闭(或使用 try-with-resources)。
  • 主线程限制:Android 中同步请求需在子线程执行。
  • 超时配置:默认无超时,建议通过 OkHttpClient.Builder 设置合理超时时间。

同步请求实现

概念定义

同步请求是指客户端发送请求后,必须等待服务器响应并处理完成后才能继续执行后续代码的请求方式。在Java中,同步请求会阻塞当前线程直到收到响应。

使用场景
  1. 需要立即获取结果的场景(如登录验证)
  2. 简单的顺序业务流程
  3. 对实时性要求较高的操作
实现方式
1. 使用HttpURLConnection
// GET请求示例
URL url = new URL("http://example.com/api?param=value");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

// 同步等待响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
    BufferedReader in = new BufferedReader(
        new InputStreamReader(conn.getInputStream()));
    String inputLine;
    StringBuilder response = new StringBuilder();
    
    while ((inputLine = in.readLine()) != null) {
        response.append(inputLine);
    }
    in.close();
    System.out.println(response.toString());
}
conn.disconnect();
2. 使用HttpClient(Java 11+)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://example.com/api"))
        .build();

// 同步发送
HttpResponse<String> response = client.send(
        request, 
        HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode());
System.out.println(response.body());
注意事项
  1. 线程阻塞:会阻塞调用线程,不适合在UI线程中使用
  2. 超时设置:必须设置合理的连接和读取超时
    conn.setConnectTimeout(5000); // 5秒连接超时
    conn.setReadTimeout(10000);    // 10秒读取超时
    
  3. 资源释放:确保关闭连接和流
  4. 异常处理:需要处理IOException等异常
性能考虑
  1. 在高并发场景下会降低系统吞吐量
  2. 每个请求都需要独立的线程处理
  3. 适合请求量不大且响应快的场景

异步回调处理

概念定义

异步回调处理是指在发起网络请求后,不阻塞当前线程,而是通过回调函数在请求完成时处理响应结果。这种方式避免了线程阻塞,提高了程序的并发性能。

使用场景
  1. 高并发场景:如服务器处理大量客户端请求时
  2. UI应用程序:防止网络请求阻塞主线程导致界面卡顿
  3. 耗时操作:当请求响应时间不确定或较长时
常见实现方式
1. Java原生方式(HttpURLConnection + 线程池)
ExecutorService executor = Executors.newFixedThreadPool(5);

executor.execute(() -> {
    try {
        URL url = new URL("https://example.com/api");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        
        // 异步读取响应
        try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(conn.getInputStream()))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            // 回调处理
            handleResponse(response.toString());
        }
    } catch (Exception e) {
        handleError(e);
    }
});

void handleResponse(String response) {
    // 处理响应逻辑
}

void handleError(Exception e) {
    // 处理错误逻辑
}
2. 使用CompletableFuture(Java 8+)
CompletableFuture.supplyAsync(() -> {
    try {
        URL url = new URL("https://example.com/api");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        // ...请求设置...
        try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(conn.getInputStream()))) {
            return reader.lines().collect(Collectors.joining());
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}).thenAccept(response -> {
    // 成功回调
    System.out.println("Response: " + response);
}).exceptionally(ex -> {
    // 异常回调
    System.out.println("Error: " + ex.getMessage());
    return null;
});
注意事项
  1. 线程安全:回调函数中访问共享资源时需要注意同步问题
  2. 内存泄漏:长时间运行的回调可能持有对象引用导致无法回收
  3. 异常处理:必须妥善处理回调中可能出现的异常
  4. 上下文切换:过多的异步回调可能导致频繁的线程切换开销
常见误区
  1. 在回调中直接更新UI(在非UI线程)
  2. 忽略回调的超时设置
  3. 未考虑回调的取消机制
  4. 回调嵌套过深导致"回调地狱"
最佳实践
  1. 使用明确的线程池管理异步任务
  2. 为回调设置超时限制
  3. 考虑使用响应式编程框架(如RxJava)处理复杂回调链
  4. 保持回调逻辑简洁,避免复杂业务逻辑

Form 表单提交

概念定义

Form 表单提交是 HTML 中用于向服务器发送数据的一种方式,通常用于用户输入数据的提交。表单通过

标签定义,可以包含输入框、下拉框、按钮等控件。表单提交时,浏览器会将用户输入的数据打包并发送到指定的服务器地址。

使用场景
  1. 用户登录/注册:提交用户名和密码。
  2. 数据收集:如问卷调查、反馈表单。
  3. 文件上传:通过表单上传文件到服务器。
常见提交方式
  1. GET 请求

    • 数据通过 URL 的查询字符串(Query String)传递。
    • 适合数据量小、安全性要求不高的场景。
    • 示例:
      <form action="/login" method="GET">
        <input type="text" name="username" />
        <input type="password" name="password" />
        <input type="submit" value="Submit" />
      form>
      
    • 提交后 URL 变为:/login?username=xxx&password=xxx
  2. POST 请求

    • 数据通过 HTTP 请求体(Body)传递,不会暴露在 URL 中。
    • 适合数据量大或安全性要求高的场景(如密码)。
    • 示例:
      <form action="/login" method="POST">
        <input type="text" name="username" />
        <input type="password" name="password" />
        <input type="submit" value="Submit" />
      form>
      
注意事项
  1. 安全性

    • 敏感数据(如密码)必须使用 POST 提交,避免通过 URL 暴露。
    • 后端需对表单数据进行验证和过滤,防止 SQL 注入或 XSS 攻击。
  2. 编码类型(enctype)

    • 默认值为 application/x-www-form-urlencoded,适合普通文本数据。
    • 文件上传需设置为 multipart/form-data
      <form action="/upload" method="POST" enctype="multipart/form-data">
        <input type="file" name="file" />
        <input type="submit" value="Upload" />
      form>
      
  3. 表单控件

    • 确保每个输入控件有 name 属性,否则数据不会被提交。
    • 使用 required 属性可以强制用户填写某些字段。
示例代码(Java 后端接收表单数据)
// 使用 Servlet 接收 POST 表单数据
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 处理业务逻辑
    }
}
常见误区
  1. 混淆 GET 和 POST

    • GET 请求适合获取数据,POST 适合提交或修改数据。
    • 浏览器地址栏直接输入 URL 是 GET 请求,无法模拟 POST。
  2. 忽略编码问题

    • 如果表单包含非英文字符,需设置页面的字符编码(如 UTF-8):
      <meta charset="UTF-8">
      
  3. 文件上传未设置 enctype

    • 文件上传时必须设置 enctype="multipart/form-data",否则文件内容无法正确传输。

JSON 数据提交

概念定义

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于前后端数据传输。在 HTTP 请求中,JSON 可以作为请求体(Request Body)提交给服务器,通常用于 POST、PUT 等非幂等请求。

使用场景
  1. 前后端交互:前端通过 JSON 格式向后端发送数据。
  2. API 调用:RESTful API 通常使用 JSON 作为请求和响应的数据格式。
  3. 微服务通信:服务间通过 JSON 格式传递数据。
注意事项
  1. Content-Type 头:必须设置为 application/json,否则服务器可能无法正确解析。
  2. 数据格式校验:确保 JSON 数据格式正确,避免因格式错误导致解析失败。
  3. 大小限制:JSON 数据不宜过大,否则可能影响传输效率。
示例代码(Java 使用 HttpClient 提交 JSON)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class JsonPostExample {
    public static void main(String[] args) throws Exception {
        // JSON 数据
        String jsonData = "{\"name\":\"John\", \"age\":30}";

        // 创建 HttpClient
        HttpClient client = HttpClient.newHttpClient();

        // 创建 HttpRequest
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com/api/users"))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(jsonData))
                .build();

        // 发送请求并获取响应
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println("Response Code: " + response.statusCode());
        System.out.println("Response Body: " + response.body());
    }
}
常见问题
  1. JSON 序列化/反序列化:可以使用库如 JacksonGson 将 Java 对象转换为 JSON 字符串。
  2. 中文乱码:确保请求和响应的编码格式一致(通常为 UTF-8)。
  3. 安全性:避免直接解析未经验证的 JSON 数据,防止注入攻击。
扩展:使用 Jackson 处理 JSON
import com.fasterxml.jackson.databind.ObjectMapper;

public class User {
    private String name;
    private int age;

    // Getters and Setters
}

public class JsonWithJackson {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        User user = new User();
        user.setName("John");
        user.setAge(30);

        // Java 对象转 JSON
        String jsonData = mapper.writeValueAsString(user);
        System.out.println("JSON: " + jsonData);

        // JSON 转 Java 对象
        User parsedUser = mapper.readValue(jsonData, User.class);
        System.out.println("User: " + parsedUser.getName());
    }
}

文件上传实现

基本概念

文件上传是指客户端(如浏览器)将本地文件通过HTTP协议传输到服务器的过程。通常使用multipart/form-data编码格式,区别于普通的application/x-www-form-urlencoded表单提交。

核心步骤
  1. 前端准备

    <form action="/upload" method="post" enctype="multipart/form-data">
      <input type="file" name="file">
      <button type="submit">上传button>
    form>
    

    关键点:必须设置enctype="multipart/form-data"

  2. 后端处理(以Spring Boot为例):

    @PostMapping("/upload")
    public String handleUpload(@RequestParam("file") MultipartFile file) {
        if (!file.isEmpty()) {
            try {
                byte[] bytes = file.getBytes();
                Path path = Paths.get("uploads/" + file.getOriginalFilename());
                Files.write(path, bytes);
                return "上传成功";
            } catch (IOException e) {
                return "上传失败";
            }
        }
        return "文件为空";
    }
    
高级特性
大文件分块上传
// 前端分片处理
const chunkSize = 5 * 1024 * 1024; // 5MB
const chunks = Math.ceil(file.size / chunkSize);
断点续传实现
// 服务端检查已上传分片
@GetMapping("/checkChunk")
public boolean checkChunk(@RequestParam String fileMd5, 
                         @RequestParam int chunkIndex) {
    return FileUtils.checkChunkExists(fileMd5, chunkIndex);
}
安全注意事项
  1. 文件类型校验

    String contentType = file.getContentType();
    if (!Arrays.asList("image/jpeg", "image/png").contains(contentType)) {
        throw new IllegalArgumentException("非法文件类型");
    }
    
  2. 存储路径安全

    String safeFilename = FilenameUtils.getName(file.getOriginalFilename());
    Path path = Paths.get("/secure/upload/dir", safeFilename);
    
  3. 病毒扫描

    if (virusScanner.scan(file.getBytes())) {
        throw new SecurityException("检测到恶意文件");
    }
    
性能优化
  1. 使用NIO进行文件写入:

    Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
    
  2. 异步处理:

    @Async
    public void asyncUpload(MultipartFile file) {
        // 上传逻辑
    }
    
常见问题解决方案
  1. 文件名乱码

    String filename = new String(file.getOriginalFilename()
        .getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
    
  2. 临时文件清理

    @Scheduled(fixedRate = 3600000)
    public void cleanTempFiles() {
        // 删除超过24小时的临时文件
    }
    

拦截器使用场景

拦截器(Interceptor)是一种在请求处理过程中插入额外逻辑的机制,常用于对请求进行预处理或后处理。以下是拦截器的常见使用场景:

1. 权限验证
  • 场景:验证用户是否有权限访问某个接口。
  • 示例:在用户访问需要登录的页面时,拦截器检查Session中是否存在用户信息,若不存在则跳转到登录页面。
2. 日志记录
  • 场景:记录请求的详细信息,如URL、参数、IP、耗时等。
  • 示例:在拦截器中打印请求日志,便于后期排查问题或分析用户行为。
3. 参数预处理
  • 场景:对请求参数进行统一校验或格式化。
  • 示例:拦截器检查参数是否为空或格式是否正确,若不符合要求则直接返回错误响应。
4. 跨域处理
  • 场景:为响应添加跨域相关的Header。
  • 示例:拦截器在所有响应中添加Access-Control-Allow-Origin等Header,解决前端跨域问题。
5. 性能监控
  • 场景:统计接口的响应时间或调用次数。
  • 示例:拦截器记录请求开始和结束时间,计算耗时并上报到监控系统。
6. 防重复提交
  • 场景:防止用户短时间内重复提交相同请求。
  • 示例:拦截器通过Token或时间间隔判断是否为重复请求,若是则拦截。
7. 数据脱敏
  • 场景:对敏感数据进行脱敏处理。
  • 示例:拦截器在返回响应前,对手机号、身份证号等字段进行脱敏。
8. 缓存处理
  • 场景:缓存高频访问的数据。
  • 示例:拦截器检查缓存中是否存在数据,若存在则直接返回,否则继续执行后续逻辑。
注意事项
  1. 执行顺序:多个拦截器的执行顺序需明确,避免逻辑冲突。
  2. 性能影响:拦截器中的逻辑应尽量轻量,避免影响请求处理速度。
  3. 异常处理:拦截器中抛出的异常需统一捕获,避免直接暴露给用户。
示例代码(Spring拦截器)
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 权限校验逻辑
        if (request.getSession().getAttribute("user") == null) {
            response.sendRedirect("/login");
            return false; // 拦截请求
        }
        return true; // 放行请求
    }
}

五、Spring RestTemplate

RestTemplate 自动配置

概念定义

RestTemplate 是 Spring 提供的一个用于发送 HTTP 请求的客户端工具类,支持 RESTful 风格的请求。在 Spring Boot 中,RestTemplate 可以通过自动配置(Auto-Configuration)快速集成到项目中,无需手动创建和管理实例。

使用场景
  1. 微服务间通信:在微服务架构中,服务之间通过 HTTP 调用彼此。
  2. 调用第三方 API:如调用支付、地图、天气等第三方服务。
  3. 测试 RESTful 接口:在单元测试或集成测试中模拟 HTTP 请求。
自动配置原理

Spring Boot 通过 RestTemplateAutoConfiguration 自动配置类,为项目提供默认的 RestTemplate Bean。如果项目中引入了 spring-boot-starter-web 依赖,Spring Boot 会自动配置 RestTemplateBuilder,用于构建 RestTemplate 实例。

如何启用自动配置
  1. 添加依赖
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
  2. 注入 RestTemplate
    在需要的地方直接注入 RestTemplate
    @Autowired
    private RestTemplate restTemplate;
    
自定义 RestTemplate

如果需要自定义 RestTemplate(如设置超时时间、拦截器等),可以通过 RestTemplateBuilder 配置:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(5))
            .build();
}
常见误区
  1. 未引入依赖:如果没有添加 spring-boot-starter-web,自动配置不会生效。
  2. 重复定义 Bean:如果手动定义了多个 RestTemplate Bean,可能会导致冲突。
  3. 忽略异常处理RestTemplate 默认会抛出异常,需结合 ResponseErrorHandler 处理错误响应。
示例代码
@RestController
public class DemoController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/call-api")
    public String callExternalApi() {
        String url = "https://api.example.com/data";
        return restTemplate.getForObject(url, String.class);
    }
}
注意事项
  1. 线程安全RestTemplate 是线程安全的,可以全局共享一个实例。
  2. 性能优化:建议复用 RestTemplate 实例,避免频繁创建。
  3. Spring Boot 2.1+ 推荐:在较新版本中,推荐使用 WebClient(响应式编程替代方案)。

GET 请求模板方法

概念定义

GET 请求模板方法是一种封装了 HTTP GET 请求通用逻辑的代码模板,通常包含 URL 构造、参数拼接、请求发送和响应处理等步骤。通过模板方法可以避免重复编写基础代码,提高开发效率。

使用场景
  1. 从服务器获取数据(如查询商品列表)
  2. 调用 RESTful API 接口
  3. 需要缓存结果的请求(因为 GET 请求天然支持缓存)
核心实现要素
  1. URL 构造(基础地址+端点路径)
  2. 查询参数拼接(key=value 形式)
  3. 请求头设置(如 Content-Type)
  4. 响应处理(状态码检查、数据解析)
Java 实现示例(使用 HttpURLConnection)
public static String sendGetRequest(String url, Map<String, String> params) throws IOException {
    // 1. 构建带参数的URL
    StringBuilder urlBuilder = new StringBuilder(url);
    if (params != null && !params.isEmpty()) {
        urlBuilder.append("?");
        for (Map.Entry<String, String> entry : params.entrySet()) {
            urlBuilder.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
                      .append("=")
                      .append(URLEncoder.encode(entry.getValue(), "UTF-8"))
                      .append("&");
        }
        urlBuilder.deleteCharAt(urlBuilder.length() - 1); // 删除最后一个&
    }

    // 2. 创建连接
    HttpURLConnection connection = (HttpURLConnection) new URL(urlBuilder.toString()).openConnection();
    connection.setRequestMethod("GET");

    // 3. 处理响应
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(connection.getInputStream()))) {
        StringBuilder response = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            response.append(line);
        }
        return response.toString();
    } finally {
        connection.disconnect();
    }
}
注意事项
  1. URL 编码:必须对参数进行 URL 编码(使用 URLEncoder)
  2. 参数长度限制:GET 请求的 URL 总长度有限制(通常 2048 字符)
  3. 敏感信息:不要用 GET 传递密码等敏感信息(会显示在 URL 中)
  4. 幂等性:GET 请求应该是幂等的(多次执行结果相同)
高级优化方向
  1. 添加连接超时和读取超时设置
  2. 支持 HTTPS 协议
  3. 加入重试机制
  4. 使用连接池提高性能

POST 请求模板方法

概念定义

POST 请求模板方法是一种封装了 HTTP POST 请求核心逻辑的代码结构,通常包含请求构建、参数处理、连接管理和响应处理等通用步骤。通过模板方法可以避免重复编写底层 HTTP 通信代码。

核心组件
  1. URL 构建:目标接口地址
  2. 请求头设置:Content-Type、Authorization 等
  3. 请求体处理:表单数据/JSON/XML 等格式封装
  4. 连接配置:超时时间、重试机制
  5. 响应解析:状态码检查、返回数据转换
基础实现(Java原生)
public static String postTemplate(String url, String params) throws IOException {
    URL obj = new URL(url);
    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
    
    // 基础配置
    con.setRequestMethod("POST");
    con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    con.setConnectTimeout(5000);
    con.setDoOutput(true);
    
    // 发送请求体
    try(OutputStream os = con.getOutputStream()) {
        byte[] input = params.getBytes(StandardCharsets.UTF_8);
        os.write(input, 0, input.length);
    }
    
    // 处理响应
    try(BufferedReader br = new BufferedReader(
        new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
        StringBuilder response = new StringBuilder();
        String responseLine;
        while ((responseLine = br.readLine()) != null) {
            response.append(responseLine.trim());
        }
        return response.toString();
    }
}
高级模板(支持JSON)
public static String postJsonTemplate(String url, Object data) throws IOException {
    HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
    con.setRequestMethod("POST");
    con.setRequestProperty("Content-Type", "application/json");
    
    // 使用Jackson转换对象为JSON
    ObjectMapper mapper = new ObjectMapper();
    try(OutputStream os = con.getOutputStream()) {
        mapper.writeValue(os, data);
    }
    
    // 处理非200响应
    if (con.getResponseCode() != 200) {
        throw new IOException("HTTP error: " + con.getResponseCode());
    }
    return new String(con.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
}
使用场景
  1. 需要重复调用相同API接口时
  2. 多环境切换(测试/生产环境)
  3. 需要统一处理异常和日志的场景
注意事项
  1. 资源释放:确保关闭所有Stream和Connection
  2. 编码统一:强制使用UTF-8避免乱码
  3. 超时设置:生产环境建议连接/读取超时分开设置
  4. HTTPS支持:需要额外处理SSL证书验证
模板优化方向
  1. 增加重试机制
  2. 添加请求日志记录
  3. 支持多格式(XML/Protobuf等)
  4. 连接池管理优化

请求/响应类型转换

概念定义

请求/响应类型转换是指在HTTP请求和响应过程中,将数据从一种格式转换为另一种格式的过程。常见的转换包括:

  • 请求体(如JSON、XML)转换为Java对象
  • Java对象转换为响应体(如JSON、XML)
  • 表单数据与Java对象之间的转换
使用场景
  1. RESTful API开发:客户端发送JSON请求,服务端将JSON转换为Java对象处理
  2. 表单提交:浏览器提交表单数据,服务端将表单数据转换为Java对象
  3. 微服务通信:服务间通过HTTP调用时进行数据格式转换
常见实现方式
1. JSON转换
// 使用Jackson将Java对象转为JSON字符串
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);

// 将JSON字符串转为Java对象
User user = mapper.readValue(json, User.class);
2. XML转换
// 使用JAXB将Java对象转为XML
JAXBContext context = JAXBContext.newInstance(User.class);
Marshaller marshaller = context.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(user, writer);
String xml = writer.toString();

// 将XML转为Java对象
Unmarshaller unmarshaller = context.createUnmarshaller();
User user = (User) unmarshaller.unmarshal(new StringReader(xml));
3. 表单数据转换
// Spring MVC自动将表单数据绑定到对象
@PostMapping("/users")
public String createUser(@ModelAttribute User user) {
    // user对象已自动填充表单数据
}
注意事项
  1. 字段映射:确保Java对象字段名与请求数据字段名一致
  2. 类型兼容:请求数据的类型必须与Java对象字段类型兼容
  3. 空值处理:考虑请求中可能缺少某些字段的情况
  4. 性能考虑:大量数据转换可能影响性能
  5. 安全性:防范JSON/XML注入攻击
常见问题解决方案
  1. 字段名不一致:使用注解指定映射关系
@JsonProperty("user_name")
private String username;
  1. 日期格式处理:指定日期格式
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
  1. 忽略未知字段:配置转换器忽略未知字段
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

异常统一处理

概念定义

异常统一处理是指在Java应用中,通过集中化的方式捕获和处理各种异常,避免在业务代码中分散处理异常逻辑。通常通过@ControllerAdvice@RestControllerAdvice注解结合@ExceptionHandler实现。

使用场景
  1. 全局异常捕获:统一处理Controller层抛出的异常。
  2. 规范化响应:将异常信息转换为标准格式(如JSON)返回给前端。
  3. 日志集中记录:避免在每个方法中单独记录异常日志。
核心实现方式
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理自定义业务异常
    @ExceptionHandler(BusinessException.class)
    public Result handleBusinessException(BusinessException e) {
        log.error("业务异常: {}", e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    // 处理系统异常
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        log.error("系统异常: ", e);
        return Result.fail(500, "系统繁忙,请稍后再试");
    }
}
常见注意事项
  1. 异常优先级:更具体的异常处理应放在前面(如BusinessExceptionException优先)
  2. 敏感信息过滤:生产环境不应返回详细的异常堆栈信息
  3. HTTP状态码:合理设置不同异常对应的HTTP状态码(如404、500等)
最佳实践
  1. 定义业务异常基类:
public class BusinessException extends RuntimeException {
    private int code;
    // 构造方法省略...
}
  1. 使用枚举管理错误码:
public enum ErrorCode {
    PARAM_ERROR(1001, "参数错误"),
    USER_NOT_FOUND(1002, "用户不存在");
    // 其他字段和方法...
}
进阶技巧
  1. 结合Validation实现参数校验统一处理:
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidException(MethodArgumentNotValidException e) {
    String message = e.getBindingResult().getFieldError().getDefaultMessage();
    return Result.fail(400, message);
}

负载均衡集成

概念定义

负载均衡集成是指在Java应用中,通过特定的技术手段将HTTP请求(如GET、POST)分发到多个服务器或服务实例上,以实现流量均衡、提高系统吞吐量和容错能力。

使用场景
  1. 高并发场景:当单台服务器无法承受大量请求时。
  2. 微服务架构:多个服务实例需要动态分配请求。
  3. 容灾备份:某台服务器故障时,自动切换到其他可用节点。
常见实现方式
1. 客户端负载均衡(如Spring Cloud Ribbon)
@RestController
public class ClientController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/balance")
    public String getService() {
        // 通过服务名调用,Ribbon自动负载均衡
        return restTemplate.getForObject(
            "http://service-provider/api/data", 
            String.class
        );
    }
}
2. 服务端负载均衡(如Nginx)
http {
    upstream backend {
        server 192.168.1.1:8080;
        server 192.168.1.2:8080;
    }

    server {
        location / {
            proxy_pass http://backend;
        }
    }
}
注意事项
  1. 会话保持:需要处理有状态服务的Session一致性(如Spring Session + Redis)。
  2. 健康检查:确保不会将请求路由到宕机的节点。
  3. 权重配置:根据服务器性能分配不同的流量比例。
示例代码(Spring Cloud LoadBalancer)
// 1. 添加依赖(Spring Cloud 2020+)
implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'

// 2. 启用负载均衡的RestTemplate
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// 3. 调用服务(自动负载均衡)
String result = restTemplate.getForObject(
    "http://service-name/api/resource", 
    String.class
);
高级策略
  1. 轮询(Round Robin):默认策略,依次分配请求。
  2. 随机(Random):随机选择节点。
  3. 响应时间加权:优先选择响应快的节点。
  4. 自定义策略:实现ReactorServiceInstanceLoadBalancer接口。

六、WebClient 响应式编程

响应式编程模型

概念定义

响应式编程是一种基于异步数据流的编程范式,核心思想是通过声明式代码处理异步事件和数据流。在Java中,响应式编程模型通常通过Reactive Streams规范实现,主要库包括RxJavaProject Reactor等。

核心特点
  1. 异步非阻塞:线程不会被长时间阻塞
  2. 事件驱动:基于事件或数据变化触发处理逻辑
  3. 背压支持:处理生产者-消费者速度不匹配问题
  4. 函数式风格:大量使用lambda表达式和函数组合
使用场景
  1. 高并发服务(如Web服务器)
  2. 实时数据处理(如股票行情)
  3. 微服务通信
  4. 响应式UI开发
Java实现示例(使用Project Reactor)
// 创建Flux流
Flux<String> flux = Flux.just("请求1", "请求2", "请求3")
    .delayElements(Duration.ofMillis(500));

// 订阅处理
flux.subscribe(
    data -> System.out.println("处理: " + data), // 正常处理
    error -> System.err.println("错误: " + error), // 错误处理
    () -> System.out.println("完成") // 完成回调
);
常见组件
  1. Publisher:数据发布者(Flux/Mono)
  2. Subscriber:数据订阅者
  3. Operator:操作符(map, filter, flatMap等)
  4. Scheduler:调度器(控制线程模型)
注意事项
  1. 避免在操作符中执行阻塞操作
  2. 注意错误处理(onErrorResume/onErrorReturn)
  3. 合理选择调度器(Schedulers.elastic()/parallel())
  4. 注意资源释放(Disposable)
与传统编程对比
特性 响应式编程 传统编程
线程模型 异步非阻塞 同步阻塞
资源使用 高效 相对低效
代码风格 声明式 命令式
错误处理 流式 try-catch

WebClient 核心 API

WebClient 是 Spring 5 引入的非阻塞、响应式 HTTP 客户端,用于发送 HTTP 请求并处理响应。它是 Spring WebFlux 的一部分,适用于异步、高性能的网络通信场景。

核心方法
  1. 创建 WebClient 实例

    // 方式1:通过工厂方法创建
    WebClient client = WebClient.create();
    
    // 方式2:通过 Builder 自定义配置
    WebClient client = WebClient.builder()
        .baseUrl("https://api.example.com")
        .defaultHeader("Content-Type", "application/json")
        .build();
    
  2. 发送请求

    • get() / post() / put() / delete():指定 HTTP 方法。
    • uri():设置请求路径或完整 URL。
    • retrieve():直接获取响应体。
    • exchangeToMono() / exchangeToFlux():更灵活地处理响应(包括状态码、头信息等)。
  3. 处理响应

    • bodyToMono():将响应体转换为 Mono(单个对象)。
    • bodyToFlux():将响应体转换为 Flux(流式数据)。
    • onStatus():自定义错误处理。
示例代码

GET 请求示例

WebClient client = WebClient.create("https://api.example.com");

Mono<String> response = client.get()
    .uri("/users/{id}", 1)
    .retrieve()
    .bodyToMono(String.class);

response.subscribe(System.out::println); // 异步打印结果

POST 请求示例

Mono<String> response = client.post()
    .uri("/users")
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue("{\"name\":\"John\"}")
    .retrieve()
    .bodyToMono(String.class);
注意事项
  1. 异步非阻塞:WebClient 默认基于 Reactor 的异步模型,需配合 Mono/Flux 使用。
  2. 连接池:默认使用 Reactor Netty 的连接池,可通过 HttpClient 配置。
  3. 超时设置:通过 HttpClient 设置全局或单次请求超时。
    HttpClient httpClient = HttpClient.create()
        .responseTimeout(Duration.ofSeconds(5));
    WebClient client = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();
    
适用场景
  • 微服务间通信(尤其是高并发场景)。
  • 需要与响应式框架(如 Spring WebFlux)集成时。
  • 替代传统的 RestTemplate(已标记为废弃)。

GET 请求链式调用

概念定义

GET 请求链式调用是一种通过方法连续调用的方式构建 HTTP GET 请求的技术。它通过返回对象自身(this)实现方法的连续调用,从而简化请求参数的拼接和配置。

使用场景
  1. 需要动态添加多个查询参数(Query Parameters)时。
  2. 需要清晰、可读性高的请求构建代码时。
  3. 在 RESTful API 调用或 Web 爬虫开发中简化请求构造。
常见实现方式
1. 使用 HttpClient(Java 11+)
HttpClient client = HttpClient.newHttpClient();
String url = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com/api"))
    .GET()
    .build()
    .uri() // 获取 URI 对象
    .toString();
2. 自定义链式工具类
public class GetRequestBuilder {
    private String baseUrl;
    private Map<String, String> params = new HashMap<>();

    public GetRequestBuilder(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public GetRequestBuilder addParam(String key, String value) {
        params.put(key, value);
        return this;
    }

    public String build() {
        StringBuilder url = new StringBuilder(baseUrl);
        if (!params.isEmpty()) {
            url.append("?");
            params.forEach((k, v) -> url.append(k).append("=").append(v).append("&"));
            url.deleteCharAt(url.length() - 1); // 移除末尾的 &
        }
        return url.toString();
    }
}

// 使用示例
String url = new GetRequestBuilder("https://example.com/api")
    .addParam("page", "1")
    .addParam("size", "10")
    .build();
注意事项
  1. URL 编码:参数值需进行 URL 编码(如 URLEncoder.encode),避免特殊字符(如 &, =)破坏 URL 结构。
  2. 参数顺序:链式调用的顺序不影响最终 URL,因为参数是无序的键值对。
  3. 空值处理:建议跳过 null 或空字符串参数,避免生成无效 URL。
优势
  1. 代码简洁:避免冗长的字符串拼接。
  2. 可读性强:方法名明确表达意图(如 addParam)。
  3. 灵活性:可动态添加/移除参数。

POST 请求数据绑定

概念定义

POST 请求数据绑定是指将客户端通过 HTTP POST 方法提交的数据(如表单数据、JSON 等)自动映射到服务器端的 Java 对象或方法参数的过程。这是 Web 开发中处理用户输入的核心机制。

使用场景
  1. 表单提交(如用户注册、登录)
  2. RESTful API 接收 JSON 数据
  3. 文件上传
  4. 复杂结构化数据的传输
常见实现方式
1. 原生 Servlet 方式
// 获取表单参数
String username = request.getParameter("username");
String password = request.getParameter("password");
2. Spring MVC 绑定
@PostMapping("/register")
public String register(@ModelAttribute User user) {
    // user 对象自动绑定表单数据
    // 属性名需与表单字段名一致
}
3. JSON 数据绑定
@PostMapping("/api/users")
public ResponseEntity createUser(@RequestBody User user) {
    // 自动将请求体中的JSON转换为User对象
    // 需要Content-Type: application/json
}
注意事项
  1. 字段匹配:客户端字段名必须与服务器端对象属性名一致
  2. 类型转换:自动类型转换可能失败(如字符串转日期)
  3. 安全性:需防范 CSRF 攻击和 XSS 注入
  4. 大文件处理:需配置合理的上传大小限制
  5. Content-Type
    • 表单:application/x-www-form-urlencoded
    • 文件:multipart/form-data
    • JSON:application/json
常见问题解决方案
  1. 字段名不一致:使用 @RequestParam 注解指定别名

    @PostMapping("/login")
    public String login(@RequestParam("u") String username) {...}
    
  2. 嵌套对象绑定:使用点号语法

    <input name="address.city" value="Beijing">
    
  3. 日期格式化:使用 @DateTimeFormat

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthDate;
    
  4. 数据验证:结合 @Valid 进行校验

    public String save(@Valid @ModelAttribute User user, BindingResult result)
    

响应结果订阅处理

概念定义

响应结果订阅处理是指在发送 HTTP 请求(如 GET、POST)后,通过异步或回调的方式处理服务器返回的响应数据。这种方式常用于非阻塞式编程,避免线程阻塞,提升程序性能。

使用场景
  1. 异步请求:适用于需要长时间等待服务器响应的场景(如网络请求)。
  2. 高并发应用:避免因同步请求导致线程阻塞,提高吞吐量。
  3. 事件驱动编程:如 GUI 应用或微服务架构中的消息处理。
常见实现方式
  1. 回调函数(Callback)

    • 通过定义成功和失败的回调函数处理响应。
    • 示例(Java 伪代码):
      httpClient.get(url, new Callback() {
          @Override
          public void onSuccess(Response response) {
              System.out.println("成功:" + response.body());
          }
          @Override
          public void onFailure(Exception e) {
              System.err.println("失败:" + e.getMessage());
          }
      });
      
  2. Future/Promise

    • 使用 CompletableFuture(Java 8+)实现异步结果处理。
    • 示例:
      CompletableFuture<Response> future = httpClient.getAsync(url);
      future.thenAccept(response -> {
          System.out.println("响应数据:" + response.body());
      }).exceptionally(e -> {
          System.err.println("请求异常:" + e.getMessage());
          return null;
      });
      
  3. Reactive Streams(响应式编程)

    • 使用如 RxJava、Project Reactor 等库订阅响应流。
    • 示例(RxJava):
      httpClient.getObservable(url)
          .subscribe(
              response -> System.out.println("响应:" + response),
              error -> System.err.println("错误:" + error)
          );
      
注意事项
  1. 线程安全:回调函数可能在非主线程执行,需注意共享数据的同步问题。
  2. 资源泄漏:未取消的订阅可能导致内存泄漏(如 Android 中的 Activity 引用)。
  3. 错误处理:务必实现失败回调,避免异常被静默吞没。
  4. 超时控制:异步请求需设置超时时间,防止长时间无响应。
示例代码(完整 HTTP 客户端)

以下是一个使用 HttpClient(Java 11+)的异步 GET 请求示例:

import java.net.URI;
import java.net.http.*;
import java.util.concurrent.CompletableFuture;

public class AsyncHttpExample {
    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/data"))
                .build();

        CompletableFuture<HttpResponse<String>> future =
                client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        future.thenApply(response -> {
            System.out.println("状态码:" + response.statusCode());
            return response.body();
        }).thenAccept(body -> {
            System.out.println("响应体:" + body);
        }).exceptionally(e -> {
            System.err.println("请求失败:" + e.getMessage());
            return null;
        });

        // 防止主线程提前退出
        future.join();
    }
}

请求超时配置

概念定义

请求超时配置是指在发送HTTP请求时,设置一个时间限制。如果在该时间内未收到服务器的响应,则自动终止请求并抛出超时异常。这是网络编程中保证系统健壮性的重要机制。

使用场景
  1. 防止因网络延迟或服务端处理缓慢导致的客户端长时间等待
  2. 避免资源(如线程、连接)被长时间占用
  3. 实现快速失败机制,提高系统响应速度
常见实现方式(Java示例)
1. HttpURLConnection
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000); // 连接超时5秒
connection.setReadTimeout(10000);   // 读取超时10秒
2. HttpClient(Apache)
RequestConfig config = RequestConfig.custom()
    .setConnectTimeout(5000)    // 连接超时
    .setSocketTimeout(10000)    // 数据传输超时
    .build();

CloseableHttpClient client = HttpClientBuilder.create()
    .setDefaultRequestConfig(config)
    .build();
3. OkHttp
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(5, TimeUnit.SECONDS)   // 连接超时
    .readTimeout(10, TimeUnit.SECONDS)     // 读取超时
    .writeTimeout(10, TimeUnit.SECONDS)    // 写入超时
    .build();
注意事项
  1. 合理设置超时值:过长会失去超时意义,过短可能导致正常请求被误判
  2. 区分连接超时和读取超时
    • 连接超时:建立TCP连接的时间
    • 读取超时:从连接建立到获取完整响应的时间
  3. 考虑重试机制:超时后是否自动重试需要根据业务场景决定
  4. 不同协议差异:HTTPS连接通常需要更长的超时时间
  5. 资源释放:超时后必须确保释放网络连接等资源
最佳实践
  • 生产环境建议:连接超时1-5秒,读取超时5-30秒(根据业务调整)
  • 在微服务架构中,超时设置应与熔断器配置协调
  • 对关键业务实现多级超时(如初次请求短超时+重试长超时)

七、实际应用场景

参数签名验证

概念定义

参数签名验证是一种确保请求数据完整性和来源可信的安全机制。通过将请求参数按特定规则排序后,使用密钥生成签名(通常为MD5、SHA1或HMAC等算法),服务端收到请求后以相同方式计算签名并进行比对,验证请求是否被篡改。

核心作用
  1. 防篡改:确保传输过程中参数未被修改
  2. 防重放:通过时间戳或nonce防止请求被重复使用
  3. 身份验证:验证请求方是否拥有合法密钥
常见实现步骤
  1. 客户端:

    • 排除签名参数本身(如sign
    • 按参数名ASCII码升序排列
    • 拼接成key1=value1&key2=value2格式
    • 追加密钥后生成签名
    // 示例:MD5签名生成
    public static String generateSign(Map<String, String> params, String secret) {
        params.remove("sign"); // 排除签名字段
        StringJoiner sj = new StringJoiner("&");
        params.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .forEach(e -> sj.add(e.getKey() + "=" + e.getValue()));
        sj.add("key=" + secret);
        return DigestUtils.md5Hex(sj.toString());
    }
    
  2. 服务端:

    • 相同流程生成签名
    • 比对客户端传来的签名值
    • 校验时间戳有效性(通常±5分钟)
注意事项
  1. 密钥管理:避免硬编码,推荐使用配置中心或KMS
  2. 空值处理:统一约定是否参与签名(建议排除null值)
  3. 编码问题:统一使用UTF-8进行URL编码
  4. 大小写敏感:MD5签名通常转为小写比对
  5. 性能影响:高频接口建议使用HMAC-SHA256替代MD5
增强方案
  1. 双因子验证:签名+时间戳/nonce组合
  2. 动态密钥:通过API获取临时签名密钥
  3. 请求指纹:加入设备特征等辅助信息
典型错误
// 错误示例:未排序直接拼接
String raw = params.entrySet().stream()
                 .map(e -> e.getKey()+"="+e.getValue())
                 .collect(Collectors.joining("&")); 
// 会导致与服务端计算不一致

通过规范的签名验证,可有效防御约70%的常见API攻击行为。


HTTPS 证书处理

概念定义

HTTPS 证书(SSL/TLS 证书)是用于验证服务器身份并加密客户端与服务器之间通信的数字证书。它由受信任的证书颁发机构(CA)签发,确保数据传输的安全性。

使用场景
  1. 安全通信:防止数据在传输过程中被窃取或篡改。
  2. 身份验证:确保客户端连接到的是真实的服务器,而非中间人攻击的伪造服务器。
  3. 合规性:满足某些行业(如金融、医疗)对数据安全的法律要求。
常见误区或注意事项
  1. 自签名证书:虽然可以用于测试环境,但在生产环境中不被浏览器信任,可能导致安全警告。
  2. 证书过期:证书通常有有效期(如1年),过期后需及时更新,否则会导致连接失败。
  3. 证书链不完整:服务器配置时需包含完整的证书链(根证书、中间证书、服务器证书),否则可能引发信任问题。
  4. 混合内容:HTTPS 页面中加载 HTTP 资源(如图片、脚本)会降低安全性,浏览器可能阻止加载。
示例代码(Java 中处理 HTTPS 证书)
1. 忽略证书验证(仅用于测试环境)
import javax.net.ssl.*;
import java.security.cert.X509Certificate;

public class SSLUtil {
    public static void disableCertificateValidation() {
        try {
            // 创建信任所有证书的 TrustManager
            TrustManager[] trustAllCerts = new TrustManager[] {
                new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                    public void checkServerTrusted(X509Certificate[] certs, String authType) {}
                }
            };

            // 应用自定义 TrustManager
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // 忽略主机名验证
            HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2. 加载自定义证书(如自签名证书)
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

public class SSLUtil {
    public static SSLContext loadCustomCertificate(String certPath, String password) throws Exception {
        // 加载证书文件
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream fis = new FileInputStream(certPath)) {
            keyStore.load(fis, password.toCharArray());
        }

        // 初始化 TrustManagerFactory
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(keyStore);

        // 创建 SSLContext
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        return sslContext;
    }
}
3. 使用示例(结合 HttpClient)
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

public class HttpsClientExample {
    public static void main(String[] args) throws Exception {
        // 加载自定义证书(可选)
        SSLContext sslContext = SSLUtil.loadCustomCertificate("path/to/cert.p12", "password");

        // 创建 HttpClient
        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLContext(sslContext)
                .build()) {

            // 发送 HTTPS 请求
            HttpGet httpGet = new HttpGet("https://example.com/api");
            httpClient.execute(httpGet, response -> {
                System.out.println("Response Code: " + response.getStatusLine().getStatusCode());
                return null;
            });
        }
    }
}

代理服务器配置

概念定义

代理服务器(Proxy Server)是客户端和目标服务器之间的中间服务器,用于转发请求和响应。在Java中配置代理服务器可以让应用程序通过代理访问网络资源。

使用场景
  1. 企业内网访问外网资源
  2. 访问被限制的地理区域内容
  3. 提高访问速度(缓存代理)
  4. 匿名访问(隐藏真实IP)
常见配置方式
1. 系统属性全局配置
System.setProperty("http.proxyHost", "proxy.example.com");
System.setProperty("http.proxyPort", "8080");
// 对于HTTPS
System.setProperty("https.proxyHost", "proxy.example.com");
System.setProperty("https.proxyPort", "8080");
2. 针对单个连接配置
Proxy proxy = new Proxy(Proxy.Type.HTTP, 
    new InetSocketAddress("proxy.example.com", 8080));
URL url = new URL("http://target.example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
3. 需要认证的代理
Authenticator.setDefault(new Authenticator() {
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication("username", "password".toCharArray());
    }
});
注意事项
  1. 代理服务器地址和端口必须正确
  2. 认证信息需要妥善保管
  3. HTTPS代理可能需要额外证书配置
  4. 连接超时设置应考虑代理服务器的响应时间
示例代码(完整HTTP请求)
// 设置代理
System.setProperty("http.proxyHost", "192.168.1.100");
System.setProperty("http.proxyPort", "3128");

// 执行HTTP请求
URL url = new URL("http://example.com/api");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");

// 读取响应
try(BufferedReader in = new BufferedReader(
    new InputStreamReader(con.getInputStream()))) {
    String inputLine;
    StringBuilder content = new StringBuilder();
    while ((inputLine = in.readLine()) != null) {
        content.append(inputLine);
    }
    System.out.println(content.toString());
}
高级配置

对于更复杂的场景(如SOCKS代理、动态代理切换),可以考虑使用:

  1. Apache HttpClient的代理配置
  2. OkHttp的ProxySelector
  3. 网络库特定的代理配置接口

连接超时重试

概念定义

连接超时重试是指在网络请求(如 GET、POST)因连接超时失败后,自动或手动重新发起请求的机制。超时通常由网络延迟、服务器负载过高或防火墙限制等原因引起。重试策略可提高请求成功率,尤其在弱网络环境下。

使用场景
  1. 高延迟网络:如移动网络或跨国请求。
  2. 服务不稳定:目标服务器可能短暂不可用。
  3. 关键业务请求:如支付、订单提交等需确保成功的操作。
常见误区
  1. 无限重试:可能导致系统资源耗尽或雪崩效应。应设置最大重试次数(如 3 次)。
  2. 无间隔重试:连续重试可能加剧服务器压力。建议采用指数退避策略(如首次间隔 1s,第二次 2s,第三次 4s)。
  3. 忽略错误类型:仅对可重试错误(如超时、5xx 错误)重试,而非 4xx 错误(如 404)。
示例代码(Java)
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class RetryExample {
    private static final int MAX_RETRIES = 3;
    private static final long BASE_DELAY_MS = 1000; // 初始延迟 1s

    public static String sendGetWithRetry(String url) throws IOException, InterruptedException {
        int retryCount = 0;
        IOException lastException = null;

        while (retryCount < MAX_RETRIES) {
            try {
                HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000); // 连接超时 5s
                conn.setReadTimeout(5000);    // 读取超时 5s

                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    return "Request succeeded after " + retryCount + " retries";
                }
            } catch (IOException e) {
                lastException = e;
                retryCount++;
                if (retryCount < MAX_RETRIES) {
                    long delay = BASE_DELAY_MS * (1 << (retryCount - 1)); // 指数退避
                    Thread.sleep(delay);
                }
            }
        }
        throw lastException; // 重试耗尽后抛出最后一次异常
    }

    public static void main(String[] args) {
        try {
            String result = sendGetWithRetry("https://example.com/api");
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
注意事项
  1. 幂等性:确保重试的请求是幂等的(如 GET 请求天然幂等,POST 需服务端支持)。
  2. 日志记录:记录重试次数和延迟,便于问题排查。
  3. 断路器模式:结合熔断机制(如 Netflix Hystrix),在连续失败时暂停重试。

文件下载进度监控

概念定义

文件下载进度监控是指在通过网络下载文件时,实时跟踪和显示下载进度的技术。通常以百分比或已下载字节数/总字节数的形式展示,提升用户体验。

使用场景
  1. 大文件下载(如视频、安装包)
  2. 需要显示实时进度的应用(如云盘客户端)
  3. 需要支持断点续传的场景
  4. 需要预估剩余下载时间的场景
实现方式(Java示例)
基础HTTPURLConnection实现
URL url = new URL("http://example.com/file.zip");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();

// 获取文件总大小
long fileSize = conn.getContentLengthLong();
try (InputStream is = conn.getInputStream();
     FileOutputStream fos = new FileOutputStream("local.zip")) {
    
    byte[] buffer = new byte[4096];
    long downloaded = 0;
    int read;
    
    while ((read = is.read(buffer)) != -1) {
        fos.write(buffer, 0, read);
        downloaded += read;
        
        // 计算并显示进度
        double progress = (double) downloaded / fileSize * 100;
        System.out.printf("下载进度: %.2f%%\n", progress);
    }
}
使用Apache HttpClient
CloseableHttpClient client = HttpClients.createDefault();
HttpGet request = new HttpGet("http://example.com/file.zip");

try (CloseableHttpResponse response = client.execute(request);
     InputStream is = response.getEntity().getContent()) {
    
    long fileSize = response.getEntity().getContentLength();
    long downloaded = 0;
    byte[] buffer = new byte[4096];
    int read;
    
    while ((read = is.read(buffer)) != -1) {
        downloaded += read;
        double progress = (double) downloaded / fileSize * 100;
        System.out.printf("进度: %.2f%%\n", progress);
    }
}
注意事项
  1. Content-Length头:服务器必须返回正确的Content-Length,否则无法计算准确进度
  2. 分块传输编码:当使用Transfer-Encoding: chunked时,无法预先知道文件总大小
  3. 进度更新频率:过于频繁的进度更新会影响性能,建议设置阈值(如每1%更新一次)
  4. 线程安全:在GUI应用中,进度更新需要切换到UI线程
  5. 断点续传:需要处理Range请求和206 Partial Content响应
高级技巧
  1. 使用观察者模式实现进度回调
  2. 结合Speedometer计算下载速度
  3. 使用环形缓冲区减少进度更新频率
  4. 对压缩文件进行流式解压时的进度计算
常见问题解决方案
  1. 未知文件大小:显示已下载字节数而非百分比
  2. 进度回退:可能由于网络重连导致,应保存最大进度值
  3. 长时间卡住:设置超时机制和心跳检测
  4. 进度显示跳跃:使用平滑算法处理进度变化

第三方 API 对接规范

概念定义

第三方 API 对接规范是指在调用外部服务提供的 API 时,需要遵循的一系列技术约定和流程标准。这些规范通常包括接口协议、认证方式、数据格式、错误处理、限流策略等,确保双方系统能稳定、安全地交互。

核心要素
1. 接口协议
  • HTTP/HTTPS:主流选择,需明确使用 GET/POST/PUT/DELETE 等方法。
  • RESTful:推荐遵循资源化设计(如 /users/{id})。
  • WebSocket:适用于实时通信场景。
2. 认证与授权
  • API Key:简单但安全性较低,需通过请求头或参数传递。
  • OAuth 2.0:适用于需要用户授权的场景(如获取第三方数据)。
  • JWT:无状态 token,适合微服务间认证。
3. 数据格式
  • JSON:主流格式,需统一字段命名风格(如 snake_casecamelCase)。
  • XML:少数传统系统仍在使用。
  • Protocol Buffers:高性能二进制格式,适合内部服务。
4. 请求与响应规范
  • 请求头:明确 Content-Type(如 application/json)、Authorization 等。
  • 响应码:遵循 HTTP 状态码(如 200 成功,400 参数错误,500 服务异常)。
  • 错误信息:统一返回结构,包含 codemessage 字段。
5. 限流与配额
  • Rate Limiting:如 X-RateLimit-Limit 头标识每秒请求数上限。
  • 配额控制:按日/月限制调用次数,超出返回 429 Too Many Requests
常见问题与注意事项
1. 安全性
  • HTTPS 必选:避免明文传输敏感数据。
  • 敏感参数加密:如密码、银行卡号等。
  • IP 白名单:限制可调用 API 的服务器 IP。
2. 兼容性
  • 版本控制:通过 URL(如 /v1/users)或请求头(如 Accept-Version: v1)区分。
  • 废弃通知:提前公告旧版本停用时间。
3. 日志与监控
  • 完整记录:请求/响应数据、耗时、调用者 IP。
  • 报警机制:针对异常状态码(如 5xx)实时通知。
示例代码(Java)
// 使用 HttpClient 调用第三方 API(GET 请求)
public String callThirdPartyAPI(String apiUrl, String apiKey) throws IOException {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(apiUrl))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .GET()
            .build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() == 200) {
        return response.body();
    } else {
        throw new RuntimeException("API 调用失败: " + response.statusCode());
    }
}

你可能感兴趣的:(Java,java,开发语言,编程基础,设计模式)