从Apache HttpClient类库,说一说springboot应用程序中的AutoConfiguration的封装

发布于:2024-05-10 ⋅ 阅读:(27) ⋅ 点赞:(0)

一、背景

在使用httpclient框架请求http接口的时候,我们往往会需要自定义配置httpclient,而非直接使用。

		<dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>

在这里插入图片描述

本文仅以自定义httpclient为例,目的是看在springboot应用程序中,如何写一个AutoConfiguration配置类。

涉及到的类有以下六个:

  • HttpClientAutoConfiguration
  • HttpClientProperties
  • ApacheHttpClientConnectionManagerFactory
  • ApacheHttpClientFactory
  • DefaultApacheHttpClientConnectionManagerFactory
  • DefaultApacheHttpClientFactory

在这里插入图片描述
在这里插入图片描述

二、写一个AutoConfiguration

1、HttpClientAutoConfiguration

HttpClientAutoConfiguration类提供了一个自动化配置HTTP客户端的解决方案,使得开发者可以方便地在Spring Boot应用中使用Apache HttpClient库。通过配置文件和自动配置,它创建了HTTP连接管理器和HTTP客户端,并且安排了定时任务来维护这些连接。


import com.google.common.util.concurrent.ThreadFactoryBuilder;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PreDestroy;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


@Configuration
// 使用配置类HttpClientProperties
@EnableConfigurationProperties(value = {HttpClientProperties.class})
public class HttpClientAutoConfiguration {
    private final ScheduledExecutorService scheduledService;
    private CloseableHttpClient httpClient;

    @Autowired
    public HttpClientAutoConfiguration() {
        // 定义一个单线程池
        this.scheduledService = new ScheduledThreadPoolExecutor(
                1,
                new ThreadFactoryBuilder().setNameFormat("http-client-manager").build(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }

    @Bean
    @ConditionalOnMissingBean
    public ApacheHttpClientConnectionManagerFactory connManFactory() {
        return new DefaultApacheHttpClientConnectionManagerFactory();
    }

    @Bean
    @ConditionalOnMissingBean
    public HttpClientBuilder apacheHttpClientBuilder() {
        return HttpClientBuilder.create();
    }

    @Bean
    @ConditionalOnMissingBean
    public ApacheHttpClientFactory apacheHttpClientFactory(
            HttpClientBuilder builder) {
        return new DefaultApacheHttpClientFactory(builder);
    }

    /**
     * Connection manager
     */
    @Bean
    @ConditionalOnMissingBean
    public HttpClientConnectionManager connectionManager(
            ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
            HttpClientProperties httpClientProperties) {
        final HttpClientConnectionManager connectionManager = connectionManagerFactory
                .newConnectionManager(httpClientProperties.getDisableSslValidation(),
                        httpClientProperties.getMaxConnections(),
                        httpClientProperties.getMaxConnectionsPerRoute(),
                        httpClientProperties.getTimeToLive(),
                        httpClientProperties.getTimeToLiveUnit());


        // 定时关闭过期的HTTP连接
        this.scheduledService.scheduleWithFixedDelay(connectionManager::closeExpiredConnections, 1000,
                httpClientProperties.getConnectionTimerRepeat(), TimeUnit.MILLISECONDS);

        return connectionManager;
    }

    /**
     * Http client
     */
    @Bean
    @ConditionalOnMissingBean
    public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
                                          HttpClientConnectionManager httpClientConnectionManager,
                                          HttpClientProperties httpClientProperties) {
        RequestConfig defaultRequestConfig = RequestConfig.custom()
                .setConnectTimeout(httpClientProperties.getConnectionTimeout())
                .setRedirectsEnabled(httpClientProperties.getFollowRedirects())
                .build();

        this.httpClient = httpClientFactory
                .createBuilder()
                .setDefaultRequestConfig(defaultRequestConfig)
                .setConnectionManager(httpClientConnectionManager)
                .build();
        return this.httpClient;
    }

    /**
     * Destroy
     */
    @PreDestroy
    // @PreDestroy注解的destroy()方法,用于在应用关闭时执行清理工作,关闭线程池scheduledService和httpClient
    public void destroy() throws Exception {
        this.scheduledService.shutdown();
        if (this.httpClient != null) {
            this.httpClient.close();
        }
    }
}

2、HttpClientProperties配置文件

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.concurrent.TimeUnit;

@Data
@ConfigurationProperties(prefix = "httpclient")
public class HttpClientProperties {

    /**
     * Value for disabling SSL validation.
     */
    private Boolean disableSslValidation = false;

    /**
     * Value for max number od connections.
     */
    private Integer maxConnections = 200;

    /**
     * Value for max number od connections per route.
     */
    private Integer maxConnectionsPerRoute = 50;

    /**
     * Value for time to live.
     */
    private Long timeToLive = 900L;

    /**
     * Time to live unit.
     */
    private TimeUnit timeToLiveUnit = TimeUnit.SECONDS;

    /**
     * Value for following redirects.
     */
    private Boolean followRedirects = true;

    /**
     * Value for connection timeout.
     */
    private Integer connectionTimeout = 2000;

    /**
     * Value for connection timer repeat.
     */
    private Integer connectionTimerRepeat = 3000;
}

3、定义两个工厂接口

  • ApacheHttpClientConnectionManagerFactory
import org.apache.http.conn.HttpClientConnectionManager;

import java.util.concurrent.TimeUnit;

public interface ApacheHttpClientConnectionManagerFactory {
    String HTTP_SCHEME = "http";

    /**
     * Scheme for HTTPS based communication.
     */
    String HTTPS_SCHEME = "https";

    /**
     * Creates a new {@link HttpClientConnectionManager}.
     *
     * @param disableSslValidation   If true, SSL validation will be disabled.
     * @param maxTotalConnections    The total number of connections.
     * @param maxConnectionsPerRoute The total number of connections per route.
     * @param timeToLive             The time a connection is allowed to exist.
     * @param timeUnit               The time unit for the time-to-live value.
     *                               manager.
     * @return A new {@link HttpClientConnectionManager}.
     */
    HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
                                                     int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive,
                                                     TimeUnit timeUnit);
}

  • ApacheHttpClientFactory
import org.apache.http.impl.client.HttpClientBuilder;

public interface ApacheHttpClientFactory {
    /**
     * Create builder
     *
     * @return HttpClientBuilder
     */
    HttpClientBuilder createBuilder();

4、工厂接口的实现

  • DefaultApacheHttpClientConnectionManagerFactory
import lombok.extern.slf4j.Slf4j;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

@Slf4j
public class DefaultApacheHttpClientConnectionManagerFactory implements ApacheHttpClientConnectionManagerFactory {

    @Override
    public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
                                                            int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive,
                                                            TimeUnit timeUnit) {
        RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
                .register(HTTP_SCHEME, PlainConnectionSocketFactory.INSTANCE);

        if (disableSslValidation) {
            try {
                final SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null,
                        new TrustManager[]{new DisabledValidationTrustManager()},
                        new SecureRandom());
                registryBuilder.register(HTTPS_SCHEME, new SSLConnectionSocketFactory(
                        sslContext, NoopHostnameVerifier.INSTANCE));
            } catch (NoSuchAlgorithmException | KeyManagementException e) {
                log.warn("Error creating SSLContext", e);
            }
        } else {
            registryBuilder.register("https", SSLConnectionSocketFactory.getSocketFactory());
        }

        final Registry<ConnectionSocketFactory> registry = registryBuilder.build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
                registry, null, null, null, timeToLive, timeUnit);
        connectionManager.setMaxTotal(maxTotalConnections);
        connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);

        return connectionManager;
    }

    static class DisabledValidationTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }
}

  • DefaultApacheHttpClientFactory
import org.apache.http.impl.client.HttpClientBuilder;

public class DefaultApacheHttpClientFactory implements ApacheHttpClientFactory {

    private final HttpClientBuilder builder;

    public DefaultApacheHttpClientFactory(HttpClientBuilder builder) {
        this.builder = builder;
    }

    @Override
    public HttpClientBuilder createBuilder() {
        return this.builder.disableContentCompression().disableCookieManagement()
                .useSystemProperties();
    }
}

三、使用自定义httpclient对象

protected final CloseableHttpClient httpClient;


protected AbstractChannel(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }


protected String postJson(String url, String body) throws IOException {
        RequestConfig requestConfig = RequestConfig.custom()
                // 200 ms
                .setConnectionRequestTimeout(1000)
                // 1000ms
                .setSocketTimeout(1000)
                .build();

        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(requestConfig);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("Content-Type", "application/json");

        // Execute
        StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8);
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        CloseableHttpResponse response = this.httpClient.execute(httpPost);

        // Response data.
        String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
        int statusCode = response.getStatusLine().getStatusCode();
        response.close();

        // Http code
        if (statusCode != 200) {
            throw new RuntimeException(String.format("http post failed! status=%d %s", statusCode, responseBody));
        }

        return responseBody;
    }

网站公告

今日签到

点亮在社区的每一天
去签到