使用Apache HttpClient 4.x进行进行一次Http请求
第1步:导入依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
第2步:构造请求并获取返回Json串
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
@Slf4j
public class HttpPostUtils {
/
* 该方法用于发送一个HTTP POST请求,并附带JSON格式的请求数据。它包含了重试机制来应对可能的网络问题。
*
* @param uri 请求地址,不能为空字符串
* @param json 请求体,必须是格式正确的JSON字符串,同样不能为空字符串
* @return 如果HTTP响应状态码为200 OK,则返回服务器返回的数据(已解码为UTF-8格式的字符串);否则抛出异常
* @throws IOException 当无法成功打开或关闭HTTP连接时抛出此异常
* @throws ParseException 如果服务器返回的数据无法被正确解析时抛出此异常
*/
public String executeWithRetryHttpPostJson(String uri, String json) throws IOException, ParseException {
if (StringUtils.isAnyBlank(uri, json)) {
throw new IllegalArgumentException("URI or JSON data is blank.");
}
MyCustomRetryHandler myCustomRetryHandler = new MyCustomRetryHandler(3);
CloseableHttpClient client = HttpClients.custom().setRetryHandler(myCustomRetryHandler).build();
HttpPost httpPost = new HttpPost(uri);
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(5000)
.setConnectTimeout(5000)
.setSocketTimeout(5000)
.build();
httpPost.setConfig(config);
try (CloseableHttpResponse response = client.execute(httpPost, HttpClientContext.create())) {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new ParseException("Unexpected response status: " + response.getStatusLine().getStatusCode());
}
HttpEntity responseEntity = response.getEntity();
return EntityUtils.toString(responseEntity, Consts.UTF_8.name());
} catch (IOException | ParseException e) {
log.error("Error occurred during HTTP request", e);
throw e;
} finally {
client.close();
}
}
}
第3步:自定义失败重试机制
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Arrays;
public class MyCustomRetryHandler implements HttpRequestRetryHandler {
private final int maxRetries;
public MyCustomRetryHandler(int maxRetries) {
this.maxRetries = maxRetries;
}
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
if (executionCount >= maxRetries) {
return false;
}
if (exception instanceof UnknownHostException) {
return false;
}
if (exception instanceof SSLHandshakeException) {
return false;
}
if (exception instanceof SSLException) {
return false;
}
if (exception instanceof NoHttpResponseException) {
return true;
}
if (exception instanceof ConnectionPoolTimeoutException) {
return true;
}
if (exception instanceof ConnectTimeoutException) {
return true;
}
if (exception instanceof SocketTimeoutException) {
return true;
}
if (exception instanceof InterruptedIOException) {
return false;
}
if (exception instanceof HttpHostConnectException) {
return true;
}
if (exception instanceof ConnectException) {
return false;
}
if (exception instanceof SocketException) {
return true;
}
HttpCoreContext coreContext = HttpCoreContext.adapt(context);
HttpRequest request = coreContext.getRequest();
if (request instanceof HttpRequestWrapper wrapper) {
HttpUriRequest originalRequest = (HttpUriRequest) wrapper.getOriginal();
String method = originalRequest.getMethod();
if ("GET".equals(method) || "HEAD".equals(method) || "PUT".equals(method) || "DELETE".equals(method)) {
return true;
}
}
if (!(request instanceof HttpEntityEnclosingRequest)) {
return true;
}
int[] RETRYABLE_STATUS_CODES = {500, 503, 504};
Object attribute = context.getAttribute(HttpClientContext.HTTP_RESPONSE);
if (attribute instanceof CloseableHttpResponse response) {
StatusLine statusLine = response.getStatusLine();
return Arrays.stream(RETRYABLE_STATUS_CODES).anyMatch(status -> status == statusLine.getStatusCode());
}
return false;
}
}
第4步:解析返回的Json串
方法1:个人已知最好方式
private static List<User> getResJsonToUsers2(String resJson) {
JSONObject jsonObject = JSON.parseObject(resJson, Feature.OrderedField);
if (jsonObject == null || jsonObject.isEmpty()) {
return new ArrayList<>();
}
List<String> fields = new ArrayList<>(jsonObject.keySet());
int size = jsonObject.getJSONArray(fields.get(0)).size();
List<User> res = new ArrayList<>(size);
try {
PropertyDescriptor[] properties = getPropertyDescriptors(User.class);
for (int i = 0; i < size; i++) {
User tempUser = new User();
for (PropertyDescriptor property : properties) {
String propertyName = property.getName();
if ("serialVersionUID".equals(propertyName)) {
continue;
}
if (!jsonObject.containsKey(propertyName)) {
continue;
}
Method writeMethod = property.getWriteMethod();
Object value = jsonObject.getJSONArray(propertyName).get(i);
writeMethod.invoke(tempUser, value);
}
res.add(tempUser);
}
} catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException("Failed to parse JSON to User objects", e);
}
return res;
}
private static Map<Class<?>, PropertyDescriptor[]> cachedDescriptors = new HashMap<>();
private static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws IntrospectionException {
synchronized (cachedDescriptors) {
if (!cachedDescriptors.containsKey(clazz)) {
cachedDescriptors.put(clazz, Introspector.getBeanInfo(clazz, Object.class).getPropertyDescriptors());
}
return cachedDescriptors.get(clazz);
}
}
方法2:做了解,每次都反射,性能不行
private static List<User> getResJsonToUsers(String resJson) {
JSONObject jsonObject = JSON.parseObject(resJson, Feature.OrderedField);
if (jsonObject == null || jsonObject.isEmpty()) {
return new ArrayList<>();
}
List<String> fields = new ArrayList<>(jsonObject.keySet());
int size = jsonObject.getJSONArray(fields.get(0)).size();
List<User> users = new ArrayList<>();
for (int i = 0; i < size; i++) {
User tempUser = new User();
for (String field : fields) {
Object value = jsonObject.getJSONArray(field).get(i);
ReflectUtils.setFieldValue(tempUser, field, value);
}
users.add(tempUser);
}
return users;
}
/
* 通过反射机制动态地为给定对象的指定属性设置值。
*
* @param <T> 对象类型
* @param object 需要修改属性的对象,不能为空
* @param fieldName 需要设置的属性名称,不能为空且不能是空字符串
* @param value 将要赋予属性的新值
* @throws IllegalArgumentException 如果传入的对象或字段名为空,或者找不到与字段名对应的setter方法时抛出异常
* @throws RuntimeException 在尝试通过反射设置属性值时发生任何反射相关的异常(如IllegalAccessException, InvocationTargetException, IntrospectionException)时,包装原始异常并抛出RuntimeException
*/
public static <T> void setFieldValue(final T object, final String fieldName, final Object value) {
if (object == null || fieldName == null || fieldName.isEmpty()) {
throw new IllegalArgumentException("Object or fieldName cannot be null/empty");
}
try {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(fieldName, object.getClass());
Method writeMethod = propertyDescriptor.getWriteMethod();
if (writeMethod != null) {
writeMethod.invoke(object, value);
} else {
System.err.println("未找到设置方法: " + fieldName);
}
} catch (IllegalAccessException | InvocationTargetException | IntrospectionException e) {
throw new RuntimeException("通过反射设置属性时发生异常: " + e.getMessage(), e);
}
}
方法3:仅了解,fieldObj.setAccessible(true)一般禁用
private static List<User> getResJsonToUsers3(String resJson) {
JSONObject jsonObject = JSON.parseObject(resJson, Feature.OrderedField);
if (jsonObject == null || jsonObject.isEmpty()) {
return new ArrayList<>();
}
List<String> fields = new ArrayList<>(jsonObject.keySet());
int size = jsonObject.getJSONArray(fields.get(0)).size();
List<User> users = new ArrayList<>();
for (int i = 0; i < size; i++) {
User tempUser = new User();
for (String field : fields) {
try {
Object value = jsonObject.getJSONArray(field).get(i);
Field fieldObj = tempUser.getClass().getDeclaredField(field);
fieldObj.setAccessible(true);
fieldObj.set(tempUser, value);
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException("Failed to parse JSON to User objects", e);
}
}
users.add(tempUser);
}
return users;
}
模拟测试
public static void main(String[] args) {
Map<String, List<String>> map = new HashMap<>();
map.put("name", Arrays.asList("name1", "name2", "name3"));
map.put("age", Arrays.asList("11", "23", "23"));
map.put("num", Arrays.asList("123", "234", "345"));
String resJson = JsonUtils.toJson(map);
System.out.println(resJson);
List<User> users = getResJsonToUsers(resJson);
List<User> users2 = getResJsonToUsers2(resJson);
List<User> users3 = getResJsonToUsers3(resJson);
System.out.println(users);
System.out.println(users2);
System.out.println(users3);
}
参考
使用Apache HttpClient 4.x进行异常重试
Need 学习消化
二十六、CloseableHttpClient的使用和优化