SpringBoot的starter到底是什么?

Posted 苏三说技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot的starter到底是什么?相关的知识,希望对你有一定的参考价值。

前言

我们都知道,Spring的功能非常强大,但也有些弊端。比如:我们需要手动去配置大量的参数,没有默认值,需要我们管理大量的jar包和它们的依赖。

为了提升Spring项目的开发效率,简化一些配置,Spring官方引入了SpringBoot。

当然,引入SpringBoot还有其他原因,在这里就不过多描述了。

本文重点跟大家一起聊聊SpringBootstarter机制,因为它太重要了。

1 为什么要用starter?

SpringBoot还没有出来之前,我们使用Spring开发项目。如果程序需要连接数据库,我们一般会使用HibernateMybatisORM框架,这里我以Mybatis为例,具体的操作步骤如下:

  1. 到maven仓库去找需要引入的mybatis jar包,选取合适的版本。
  2. 到maven仓库去找mybatis-spring整合的jar包,选取合适的版本。
  3. 在spring的applicationContext.xml文件中配置dataSource和mybatis相关信息。

当然有些朋友可能会指正,不是还需要引入数据库驱动包吗?

确实需要引入,但数据库驱动有很多,比如:mysql、oracle、sqlserver,这不属于mybatis的范畴,使用者可以根据项目的实际情况单独引入。

如果程序只是需要连接数据库这一个功能还好,按上面的步骤做基本可以满足需求。但是,连接数据库可能只是庞大的项目体系中一个环节,实际项目中往往更复杂,需要引入更多的功能,比如:连接redis、连接mongodb、使用rocketmq、使用excel功能等等。

引入这些功能的话,需要再把上面的步骤再重复一次,工作量无形当中增加了不少,而且有很多重复的工作

另外,还是有个问题,每次到要到maven中找合适的版本,如果哪次找的mybatis.jar包 和 mybatis-spring.jar包版本不兼容,程序不是会出现问题?

SpringBoot为了解决以上两个问题引入了starter机制

2 starter有哪些要素?

我们首先一起看看mybatis-spring-boot-starter.jar是如何定义的。


可以看到它的META-INF目录下只包含了:

  • pom.protperties 配置maven所需的项目version、groupId和artifactId。
  • pom.xml 配置所依赖的jar包。
  • MANIFEST.MF 这个文件描述了该Jar文件的很多信息。
  • spring.provides 配置所依赖的artifactId,给IDE使用的,没有其他的作用。

注意一下,没有一行代码。

我们重点看一下pom.xml,因为这个jar包里面除了这个没有啥重要的信息

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>1.3.1</version>
  </parent>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <name>mybatis-spring-boot-starter</name>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>
</project>

从上面可以看出,pom.xml文件中会引入一些jar包,其中除了引入spring-boot-starter,之外重点看一下:mybatis-spring-boot-autoconfigure

我们找到mybatis-spring-boot-autoconfigure.jar文件,打开这个文件。

里面包含如下文件:

  • pom.properties 配置maven所需的项目version、groupId和artifactId
  • pom.xml 配置所依赖的jar包
  • additional-spring-configuration-metadata.json 手动添加IDE提示功能
  • MANIFEST.MF 这个文件描述了该Jar文件的很多信息
  • spring.factories SPI会读取的文件
  • spring-configuration-metadata.json 系统自动生成的IDE提示功能
  • ConfigurationCustomizer 自定义Configuration回调接口
  • MybatisAutoConfiguration mybatis配置类
  • MybatisProperties mybatis属性类
  • SpringBootVFS 扫描嵌套的jar包中的类

spring-configuration-metadata.jsonadditional-spring-configuration-metadata.json的功能差不多,我们在applicationContext.properties文件中输入spring时,会自动出现下面的配置信息可供选择,就是这个功能了。

来自灵魂的一问:这两个文件有什么区别?

答:如果pom.xml中引入了spring-boot-configuration-processor包,则会自动生成spring-configuration-metadata.json

如果需要手动修改里面的元数据,则可以在additional-spring-configuration-metadata.json中编辑,最终两个文件中的元数据会合并到一起。

MybatisProperties类是属性实体类:

@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties 

  public static final String MYBATIS_PREFIX = "mybatis";
  private String configLocation;
  private String[] mapperLocations;
  private String typeAliasesPackage;
  private String typeHandlersPackage;
  private boolean checkConfigLocation = false;
  private ExecutorType executorType;
  private Properties configurationProperties;
  @NestedConfigurationProperty
  private Configuration configuration;

  public String getConfigLocation() 
    return this.configLocation;
  

  public void setConfigLocation(String configLocation) 
    this.configLocation = configLocation;
  

  @Deprecated
  public String getConfig() 
    return this.configLocation;
  

  @Deprecated
  public void setConfig(String config) 
    this.configLocation = config;
  

  public String[] getMapperLocations() 
    return this.mapperLocations;
  

  public void setMapperLocations(String[] mapperLocations) 
    this.mapperLocations = mapperLocations;
  
  
  public String getTypeHandlersPackage() 
    return this.typeHandlersPackage;
  

  public void setTypeHandlersPackage(String typeHandlersPackage) 
    this.typeHandlersPackage = typeHandlersPackage;
  

  public String getTypeAliasesPackage() 
    return this.typeAliasesPackage;
  

  public void setTypeAliasesPackage(String typeAliasesPackage) 
    this.typeAliasesPackage = typeAliasesPackage;
  

  public boolean isCheckConfigLocation() 
    return this.checkConfigLocation;
  

  public void setCheckConfigLocation(boolean checkConfigLocation) 
    this.checkConfigLocation = checkConfigLocation;
  

  public ExecutorType getExecutorType() 
    return this.executorType;
  

  public void setExecutorType(ExecutorType executorType) 
    this.executorType = executorType;
  

  public Properties getConfigurationProperties() 
    return configurationProperties;
  

  public void setConfigurationProperties(Properties configurationProperties) 
    this.configurationProperties = configurationProperties;
  

  public Configuration getConfiguration() 
    return configuration;
  

  public void setConfiguration(Configuration configuration) 
    this.configuration = configuration;
  

  public Resource[] resolveMapperLocations() 
    ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    List<Resource> resources = new ArrayList<Resource>();
    if (this.mapperLocations != null) 
      for (String mapperLocation : this.mapperLocations) 
        try 
          Resource[] mappers = resourceResolver.getResources(mapperLocation);
          resources.addAll(Arrays.asList(mappers));
         catch (IOException e) 
          // ignore
        
      
    
    return resources.toArray(new Resource[resources.size()]);
  

可以看到Mybatis初始化所需要的很多属性都在这里,相当于一个JavaBean

下面重点看一下MybatisAutoConfiguration的代码:

@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;
  private final DatabaseIdProvider databaseIdProvider;
  private final List<ConfigurationCustomizer> configurationCustomizers;
  public MybatisAutoConfiguration(MybatisProperties properties,
                                  ObjectProvider<Interceptor[]> interceptorsProvider,
                                  ResourceLoader resourceLoader,
                                  ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                  ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) 
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  

  @PostConstruct
  public void checkConfigFileExists() 
    if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) 
      Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
      Assert.state(resource.exists(), "Cannot find config location: " + resource
          + " (please add config file or check your Mybatis configuration)");
    
  

  @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()));
    
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) 
      configuration = new Configuration();
    
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) 
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) 
        customizer.customize(configuration);
      
    
    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) 
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    
    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();
  

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) 
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) 
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
     else 
      return new SqlSessionTemplate(sqlSessionFactory);
    
  

  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware 
    private BeanFactory beanFactory;
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) 

      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      try 
        if (this.resourceLoader != null) 
          scanner.setResourceLoader(this.resourceLoader);
        

        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        if (logger.isDebugEnabled()) 
          for (String pkg : packages) 
            logger.debug("Using auto-configuration base package ''", pkg);
          
        

        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(packages));
       catch (IllegalStateException ex) 
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
      
    

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException 
      this.beanFactory = beanFactory;
    

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) 
      this.resourceLoader = resourceLoader;
    
  

  @org.springframework.context.annotation.Configuration
  @Import( AutoConfiguredMapperScannerRegistrar.class )
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration 
  
    @PostConstruct
    public void afterPropertiesSet() 
      logger.debug("No  found.", MapperFactoryBean.class.getName());
    
  

这个类就是一个Configuration(配置类),它里面定义很多bean,其中最重要的就是SqlSessionFactory的bean实例,该实例是Mybatis的核心功能,用它创建SqlSession,对数据库进行CRUD操作。

除此之外,MybatisAutoConfiguration类还包含了: