乐趣区

关于springboot:springBootstarter思想及原理

🍓starter 作用

springBoot starter 基于约定大于配置思维,应用 spi 机制及主动拆卸原理,能够将一些通用的性能可能封装成一个独立组件并很不便的集成到不同的我的项目外面,简化开发,晋升代码复用能力。

springBoot 在配置上相比 spring 要简略许多, 其外围在于 starter 的设计, 在应用 springBoot 来搭建一个我的项目时, 只须要引入官网提供的 starter, 就能够间接应用, 免去了各种配置。starter 简略来讲就是引入了一些相干依赖和一些初始化的配置。

🍀约定大于配置

  • 对于大部分罕用状况,极简短的配置即可应用
  • 对于少部分的略非凡状况,大量的配置即可应用
  • 对于极为非凡的定制化需要,能够通过各选项手动配置实现

约定能够缩小很多配置, 比如说在 maven 的构造中:
/src/main/java 目录用来寄存 java 源文件
src/main/resources 目录用来寄存资源文件,如 application.yml 文件
/src/test/java 目录用来寄存 java 测试文件
/src/test/resources 目录用来寄存测试资源文件
/target 目录为我的项目的输入地位

  • springBoot 的理念就是约定大于配置,这一点在各种 starter 外面尤其体现的酣畅淋漓。在 springBoot 中提供了一套默认配置,不须要手动去写 xml 配置文件,只有默认配置不能满足咱们的需要时,才会去批改配置。相比于晚期的 spring 须要编写各种 xml 配置文件,starter 极大的缩小了各种简单的配置

🌹starter 命名

spring 官网提供了很多 starter,第三方也能够定义 starter。为了加以辨别,starter 从名称上进行了如下标准:

  • spring 官网 starter 通常命名为 spring-boot-starter-{name}, 如spring-boot-starter-web
  • spring 官网倡议非官方 starter 命名应遵循 {name}-spring-boot-starter 的格局, 例如由 mybatis 提供的mybatis-spring-boot-starter

🌼starter 原理

mybatis-spring-boot-starter 来阐明主动配置的实现过程

  • 🍊依赖
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis-spring-boot.version}</version>
            </dependency>
  • 🍍主动配置类

@Configuration
注解的类能够看作是能生产让 Spring IoC 容器治理的 Bean 实例的工厂。
@Configuration 和 @Bean
这两个注解一起应用就能够创立一个基于 java 代码的配置类,能够用来代替传统的 xml 配置文件。
@Bean
注解的办法返回的对象能够被注册到 spring 容器中。

上面的 MybatisAutoConfiguration 这个类,主动帮咱们生成了 SqlSessionFactory 和 SqlSessionTemplate 这些 Mybatis 的重要实例并交给 spring 容器治理。
@EnableConfigurationProperties(MybatisProperties.class)
能够将 mybatis 的配置信息注入到 MybatisProperties 对应的 bean 实例外面。
@ConditionalOnClass,@ConditionalOnBean 代表主动拆卸的条件
要实现 Mybatis 的主动配置,须要在类门路中存在 SqlSessionFactory.class、SqlSessionFactoryBean.class 这两个类,同时须要存在 DataSource 这个 bean 且这个 bean 实现主动注册。

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

  private final MybatisProperties properties;

  private final Interceptor[] interceptors;

  private final ResourceLoader resourceLoader;

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
   ...
    if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();}
  • 🍒配置属性

@ConfigurationProperties 把 yml 或者 properties 配置文件中的配置参数信息封装到 ConfigurationProperties 注解标注的 bean 里,个别联合 @EnableConfigurationProperties 注解应用

/**
 * Configuration properties for MyBatis.
 */
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

  public static final String MYBATIS_PREFIX = "mybatis";

  /**
   * Location of MyBatis xml config file.
   */
  private String configLocation;

  /**
   * Locations of MyBatis mapper files.
   */
  private String[] mapperLocations;

  /**
   * Packages to search type aliases. (Package delimiters are ",; \t\n")
   */
  private String typeAliasesPackage;

  /**
   * Packages to search for type handlers. (Package delimiters are ",; \t\n")
   */
  private String typeHandlersPackage;

  ...
  /**
   * A Configuration object for customize default settings. If {@link #configLocation}
   * is specified, this property is not used.
   */
  @NestedConfigurationProperty
  private Configuration configuration;
  ...
  }
  • 🍈starter 里 Bean 的发现与注册

META-INF 目录下的 spring.factories 文件

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

spring boot 默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包含依赖包中的类,那么依赖包中的 bean 是如何被发现和加载的?

咱们须要从 Spring Boot 我的项目的启动类开始跟踪,在启动类上咱们个别会退出 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 {

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    @AliasFor(annotation = EnableAutoConfiguration.class)
    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
     */
    @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 {};

    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;}

SpringBootConfiguration:
作用就相当于 Configuration 注解,被注解的类将成为一个 bean 配置类
ComponentScan:
作用就是主动扫描并加载符合条件的组件,最终将这些 bean 加载到 spring 容器中
EnableAutoConfiguration
这个注解是要害,会引入 @Import 注解,借助 @Import 的反对,收集和注册依赖包中相干的 bean 定义

@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 {};}

@Import:导入须要主动配置的组件,此处为 AutoConfigurationImportSelector 这个类

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    ...

    /**
     * Return the auto-configuration class names that should be considered. By default
     * this method will load candidates using {@link SpringFactoriesLoader} with
     * {@link #getSpringFactoriesLoaderFactoryClass()}.
     * @param metadata the source metadata
     * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     * attributes}
     * @return a list of candidate configurations
     */
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you"
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    ...

}

getCandidateConfigurations 里的 SpringFactoriesLoader 的 loadFactoryNames 静态方法能够从所有的 jar 包中读取META-INF/spring.factories 文件,而主动配置的类就在这个文件中进行配置, 从而加载在 starter 外面的主动配置类

在 SpringBoot 利用中要让一个一般类交给 Spring 容器治理,通常有以下办法:
1、应用 @Configuration 与 @Bean 注解
2、应用 @Controller @Service @Repository @Component 注解标注该类并且启用 @ComponentScan 主动扫描
3、 应用 @Import办法
其中 SpringBoot 实现主动配置应用的是 @Import 注解这种形式,AutoConfigurationImportSelector 类的 selectImports 办法返回一组从 META-INF/spring.factories 文件中读取的 bean 的全类名,这样 SpringBoot 就能够加载到这些 Bean 并实现实例的创立工作。

🍏SPI 机制

SPI(Service Provider Interface)是一种服务提供发现机制,能够用来启用框架扩大和替换组件, 次要用于框架中开发,例如 Dubbo、Spring、Common-Logging,JDBC 等采纳采纳 SPI 机制,针对同一接口采纳不同的实现提供给不同的用户,从而进步了框架的扩展性。
Java 内置的 SPI 通过 java.util.ServiceLoader 类应用 load 办法解析 classPath 和 jar 包的 META-INF/services/ 目录 下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此实现调用。
Spring SPI 沿用了 Java SPI 的设计思维,Spring 采纳的是 spring.factories 形式实现 SPI 机制,能够在不批改 Spring 源码的前提下,提供 Spring 框架的扩展性。如主动拆卸外面获取主动配置的各种实现类,starter 外面的 spring.factories 文件里的内容都是某某注解类型(接口)= 对应的实现类。


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

**Spring factories SPI 是一个 spring.factories 配置文件寄存多个接口及对应的实现类,以接口全限定名作为 key,实现类作为 value 来配置,多个实现类用逗号隔开,仅 spring.factories 一个配置文件。
Java SPI 是一个服务提供接口对应一个配置文件,配置文件中寄存以后接口的所有实现类,多个服务提供接口对应多个配置文件,所有配置都在 services 目录下 **

    /**
     * Return the auto-configuration class names that should be considered. By default
     * this method will load candidates using {@link SpringFactoriesLoader} with
     * {@link #getSpringFactoriesLoaderFactoryClass()}.
     * @param metadata the source metadata
     * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     * attributes}
     * @return a list of candidate configurations
     */
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you"
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

    /**
     * Return the class used by {@link SpringFactoriesLoader} to load configuration
     * candidates.
     * @return the factory class
     */
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;}

SpringFactoriesLoader.loadFactoryNames 获取主动拆卸注解的全限定类名,须要传入的接口类型。这里传入的是 EnableAutoConfiguration 注解类型,注解实质上也是一种 Interface。

🍭 总结

springBoot 应用 starter 设计各种组件简化开发过程中很多简单的配置,把简单留给本人,简略不便给予别人,赠人玫瑰,手有余香,真不愧是 java 的开发框架之王,外面的设计之道值得咱们学习借鉴

参考文献:
https://zhuanlan.zhihu.com/p/…
https://mp.weixin.qq.com/s/GL…

退出移动版