简介
HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
最近我所负责的项目需要向其他项目调用接口,用到了HttpClients实现请求调用,并且对消息反馈做业务功能。下面总结一下使用方式!
HttpClient的主要功能
- 实现了所有 HTTP 的方法(GET、POST、PUT、HEAD、DELETE、HEAD、OPTIONS 等)
- 支持 HTTPS 协议
- 支持代理服务器(Nginx等)等
- 支持自动(跳转)转向…
实战
准备环境
- Springboot + JDK1.8
引入依赖
<!-- Json工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<!--解析json数据工具类,如JsonPath-->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
<!-- http工具连接依赖 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
响应体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Response implements Serializable {
private String code;
private String message;
private Object content;
}
http服务端代码
这是一个post请求,使用@RequestBody方式接收一个String类型的参数!
注意,请求的时候Content-Type
需要设置为application/json;charset=utf-8
@Slf4j
@RestController
@RequestMapping("/hzy")
public class HzyController {
@PostMapping("/receiveData")
public Response receiveData(@RequestBody String jsonTask) {
SaveOrUpdateTask task = JSON.parseObject(jsonTask, SaveOrUpdateTask.class);
System.out.println(task);
log.info("接收项目管理数据:{}", jsonTask);
// 这里可以处理接受到的数据
...
return new Response("00000","成功", "返回成功");
}
}
http客户端调用示例(Post请求)
客户端这里使用了@Async异步执行方法
使用方式:
- @Async声明在需要异步执行的方法上
- @EnableAsync声明在启动类上即可
@Async
@Override
public void pushTaskToHzy(SaveOrUpdateTask task) {
HttpPost httpPost = new HttpPost("http://192.168.1.31:8081/hzy/receiveData");
// 数据转换成json字符串
String jsonString = JSON.toJSONString(task);
StringEntity entity = new StringEntity(jsonString, "UTF-8");
// 把数据放入请求体中
httpPost.setEntity(entity);
// 由于服务端是post请求使用了@RequestBody注解,这边设置Content-Type
httpPost.setHeader("Content-Type", "application/json;charset=utf8");
HttpResponse response;
try {
// 执行post方法
response = HttpClients.createDefault().execute(httpPost);
// 获取响应体
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
// 解析响应体
String res = EntityUtils.toString(responseEntity, Charsets.UTF_8);
log.info("响应内容为:{}", res);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
// 使用json解析工具,获取数据
String code = JsonPath.read(res, "$.code");
String message = JsonPath.read(res, "$.message");
// 这里做后续的业务功能处理
......
return;
}
throw new BusinessException("响应状态失败,状态码:" + statusCode);
}
} catch (Exception e) {
log.error("接收消息反馈失败了", e);
throw new BusinessException("接收消息反馈失败了.原因:" + e.getMessage());
}
}
一个简单的http post调用远程接口就完成了,那么假如网络不稳定,请求调用了之后无响应获取出现异常了,我们想要完成http远程调用的重试,当然httpclient自带的就有这种实现方式,不过感觉有些麻烦,还是去找了别的实现方式,就是使用spring-retry
这个spring提供的重试工具。
spring-retry依赖如下
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>
Spring提供的声明式的重试类库spring-retry
,既然是声明式,那么使用起来是很简单的了!
先来介绍两个注解
- @Retryable:这个注解可以去声明重试的一些机制,比如重试次数、重试间隔之类
- @Recover:用于当重试全部失败后的处理
@Retryable具体的方法参数可以参考@Retryable具体的方法参数,介绍的还是挺详细的 0.0
spring-retry的使用
实现步骤:
- @Retryable、@Recover定义在方法上
- @EnableRetry声明在启动类上
@Retryable参数
- value = BusinessException.class,value表示当出现什么异常的时候进行重试,可以自定义一个异常,程序可能出现问题的地方直接抛出异常,然后retry组件会自动捕获到并且执行方法重试。
- maxAttempts = 4,表示这个方法会一共执行4次(如果一直失败)
启动类
@EnableAsync
@EnableRetry
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
方法
@Async
@Override
@Retryable(value = BusinessException.class,maxAttempts = 4,backoff = @Backoff(delay = 2000,multiplier = 1.5))
public void pushTaskToHzy(SaveOrUpdateTask task) {
HttpPost httpPost = new HttpPost("http://192.168.1.31:8081/hzy/receiveData");
// 数据转换成json字符串
String jsonString = JSON.toJSONString(task);
StringEntity entity = new StringEntity(jsonString, "UTF-8");
// 把数据放入请求体中
httpPost.setEntity(entity);
// 由于服务端是post请求使用了@RequestBody注解,这边设置Content-Type
httpPost.setHeader("Content-Type", "application/json;charset=utf8");
HttpResponse response;
try {
// 执行post方法
response = HttpClients.createDefault().execute(httpPost);
// 获取响应体
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
// 解析响应体
String res = EntityUtils.toString(responseEntity, Charsets.UTF_8);
log.info("响应内容为:{}", res);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
// 使用json解析工具,获取数据
String code = JsonPath.read(res, "$.code");
String message = JsonPath.read(res, "$.message");
// 这里做后续的业务功能处理
......
return;
}
throw new BusinessException("响应状态失败,状态码:" + statusCode);
}
} catch (Exception e) {
log.error("接收消息反馈失败了", e);
throw new BusinessException("接收消息反馈失败了.原因:" + e.getMessage());
}
}
后续的失败处理
定义一个方法,参数是上面代码抛出的异常类,和上面代码方法中的参数(重要:参数类型一定要一致,否则就直接报错了)
【注意】@Recover 的用法。它要求它注解的方法的返回值必须和@Retryable的注解的方法返回值保持一致,否则@Recover 注解声明的方法不会被调用。
要点:
- @Recover和@Retryable声明的方法返回值要一致
- @Recover和@Retryable声明的方法参数类型要一致(@Recover注解的方法,第一个参数是异常类,其他参数是@Retryable方法上的形参)
=注意:此方法一定要和上面的代码方法放在一个类中=
@Recover
public void failHandler(BusinessException e, SaveOrUpdateTask task) {
log.error("重试失败3次后,处理数据中==", e);
MProjectRefNodeEntity nodeEntity = nodeService.getByCode(task.getSequenceNumber(), null);
nodeEntity.setTaskPushCode(ManagerCloudConstants.PUSH_FAIL);
nodeEntity.setTaskPushMsg(message);
nodeService.updateById(nodeEntity);
}