此Starter使用的版本

  • elasticsearch-rest-high-level-client 7.2.0
  • elasticsearch-rest-client 7.2.0
  • elasticsearch
  • elastic 7.*

为什么写这个Starter

公司的Elastic版本还是6.x,而且使用的transportClient,作为客户端。一则再升级版本会面临客户端不支持,另外一个官方也已经不再推荐使用transportClient,写这个Starter练练手,另外有备无患。

如何写一个SpringBoot的Starter

写一个Starter相对来说其实很简单,按约定格式引入依赖,加入一个spring.factories文件即可。

  • 创建一个Maven项目,名称使用xxx-spring-boot-starter命名,以便和官方的Starter区分开,比如au92-elastic-spring-boot-starter

  • POM引用,实际需要用到的依赖包只有两个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
  • resources目录创建文件夹META-INF,新建一个spring.factories文件

    1
    2
    
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.au92.common.elastic.ElasticSearchAutoConfiguration
  • 创建一个ElasticSearchProperties类加载配置信息

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    package com.au92.common.elastic;
    
    import lombok.Data;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    /**
    * @author: p_x_c
    */
    @Data
    @ConfigurationProperties(prefix = "elasticsearch")
    public class ElasticSearchProperties {
        /**
        * es集群地址,多个用英文逗号分开
        */
        private String clusterNodes = "http://127.0.0.1:9200";
        /**
        * es 用户名
        */
        private String userName = StringUtils.EMPTY;
    
        /**
        * es 用户密码
        */
        private String password = StringUtils.EMPTY;
    
        /**
        * 默认es索引名称
        */
        private String index = "default";
    }
  • 新建 ElasticSearchAutoConfiguration 用来将配置自动加载进来

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    
    package com.au92.common.elastic;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestClientBuilder;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.DisposableBean;
    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 org.springframework.context.annotation.Scope;
    
    import javax.annotation.Resource;
    import java.io.IOException;
    import java.util.Arrays;
    import java.util.Objects;
    import java.util.stream.Collectors;
    
    import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE;
    
    /**
    * @author: p_x_c 
    */
    @Slf4j
    @Configuration
    @EnableConfigurationProperties(ElasticSearchProperties.class)
    public class ElasticSearchAutoConfiguration implements DisposableBean {
        private RestHighLevelClient client;
    
        @Resource
        private ElasticSearchProperties properties;
    
        @Override
        public void destroy() throws Exception {
            log.info("销毁ES连接");
            if (client != null) {
                client.close();
            }
        }
    
        /**
        * 创建client
        *
        * @return
        * @throws IOException
        */
        @Bean
        @ConditionalOnMissingBean(RestHighLevelClient.class)
        public RestHighLevelClient client() throws IOException {
            if (!Objects.equals(null, client)) {
                log.warn("client对象没有正确关闭");
                client.close();
            }
            RestClientBuilder builder = RestClient.builder(setting());
            setConnectTimeOutConfig(builder);
            setMutiConnectConfig(builder);
            client = new RestHighLevelClient(builder);
            return client;
        }
    
        /**
        * Elastic Service
        *
        * @return
        */
        @Bean
        @Scope(value = SCOPE_PROTOTYPE)
        @ConditionalOnMissingBean(IElasticSearchService.class)
        public IElasticSearchService service() {
            return new ElasticSearchServiceImpl();
        }
    
        /**
        * 解析配置文件
        *
        * @return
        */
        private HttpHost[] setting() {
            return Arrays.stream(properties.getClusterNodes().split(ElasticConstant.CLUSTER_SPLIT))
                    .map(x -> {
                        String[] addressPortPairs = x.split(ElasticConstant.SCHEME_SPLIT);
                        String schemeName = addressPortPairs[0].toLowerCase();
                        String hostname = addressPortPairs[1].substring(2);
                        Integer port = Integer.valueOf(addressPortPairs[2]);
                        return new HttpHost(hostname, port, schemeName);
                    })
                    .collect(Collectors.toList())
                    .toArray(new HttpHost[0]);
    
        }
    
        /**
        * 超时设置
        *
        * @param builder
        */
        private void setConnectTimeOutConfig(RestClientBuilder builder) {
            builder.setRequestConfigCallback(requestConfigBuilder -> {
                requestConfigBuilder.setConnectTimeout(ElasticConstant.CONNECT_TIME_OUT)
                        .setSocketTimeout(ElasticConstant.SOCKET_TIME_OUT)
                        .setConnectionRequestTimeout(ElasticConstant.CONNECTION_REQUEST_TIME_OUT);
                return requestConfigBuilder;
            });
        }
    
        /**
        * 并发连接数设置
        *
        * @param builder
        */
        private void setMutiConnectConfig(RestClientBuilder builder) {
            builder.setHttpClientConfigCallback(httpClientBuilder -> {
                httpClientBuilder.setMaxConnTotal(ElasticConstant.MAX_CONNECT_NUM)
                        .setMaxConnPerRoute(ElasticConstant.MAX_CONNECT_PER_ROUTE);
                return httpClientBuilder;
            });
        }
    }

抛开其他业务相关的逻辑,一个Spring Boot的Starter已经可以算完成了。

用到的其他工具类和一些操作类

https://gist.github.com/Chairo/1b0d06d45e5155b134b7b43abb3d84f6