zoukankan      html  css  js  c++  java
  • 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即可。

     

  • 相关阅读:
    Cmder配置
    uboot移植
    嵌入式产品开发技术问题
    flexbox布局
    使用PS过程
    STM32 使用 FreeRTOS过程记录
    TTL、RS232、RS485、串口
    用纯css改变下拉列表select框的默认样式
    task9暂存
    Hello World
  • 原文地址:https://www.cnblogs.com/dyg0826/p/11132305.html
Copyright © 2011-2022 走看看