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)

       @Import注解:把多个分来的容器配置合并在一个配置中,xml 形式下有一个<import resource/> 形式的注解,在JavaConfig 中所表达的意义是一样的。

       @Import(AutoConfigurationImportSelector.class)中AutoConfigurationImportSelector的类图

         

         实现了ImportSelector接口,重写了selectImports方法

    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中的小部分内容

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。

 

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

       

          扫描 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.class

    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解析成String

public interface JsonFormat<T> {

   String parse (T t) throws JsonProcessingException;

}

      两个实现类,一个基于FastJson,一个基于Jackson

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即可。

 

posted @ 2019-07-04 14:37  Don'tYouSee  阅读(1036)  评论(0编辑  收藏  举报