手把手教你自定義自己SpringBoot Starter元件原始碼剖析

2023-07-07 12:01:33

我們知道SpringBoot Starter也就是啟動器。是SpringBoot元件化的一大優點。基於這個思想,基於這個思想SpringBoot 才變得非常強大,官方給我們提供很多開箱即用的啟動器。

Spring Boot Starter 是 Spring Boot 的一個重要特性,它有以下優點:

  1. 依賴管理:Starter 自動處理專案的依賴關係,使得開發者無需手動新增和管理每個依賴。

  2. 自動設定:Starter 提供了一種自動設定的方式,可以根據你的 classpath 和你定義的屬性自動設定 Spring 應用。

  3. 簡化開發:通過提供各種服務的 Starter(如資料庫、安全、快取等),極大地簡化了開發過程。

  4. 減少樣板程式碼:由於 Starter 的自動設定和依賴管理,開發者可以專注於業務邏輯,而不是設定和基礎設施程式碼。

  5. 快速原型開發:使用 Starter 可以快速建立可執行的原型。

  6. 易於理解和使用:Spring Boot Starter 的設計目標之一就是讓非專業的開發者也能快速上手。

  7. 社群支援:除了官方提供的 Starter,還有大量的社群提供的 Starter,可以滿足各種特定需求。

我現在手把手教大家如何封裝自己的starter 做自己的springboot元件,當然你也可以釋出自己的starter 到maven中央倉庫供大家使用

剖析SpringBoot自帶Starter

我們以WebMvcAutoConfiguration這個自動載入為例

自動設定類要能載入,有一個要求,原始碼分析結果是,需要在\META-INF\spring.factories中做如下設定

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

這樣SpringBoot在啟動完成時候,會找到我們引入,的starter 找到\META-INF\spring.factories 屬性檔案,找到需要自動載入設定的類路徑,然後幫我們自動注入到Spring IOC 容器,我們在專案中就可以直接使用了。

這裡實現自動載入還要依賴一些註解如:

@Configuration // 指定這個類是個設定類
@ConditionalOnXXX // 在指定條件成立的情況下自動設定類生效
@AutoConfigureOrder //設定類順序
@AutoConfigureAfter // 在哪個設定類之後
@Bean //給容器中新增元件

@ConfigurationProperties //結合相關的XXXProperties類 來繫結相關的設定
@EnableConfigurationProperties // 讓XXXProperties加入到容器中,別人就可以自動裝配

自定義自己的starter

剖析了SpringBoot 官方的starter 我們自定義自己的starter,(我們仿照著寫)

命名規範

設定提示

如果自定義屬性檔案中,需要IDEA智慧提示需要引入

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

定義starter

這裡我以自己封裝總結我工作以來總結專案封裝的一個SpringBoot starter為例

 <dependency>
            <groupId>cn.soboys</groupId>
            <artifactId>rest-api-spring-boot-starter</artifactId>
            <version>1.2.0</version>
        </dependency>

就是我自己封裝的start。已經發布中央倉庫。

目前更新版本1.3.0 功能如下

  1. 支援一鍵設定自定義RestFull API 統一格式返回
  2. 支援RestFull API 錯誤國際化
  3. 支援全域性例外處理,全域性引數驗證處理
  4. 業務錯誤斷言工具封裝,遵循錯誤優先返回原則
  5. redis工作封裝。支援所有key操作工具
  6. RestTemplate 封裝 POST,GET 請求工具
  7. 紀錄檔整合。自定義紀錄檔路徑,按照紀錄檔等級分類,支援壓縮和檔案大小分割。按時間顯示
  8. 工具庫整合 整合了lombok,hutool,commons-lang3,guava。不需要自己單個引入
  9. 整合mybatisPlus一鍵程式碼生成

rest-api-spring-boot-starter
倉庫地址
github

  1. 自定義設定屬性檔案
rest-api:
  enabled: false
  logging:
    path: ./logs
  i18n:
    # 若前端無header傳參則返回中文資訊
    i18n-header: Lang
    default-lang: cn
    message:
      # admin
      internal_server_error:
        en: Internal Server Error
        cn: 系統錯誤
      not_found:
        en: Not Found
        cn: 請求資源不存在

  1. 定義屬性設定類
package cn.soboys.restapispringbootstarter.i18n;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;


import java.util.Map;
import java.util.Optional;

/**
 * @author 公眾號 程式設計師三時
 * @version 1.0
 * @date 2023/6/26 11:55
 * @webSite https://github.com/coder-amiao
 */
//@PropertySource(value = "classpath:i18n.yaml", factory = YamlPropertySourceFactory.class)
@Configuration
@ConfigurationProperties(prefix = "rest-api.i18n")
@Data
public class I18NMessage {
    /**
     * message-key:<lang:message>
     */
    private Map<String, Map<String, String>> message;
    /**
     * Default language setting (Default "cn").
     */
    private String defaultLang = "cn";


    private String i18nHeader = "Lang";


    /**
     * get i18n message
     *
     * @param key
     * @param language
     * @return
     */
    public String message(I18NKey key, String language) {
        return Optional.ofNullable(message.get(key.key()))
                .map(map -> map.get(language == null ? defaultLang : language))
                .orElse(key.key());
    }

    /**
     * get i18n message
     *
     * @param key
     * @param language
     * @return
     */
    public String message(String key, String language) {
        return Optional.ofNullable(message.get(key))
                .map(map -> map.get(language == null ? defaultLang : language))
                .orElse(key);
    }

}

  1. 定義BeanAutoConfiguration自動載入設定類
package cn.soboys.restapispringbootstarter.config;

import cn.soboys.restapispringbootstarter.ApplicationRunner;
import cn.soboys.restapispringbootstarter.ExceptionHandler;
import cn.soboys.restapispringbootstarter.ResultHandler;
import cn.soboys.restapispringbootstarter.aop.LimitAspect;
import cn.soboys.restapispringbootstarter.i18n.I18NMessage;
import cn.soboys.restapispringbootstarter.utils.RedisTempUtil;
import cn.soboys.restapispringbootstarter.utils.RestFulTemp;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.Charset;
import java.util.List;

/**
 * @author 公眾號 程式設計師三時
 * @version 1.0
 * @date 2023/6/27 11:36
 * @webSite https://github.com/coder-amiao
 */
@Configuration
@ConditionalOnProperty(name = "rest-api.enabled", havingValue = "true")
public class BeanAutoConfiguration {


    @Bean
    public I18NMessage i18NMessage() {
        return new I18NMessage();
    }

    @Bean
    public ResultHandler resultHandler() {
        return new ResultHandler();
    }

    @Bean
    public ExceptionHandler exceptionHandler() {
        return new ExceptionHandler();
    }

    @Bean
    public StartupApplicationListener startupApplicationListener() {
        return new StartupApplicationListener();
    }


    @Bean
    public RestApiProperties restApiProperties() {
        return new RestApiProperties();
    }

    @Bean
    public RestApiProperties.LoggingProperties loggingProperties(RestApiProperties restApiProperties) {
        return restApiProperties.new LoggingProperties();
    }

    @Bean
    public ApplicationRunner applicationRunner() {
        return new ApplicationRunner();
    }




    /**
     * restTemplate 自動注入
     */
    @Configuration
    @ConditionalOnProperty(name = "rest-api.enabled", havingValue = "true")
    class RestTemplateConfig {
        /**
         * 第三方請求要求的預設編碼
         */
        private final Charset thirdRequest = Charset.forName("utf-8");

        @Bean
        public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
            RestTemplate restTemplate = new RestTemplate(factory);
            // 處理請求中文亂碼問題
            List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
            for (HttpMessageConverter<?> messageConverter : messageConverters) {
                if (messageConverter instanceof StringHttpMessageConverter) {
                    ((StringHttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest);
                }
                if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
                    ((MappingJackson2HttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest);
                }
                if (messageConverter instanceof AllEncompassingFormHttpMessageConverter) {
                    ((AllEncompassingFormHttpMessageConverter) messageConverter).setCharset(thirdRequest);
                }
            }
            return restTemplate;
        }

        @Bean
        public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(15000);
            factory.setReadTimeout(5000);
            return factory;
        }


        @Bean
        public RestFulTemp restFulTemp() {
            return new RestFulTemp();
        }

    }

}
  1. 自動裝配
    在專案

spring.factories 設定自己載入設定類

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.soboys.restapispringbootstarter.config.BeanAutoConfiguration,\
cn.soboys.restapispringbootstarter.config.BeanAutoConfiguration.RestTemplateConfig,\
cn.soboys.restapispringbootstarter.utils.RedisTempUtil\

擴充套件思考,我們可以看到SpringBoot官方stater 很多啟用都類似@Enablexxx註解
這個怎麼實現。我的rest-api-spring-boot-starter 1.3.0已經實現不需要在application.properties設定一行 直接在啟動類或者設定類使用EnableRestFullApi就可以使用全部功能

完善檔案使用可以看我

SpringBoot定義優雅全域性統一Restful API 響應框架完結撒花篇封裝starter元件

這篇文章

到此自己定義starter就寫完了 接下來就是打包,釋出到maven中央倉庫

我會在 下一篇文章繼續分享

留下你的思考,關注公眾 程式設計師三時

持續輸出優質內容 希望給你帶來一點啟發和幫助