從nacos-spring-boot-project角度全面看Springboot Starter

FeelTouch發表於2019-05-11

一、作用與原理

首先說說原理,我們知道使用一個公用的starter的時候,只需要將相應的依賴新增的Maven的配置檔案當中即可,免去了自己需要引用很多依賴類,並且SpringBoot會自動進行類的自動配置。那麼 SpringBoot 是如何知道要例項化哪些類,並進行自動配置的呢? 下面簡單說一下。

首先,SpringBoot 在啟動時會去依賴的starter包中尋找 resources/META-INF/spring.factories 檔案,然後根據檔案中配置的Jar包去掃描專案所依賴的Jar包,這類似於 Java 的 SPI 機制。

第二步,根據 spring.factories配置載入AutoConfigure類。

最後,根據 @Conditional註解的條件,進行自動配置並將Bean注入Spring Context 上下文當中。

我們也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class}) 指定自動配置哪些類。

二、nacos-spring-boot-project starter專案解讀

官方提供的starter,大多包含兩個jar包: 一個starter——沒有任何實現,只用來管理依賴(spring.providers檔案宣告);一個autoconfigure包——包含所有具體實現,包括自動配置類,及META-INF/spring.factories檔案。自定義starter的時候,可以合併寫到一個。

官方提供的starter,命名遵循spring-boot-starter-xx; 自定義starter,命名遵循xx-spring-boot-starter。

1. nacos-spring-boot-project 包組成與命名

從專案的截圖可以看出,包的拆分上採取了類似官方的2個jar的形式;命名上採取了 自定義starter,遵循xx-spring-boot-starter的命名方式。

2. 分析nacos-config-spring-boot-starter

如上圖所示,這是一個starter——沒有任何實現,只用來管理依賴(spring.providers檔案宣告), 該宣告指明瞭依賴由

provides: nacos-config-spring-boot-autoconfigure 提供。

其pom.xml 內容如期望的一樣:僅有業務依賴和基礎starter依賴,而沒有基礎配置依賴,因為包含在了nacos-config-spring-boot-autoconfigure包中。

<dependencies>
        <!-- Spring Boot dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <scope>true</scope>
        </dependency>


        <!-- Nacos -->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-spring-context</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-spring-boot-base</artifactId>
        </dependency>

       <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-config-spring-boot-autoconfigure</artifactId>
        </dependency>

    </dependencies>

3.分析nacos-config-spring-boot-autoconfigure

如上圖所示,在resources/META-INF/spring.factories檔案中如下內容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.boot.nacos.config.autoconfigure.NacosConfigAutoConfiguration

是整個配置的關鍵部分,其指明瞭AutoConfigure實現類是 NacosConfigAutoConfiguration類。而該類的核心作用就是實現其註解中的相關bean在某些條件下的自動裝配。

@ConditionalOnProperty(name = NacosConfigConstants.ENABLED, matchIfMissing = true)
@ConditionalOnMissingBean(name = CONFIG_GLOBAL_NACOS_PROPERTIES_BEAN_NAME)
@EnableNacosConfig
@EnableConfigurationProperties(value = NacosConfigProperties.class)
@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder")
public class NacosConfigAutoConfiguration {

}

該類通過註解進行實現,其註解的含義如下:

  • @ConditionalOnClass,當classpath下發現該類的情況下進行自動配置。
  • @ConditionalOnMissingBean,當Spring Context中不存在該Bean時進行自動配置。
  • @ConditionalOnProperty(name = NacosConfigConstants.ENABLED, matchIfMissing = true)時進行自動配置。

其中NacosConfigConstants類的如下:

public interface NacosConfigConstants {

	String ENDPOINT_PREFIX = "nacos-config";

	String ENABLED = EnableNacosConfig.CONFIG_PREFIX + "enabled";

    String PREFIX = "nacos.config";

}

有引用到了另一個註解

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({NacosConfigBeanDefinitionRegistrar.class})
public @interface EnableNacosConfig {
    String CONFIG_PREFIX = "nacos.config.";
    String ENDPOINT_PLACEHOLDER = "${nacos.config.endpoint:${nacos.endpoint:}}";
    String NAMESPACE_PLACEHOLDER = "${nacos.config.namespace:${nacos.namespace:}}";
    String ACCESS_KEY_PLACEHOLDER = "${nacos.config.access-key:${nacos.access-key:}}";
    String SECRET_KEY_PLACEHOLDER = "${nacos.config.secret-key:${nacos.secret-key:}}";
    String SERVER_ADDR_PLACEHOLDER = "${nacos.config.server-addr:${nacos.server-addr:}}";
    String CONTEXT_PATH_PLACEHOLDER = "${nacos.config.context-path:${nacos.context-path:}}";
    String CLUSTER_NAME_PLACEHOLDER = "${nacos.config.cluster-name:${nacos.cluster-name:}}";
    String ENCODE_PLACEHOLDER = "${nacos.config.encode:${nacos.encode:UTF-8}}";

    NacosProperties globalProperties() default @NacosProperties(
    endpoint = "${nacos.config.endpoint:${nacos.endpoint:}}",
    namespace = "${nacos.config.namespace:${nacos.namespace:}}",
    accessKey = "${nacos.config.access-key:${nacos.access-key:}}",
    secretKey = "${nacos.config.secret-key:${nacos.secret-key:}}",
    serverAddr = "${nacos.config.server-addr:${nacos.server-addr:}}",
    contextPath = "${nacos.config.context-path:${nacos.context-path:}}",
    clusterName = "${nacos.config.cluster-name:${nacos.cluster-name:}}",
    encode = "${nacos.config.encode:${nacos.encode:UTF-8}}"
);
}

從上面的類和註解可以看出,其實,就相當於更為常見的 @ConditionalOnProperty(prefix = "nacos.config",value = "enabled",havingValue = "true")。

從上面可以看出,其毫無例外的都用都了基於字首的自動配置進行bean的裝配,那麼最終依賴如下

spring-boot-configuration-processor 也是必然的了。
<!-- @ConfigurationProperties annotation processing (metadata for IDEs) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

在最上面的紅框中我們看到了 spring-configuration-metadata.json 核心作用是使用時給出自動給出IDE提示,需要料及而更多請參照:Springboot實現基於字首的自定義配置和自動提示功能

進一步,看下pom.xml中依賴了哪些包?

<dependencies>
        <!-- Spring Boot dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- @ConfigurationProperties annotation processing (metadata for IDEs) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Nacos -->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-spring-context</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-spring-boot-base</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Test Dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

最終,確實依賴到了如下2個包:

spring-boot-autoconfigure
spring-boot-configuration-processor

附加額外註解解釋:

@ConditionalOnBean:當容器中有指定的Bean的條件下  
@ConditionalOnClass:當類路徑下有指定的類的條件下  
@ConditionalOnExpression:基於SpEL表示式作為判斷條件  
@ConditionalOnJava:基於JVM版本作為判斷條件  
@ConditionalOnJndi:在JNDI存在的條件下查詢指定的位置  
@ConditionalOnMissingBean:當容器中沒有指定Bean的情況下  
@ConditionalOnMissingClass:當類路徑下沒有指定的類的條件下  
@ConditionalOnNotWebApplication:當前專案不是Web專案的條件下  
@ConditionalOnProperty:指定的屬性是否有指定的值  
@ConditionalOnResource:類路徑下是否有指定的資源  
@ConditionalOnSingleCandidate:當指定的Bean在容器中只有一個,或者在有多個Bean的情況下,用來指定首選的Bean @ConditionalOnWebApplication:當前專案是Web專案的條件下  

參考: 1. 編寫自己的SpringBoot-starter

            2.實現自定義Spring Boot Starter

            3.nacos-spring-boot-project

相關文章