HttpClilent整合Spring使用
1. 描述
HttpClient是appache组织开发的,感觉也比较全面,不管做爬虫还是特殊网络请求都还不错,如果没有基础的同学百度下吧 。说实话,httpclient用法确实挺简单的,不过当这些玩具代码要结合到实际开发中时还是要注意许多细节,为了加快写博客速度我就copy传智的玩具demo了,安静的夜晚码字好。。。安谧??
2. 依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<httpclient.version>4.3.5</httpclient.version>
</dependency
3. 从HTTP连接管理池中获取一个【连接】才是【最重要】的,Demo转成Spring配置方式
package cn.itcast.httpclient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
public class HttpConnectManager {
public static void main(String[] args) throws Exception {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 设置最大连接数
cm.setMaxTotal(200);
// 设置每个主机地址的并发数
cm.setDefaultMaxPerRoute(20);
doGet(cm);
doGet(cm);
}
public static void doGet(HttpClientConnectionManager cm) throws Exception {
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
// 创建http GET请求
HttpGet httpGet = new HttpGet("http://www.baidu.com/");
CloseableHttpResponse response = null;
try {
// 执行请求
response = httpClient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println("内容长度:" + content.length());
}
} finally {
if (response != null) {
response.close();
}
// 此处不能关闭httpClient,如果关闭httpClient,连接池也会销毁
// httpClient.close();
}
}
}
扫一眼这个代码不就是获取连接发一个GET请求返回数据嘛,当然我们关注点需要集中在获取一个请求连接。我们要这样去理解,在Spring中以bean方式获取一个连接,这样是不是很方便,我们操作因为涉及到连接,对比数据源当然也能理解使用一个连接管理池 【PoolingHttpClientConnectionManager】,毕竟“池”的概念太通用了。从pool中借一个可关闭连接【CloseableHttpClient】有借有还工作起来效率才高。
- 好了开始正题,创建HttpClient连接管理器
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 设置最大连接数
cm.setMaxTotal(200);
// 设置每个主机地址的并发数
cm.setDefaultMaxPerRoute(20);
<!-- 创建httpclient管理器 -->
<bean id="connectionManager"
class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager">
<!-- 最大连接数 -->
<property name="maxTotal" value="${http.maxTotal}" />
<!-- 每个连接并发数 -->
<property name="defaultMaxPerRoute" value="${http.DefaultMaxPerRoute}" />
</bean>
这个很好理解吧,创建了一个HTTP连接管理池,beanId=“connectionManager”,最大连接数和并发数在配置文件中配置:
http.maxTotal=200
http.DefaultMaxPerRoute=50
- 获取HttpClientBuilder和CloseableHttpClient
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
看看源码 HttpClients.custom()的实现:
/**
* Creates builder object for construction of custom
* {@link CloseableHttpClient} instances.
*/
public static HttpClientBuilder custom() {
return HttpClientBuilder.create();
}
=========================================================
public static HttpClientBuilder create() {
return new HttpClientBuilder();
}
无非就是 new HttpClientBuilder();ok那么我们可以用配置来解决:
<!-- HttpClient构建器 -->
<bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder">
<!-- 注入http_client管理器 -->
<property name="ConnectionManager" ref="connectionManager" />
</bean>
至于property name="ConnectionManager"这个配置,不就是HttpClientBuilder.setConnectionManager(cm);最后一个HttpClientBuilder.build()对应源码:
public CloseableHttpClient build(){
//该处实现代码省略。。。
// 返回了我们所需的CloseableHttpClient
// 不过需要注意下:该方法不是静态方法,为什么呢?因为我们所需的HttpClient连接是一种无状态请求
// 一次请求一次打开和关闭,需要创建新的连接,所以不能用static修饰
}
对应配置文件:
<!--HttpCient对象 -->
<bean id="httpclient" class="org.apache.http.impl.client.CloseableHttpClient"
factory-bean="httpClientBuilder" factory-method="build" scope="prototype">
</bean>
factory-method="build"这个标签用HttpClientBuilder的工厂方法build创建CloseableHttpClient ,还有值得注意的是:scope=“prototype”,一定不是sington
4. 设置CloseableHttpClient的请求配置信息
前面3个步骤拿到了CloseableHttpClient,但还得为HttpClient对象设置请求配置信息,其中无非就是从pool中获取超时设置,请求本身的某些生命周期配置,Demo如下:
public class RequestConfigDemo {
public static void main(String[] args) throws Exception {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建http GET请求
HttpGet httpGet = new HttpGet("http://www.baidu.com/");
// 构建请求配置信息
RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) // 创建连接的最长时间
.setConnectionRequestTimeout(500) // 从连接池中获取到连接的最长时间
.setSocketTimeout(10 * 1000) // 数据传输的最长时间
.setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用
.build();
// 设置请求配置信息
httpGet.setConfig(config);
CloseableHttpResponse response = null;
try {
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(content);
}
} finally {
if (response != null) {
response.close();
}
httpclient.close();
}
}
}
========================================================================
// 构建请求配置信息
RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) // 创建连接的最长时间
.setConnectionRequestTimeout(500) // 从连接池中获取到连接的最长时间
.setSocketTimeout(10 * 1000) // 数据传输的最长时间
.setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用
.build();
// 设置请求配置信息
httpGet.setConfig(config); =========================================================================
这段就是核心了,RequestConfig.custom()对应源码:
public static RequestConfig.Builder custom() {
return new Builder();
}
这个配置就很简单了,构建Builder的Spring配置如下:
<!-- 构建请求配置信息Builder -->
<bean id="requestConfigBuilder" class="org.apache.http.client.config.RequestConfig.Builder">
<!-- 创建连接的最长时间 -->
<property name="connectTimeout" value="${http.connectTimeout}" />
<!-- 从连接池中获取到连接的最长时间 -->
<property name="connectionRequestTimeout" value="${http.connectionRequestTimeout}" />
<!-- 数据传输的最长时间 -->
<property name="socketTimeout" value="${http.socketTimeout}" />
<!-- 提交请求前测试连接是否可用 -->
<property name="staleConnectionCheckEnabled" value="${http.staleConnectionCheckEnabled}" />
</bean>
对应properties配置如下:
http.connectTimeout=1000
http.connectionRequestTimeout=500
http.socketTimeout=10000
http.staleConnectionCheckEnabled=true
对应Builder.build();源码如下:
public RequestConfig build() {
return new RequestConfig(
expectContinueEnabled,
proxy,
localAddress,
staleConnectionCheckEnabled,
cookieSpec,
redirectsEnabled,
relativeRedirectsAllowed,
circularRedirectsAllowed,
maxRedirects,
authenticationEnabled,
targetPreferredAuthSchemes,
proxyPreferredAuthSchemes,
connectionRequestTimeout,
connectTimeout,
socketTimeout);
}
好了该拿到RequestConfig 对象了,其实跟CloseableHttpClient对象一样都是通过工厂方法去获取:
<bean id="requestConfig" class="org.apache.http.client.config.RequestConfig"
factory-bean="requestConfigBuilder" factory-method="build">
</bean>
这里不需要像CloseableHttpClient那样scope=“prototype”,配置属性一份就够了
至此我们有了CloseableHttpClient和RequestConfig 的配置,在开发中可以随意获取了。。。。
5. 编写HttpClient工具类
- 1.最基本的Get请求:
package com.taotao.common.service;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.taotao.common.httpclient.HttpResult;
@Service
public class ApiService implements BeanFactoryAware{
@Autowired(required=false)
private RequestConfig requestConfig;
private BeanFactory beanFactory;
/**
* 200返回数据,其它为null
* @author: kaixing
* @createTime: 2018年9月13日 下午10:40:00
* @history:
* @param url
* @return
* @throws ClientProtocolException
* @throws IOException String
*/
public String doGet(String url) throws ClientProtocolException, IOException{
// 创建http GET请求
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(this.requestConfig);
CloseableHttpResponse response = null;
try {
// 执行请求
response = createHttpClient().execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
return EntityUtils.toString(response.getEntity(), "UTF-8");
}
} finally {
if (response != null) {
response.close();
}
}
return null;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
private CloseableHttpClient createHttpClient(){
return this.beanFactory.getBean(CloseableHttpClient.class);
}
}
可能最不能理解的就是实现BeanFactoryAware接口了,解释如下:
1.很明显ApiService 作为一个工具服务类,在Spring容器中只有一个实例,@Service注解也说明如此。我们很有可能会想到将CloseableHttpClient以注入方式注入进来,eg:
@Autowired
private CloseableHttpClient httpclient;
因为我们前面不是配置了CloseableHttpClient 这个对象吗,所以这样做有问题吗?
2.如果我们以注入的方式去获取CloseableHttpClient ,想想我们是不是永远只能获取到同一个CloseableHttpClient 对象,为什么?我们不是将CloseableHttpClient 设置成多实例的吗?。。。因为在一个service中注入另一个对象,在初始化时就已经确定了,这个对象怎么可能不同!!!!
3.怎么解决呢?所以实现BeanFactoryAware这个接口就有原因了,看源码:
* @author Rod Johnson
* @author Chris Beams
* @since 11.03.2003
* @see BeanNameAware
* @see BeanClassLoaderAware
* @see InitializingBean
* @see org.springframework.context.ApplicationContextAware
*/
public interface BeanFactoryAware extends Aware {
/**
* Callback that supplies the owning factory to a bean instance.
* <p>Invoked after the population of normal bean properties
* but before an initialization callback such as
* {@link InitializingBean#afterPropertiesSet()} or a custom init-method.
* @param beanFactory owning BeanFactory (never {@code null}).
* The bean can immediately call methods on the factory.
* @throws BeansException in case of initialization errors
* @see BeanInitializationException
*/
void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
这个接口给我们提供了BeanFactory 这个对象,玛德这个对象实在太重要了!!!!
private BeanFactory beanFactory;
...
...
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
//获取到spring提供的该对象保存起来
}
private CloseableHttpClient createHttpClient(){
return this.beanFactory.getBean(CloseableHttpClient.class);
// 该处是不是完美解决拿不到多个实例的问题
}
所以总结下就是在单例Service中想要获取多实例的注入属性,注入方式肯定不行的,实现BeanFactory 类似接口是ok的。
6. 结语
想要完整代码的可以参考 HttpClilent整合Spring使用【配置和代码】