HttpClilent整合Spring使用

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使用【配置和代码】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值