SpringBoot的Starter機制

 Starter機制自己實現基於SPI,不少框架都使用了java的SPI機制,如java.sql.Driver的SPI實現(mysql驅動、oracle驅動等)、common-logging的日誌接口實現、dubbo的擴展實現等等框架,Starter是Spring Boot中的一個很是重要的概念,Starter至關於模塊,它能將模塊所需的依賴整合起來並對模塊內的 Bean 根據環境( 條件)進行自動配置。使用者只須要依賴相應功能的 Starter,無需作過多的配置和依賴,SpringBoot 就能自動掃描並加載相應的模塊。SpringBoot 存在不少開箱即用的 Starter 依賴,使得咱們在開發業務代碼時可以很是方便的、不須要過多關注框架的配置,而只須要關注業務便可。要熟悉Starter機制,首先須要瞭解@SpringBootApplication註解
@SpringBootApplication注
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
     
  @SpringBootApplication 本質上是由 3 個註解組成,分別是
           1. @SpringbootConfiguration
                  它是 JavaConfig形式的基於 Spring IOC 容器的配置類使用的一種註解。由於 SpringBoot 本質上就是一個 spring 應用,因此經過這個註解來加載 IOC 容器的配置是很正常的。因此在啓動類裏面標註了@Configuration,意味着它其實也是一個 IoC容器的配置類。傳統意義上的 spring 應用都是基於 xml 形式來配置 bean的依賴關係。而後經過 spring 容器在啓動的時候,把 bean進行初始化而且,若是 bean 之間存在依賴關係,則分析這些已經在 IoC 容器中的 bean 根據依賴關係進行組裝。直到 Java5 中,引入了 Annotations 這個特性,Spring 框架也緊隨大流而且推出了基於 Java 代碼和 Annotation 元信息的依賴關係綁定描述的方式。也就是 JavaConfig。 從 spring3 開始,spring 就支持了兩種 bean 的配置方式,一種是基於 xml 文件方式、另外一種就是 JavaConfig任何一個標註了@Configuration 的 Java 類定義都是一個JavaConfig 配置類。而在這個配置類中,任何標註了@Bean 的方法,它的返回值都會做爲 Bean 定義註冊到Spring 的 IOC 容器,方法名默認成爲這個 bean 的 id。
           2. @ComponentScan
               至關於 xml 配置文件中的<context:component-scan>。 它的主要做用就是掃描指定路徑下的標識了須要裝配的類,自動裝配到 spring 的 Ioc 容器中。標識需 要裝配的類的 形式主要是: @Component 、@Repository、@Service、@Controller 這類的註解標識的類。ComponentScan 默認會掃描當前 package 下的的全部加了相關注解標識的類到 IoC 容器中。
          3. @EnableAutoConfiguration
                在 spring3.1 版本中,提供了一系列的@Enable 開頭的註解,Enable 主機應該是在 JavaConfig 框架上更進一步的完善,是的用戶在使用 spring 相關的框架是,避免配置大量的代碼從而下降使用的難度好比常見的一些 Enable 註解:EnableWebMvc,(這個註解引入了 MVC 框架在 Spring 應用中須要用到的全部bean);好比說@EnableScheduling,開啓計劃任務的支持;
 
       @EnableAutoConfiguration的定義       
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default {};

}
View Code

         主要包含:@AutoConfigurationPackage   @Import(AutoConfigurationImportSelector.class)java

       @Import註解:把多個分來的容器配置合併在一個配置中,xml 形式下有一個<import resource/> 形式的註解,在JavaConfig 中所表達的意義是同樣的。mysql

       @Import(AutoConfigurationImportSelector.class)中AutoConfigurationImportSelector的類圖react

         

         實現了ImportSelector接口,重寫了selectImports方法web

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        //加載   "META-INF/spring-autoconfigure-metadata.properties"文件,裏面都是加載bean的conditon
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        //下面代碼具體分析
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

   //具體分析
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //記載配置文件  META-INF/spring.factories中EnableAutoConfiguration.class爲key的value集合
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        //去重和根據條件Condition加載bean到Ioc容器中
configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

       spring-autoconfigure-metadata.properties中的小部份內容spring

org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,org.springframework.data.cassandra.core.ReactiveCassandraTemplate,reactor.core.publisher.Flux
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository

        ConditionalOnClass表明必須存在後面的class纔會加載,autoConfigureAfter表明後面bean實例化後再實例化當前bean,還有其餘條件,這裏只是部分,目的就是根據條件加載bean。sql

 

        META-INF/spring.factories中EnableAutoConfiguration.class爲key的value集合apache

       

          掃描 spring-autoconfiguration-metadata.properties文件,最後在掃描 spring.factories 對應的類時,會結合前面的元數據進行過濾,爲何要過濾呢? 緣由是不少的@Configuration 實際上是依託於其餘的框架來加載的,
若是當前的 classpath 環境下沒有相關聯的依賴,則意味着這些類不必進行加載,因此,經過這種條件過濾能夠有效的減小@configuration 類的數量從而下降SpringBoot 的啓動時間。
       @AutoConfigurationPackage
       做用和@Import(AutoConfigurationImportSelector.class)相似,都是動態注入
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

       AutoConfigurationPackages.Registrar.classjson

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//實現動態註冊IOC register(registry,
new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }
  直接用這三個註解也能夠啓動 springboot 應用,只是每次配置三個註解比較繁瑣,因此直接用一個複合註解更方便些。
   
        基於上面的分析,實現一個簡單的Starter 
         pom文件以下
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.36</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.0.4.RELEASE</version>
    </dependency>
  </dependencies>

     定義接口JsonFormat,用來把java bean解析成Stringspringboot

public interface JsonFormat<T> {

   String parse (T t) throws JsonProcessingException;

}

      兩個實現類,一個基於FastJson,一個基於Jacksonoracle

public class fastJsonFormat<T> implements JsonFormat<T> {
    
    @Override
    public String parse(T o) {
        String s = JSON.toJSONString(o);
        return s;
    }
}
public class JacksonFormat<T> implements JsonFormat<T> {
    @Override
    public String parse(T t) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(t);
        return s;
    }
}

      動態配置屬性類 FormatConfigProperties 

@ConfigurationProperties(prefix = "json.format", ignoreUnknownFields = true)
public class FormatConfigProperties {
    public FormatConfigProperties() { }
    public FormatConfigProperties(String name1, String name2, String name3) { this.name1 = name1;this.name2 = name2;this.name3 = name3; }
    private String name1;
    private String name2;
    private String name3;

    public String getName1() { return name1; }

    public void setName1(String name1) { this.name1 = name1; }

    public String getName2() { return name2; }

    public void setName2(String name2) { this.name2 = name2; }

    public String getName3() { return name3; }

    public void setName3(String name3) { this.name3 = name3; }
}

      實例化兩種  JsonFormat

@Configuration
public class JsonFormatConfiguration {

    @ConditionalOnClass(name ="com.alibaba.fastjson.JSON")
    @Bean
    @Primary
    public JsonFormat stringFormat(){
        return new fastJsonFormat();
    }

    @ConditionalOnClass(name = "com.fasterxml.jackson.databind")
    @Bean
    public JsonFormat jsonFormat(){
        return new JacksonFormat();
    }
}

    實現動態注入的類JsonFormatAutoConfiguration 

@EnableConfigurationProperties(FormatConfigProperties.class)
@Import({JsonFormatConfiguration.class})
@Configuration
public class JsonFormatAutoConfiguration {
    @Bean
    public JsonFormatTemplate jsonFormatTemplate(JsonFormat jsonFormat,FormatConfigProperties formatConfigProperties) {
        return new JsonFormatTemplate(jsonFormat,formatConfigProperties);
    }
}

         

         最重要的一步,也是最後一步,在當前項目的resources目錄下新建 META-INF/spring.factories 文件裏面內容以下

         org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.json.config.JsonFormatAutoConfiguration

         基於springboot自帶的@EnableAutoConfiguration註解,就用將須要的bean注入Ioc容器中,實現動態注入bean,而後把當前項目打成jar包,須要用到此功能的項目直接pom引入便可。    

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
    @Autowired
    private JsonFormatTemplate jsonFormatTemplate;
    @Test
    public void contextLoads() throws JsonProcessingException {
        Person person = new Person("meixi", "阿根廷", 18);
        System.out.println( jsonFormatTemplate.parse(person));
    }

}

 

           

          上面的starter demo自己沒有實際意義,真實項目中按照這個方式定製Starter便可。

相關文章
相關標籤/搜索