springBoot-starter思想及原理

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来说明自动配置的实现过程
  • 依赖
            
                org.mybatis.spring.boot
                mybatis-spring-boot-starter
                ${mybatis-spring-boot.version}
            
  • 自动配置类

@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 getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List 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 getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List 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...

你可能感兴趣的:(springbootjava)