SpringBoot的starter到底是什么?

╰半橙微兮° 2023-09-26 10:10 59阅读 0赞

前言

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

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

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

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

9027f596aa164b61af35ada88c639f44.png

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是如何定义的。

4abbb384d0d0d2fd993219f9efcf8770.png

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

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

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

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>org.mybatis.spring.boot</groupId>
  6. <artifactId>mybatis-spring-boot</artifactId>
  7. <version>1.3.1</version>
  8. </parent>
  9. <artifactId>mybatis-spring-boot-starter</artifactId>
  10. <name>mybatis-spring-boot-starter</name>
  11. <dependencies>
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter</artifactId>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-jdbc</artifactId>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.mybatis.spring.boot</groupId>
  22. <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.mybatis</groupId>
  26. <artifactId>mybatis</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.mybatis</groupId>
  30. <artifactId>mybatis-spring</artifactId>
  31. </dependency>
  32. </dependencies>
  33. </project>

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

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

59946fd0cea9323ecc75b78bf52a8db2.png

里面包含如下文件:

  • 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时,会自动出现下面的配置信息可供选择,就是这个功能了。

c3a3edcccc67986f8cbbfcbc11060f44.png

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

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

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

MybatisProperties类是属性实体类:

  1. @ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
  2. public class MybatisProperties {
  3. public static final String MYBATIS_PREFIX = "mybatis";
  4. private String configLocation;
  5. private String[] mapperLocations;
  6. private String typeAliasesPackage;
  7. private String typeHandlersPackage;
  8. private boolean checkConfigLocation = false;
  9. private ExecutorType executorType;
  10. private Properties configurationProperties;
  11. @NestedConfigurationProperty
  12. private Configuration configuration;
  13. public String getConfigLocation() {
  14. return this.configLocation;
  15. }
  16. public void setConfigLocation(String configLocation) {
  17. this.configLocation = configLocation;
  18. }
  19. @Deprecated
  20. public String getConfig() {
  21. return this.configLocation;
  22. }
  23. @Deprecated
  24. public void setConfig(String config) {
  25. this.configLocation = config;
  26. }
  27. public String[] getMapperLocations() {
  28. return this.mapperLocations;
  29. }
  30. public void setMapperLocations(String[] mapperLocations) {
  31. this.mapperLocations = mapperLocations;
  32. }
  33. public String getTypeHandlersPackage() {
  34. return this.typeHandlersPackage;
  35. }
  36. public void setTypeHandlersPackage(String typeHandlersPackage) {
  37. this.typeHandlersPackage = typeHandlersPackage;
  38. }
  39. public String getTypeAliasesPackage() {
  40. return this.typeAliasesPackage;
  41. }
  42. public void setTypeAliasesPackage(String typeAliasesPackage) {
  43. this.typeAliasesPackage = typeAliasesPackage;
  44. }
  45. public boolean isCheckConfigLocation() {
  46. return this.checkConfigLocation;
  47. }
  48. public void setCheckConfigLocation(boolean checkConfigLocation) {
  49. this.checkConfigLocation = checkConfigLocation;
  50. }
  51. public ExecutorType getExecutorType() {
  52. return this.executorType;
  53. }
  54. public void setExecutorType(ExecutorType executorType) {
  55. this.executorType = executorType;
  56. }
  57. public Properties getConfigurationProperties() {
  58. return configurationProperties;
  59. }
  60. public void setConfigurationProperties(Properties configurationProperties) {
  61. this.configurationProperties = configurationProperties;
  62. }
  63. public Configuration getConfiguration() {
  64. return configuration;
  65. }
  66. public void setConfiguration(Configuration configuration) {
  67. this.configuration = configuration;
  68. }
  69. public Resource[] resolveMapperLocations() {
  70. ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
  71. List<Resource> resources = new ArrayList<Resource>();
  72. if (this.mapperLocations != null) {
  73. for (String mapperLocation : this.mapperLocations) {
  74. try {
  75. Resource[] mappers = resourceResolver.getResources(mapperLocation);
  76. resources.addAll(Arrays.asList(mappers));
  77. } catch (IOException e) {
  78. // ignore
  79. }
  80. }
  81. }
  82. return resources.toArray(new Resource[resources.size()]);
  83. }
  84. }

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

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

  1. @org.springframework.context.annotation.Configuration
  2. @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
  3. @ConditionalOnBean(DataSource.class)
  4. @EnableConfigurationProperties(MybatisProperties.class)
  5. @AutoConfigureAfter(DataSourceAutoConfiguration.class)
  6. public class MybatisAutoConfiguration {
  7. private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
  8. private final MybatisProperties properties;
  9. private final Interceptor[] interceptors;
  10. private final ResourceLoader resourceLoader;
  11. private final DatabaseIdProvider databaseIdProvider;
  12. private final List<ConfigurationCustomizer> configurationCustomizers;
  13. public MybatisAutoConfiguration(MybatisProperties properties,
  14. ObjectProvider<Interceptor[]> interceptorsProvider,
  15. ResourceLoader resourceLoader,
  16. ObjectProvider<DatabaseIdProvider> databaseIdProvider,
  17. ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
  18. this.properties = properties;
  19. this.interceptors = interceptorsProvider.getIfAvailable();
  20. this.resourceLoader = resourceLoader;
  21. this.databaseIdProvider = databaseIdProvider.getIfAvailable();
  22. this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  23. }
  24. @PostConstruct
  25. public void checkConfigFileExists() {
  26. if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
  27. Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
  28. Assert.state(resource.exists(), "Cannot find config location: " + resource
  29. + " (please add config file or check your Mybatis configuration)");
  30. }
  31. }
  32. @Bean
  33. @ConditionalOnMissingBean
  34. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  35. SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  36. factory.setDataSource(dataSource);
  37. factory.setVfs(SpringBootVFS.class);
  38. if (StringUtils.hasText(this.properties.getConfigLocation())) {
  39. factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  40. }
  41. Configuration configuration = this.properties.getConfiguration();
  42. if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
  43. configuration = new Configuration();
  44. }
  45. if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
  46. for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
  47. customizer.customize(configuration);
  48. }
  49. }
  50. factory.setConfiguration(configuration);
  51. if (this.properties.getConfigurationProperties() != null) {
  52. factory.setConfigurationProperties(this.properties.getConfigurationProperties());
  53. }
  54. if (!ObjectUtils.isEmpty(this.interceptors)) {
  55. factory.setPlugins(this.interceptors);
  56. }
  57. if (this.databaseIdProvider != null) {
  58. factory.setDatabaseIdProvider(this.databaseIdProvider);
  59. }
  60. if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
  61. factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
  62. }
  63. if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
  64. factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
  65. }
  66. if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
  67. factory.setMapperLocations(this.properties.resolveMapperLocations());
  68. }
  69. return factory.getObject();
  70. }
  71. @Bean
  72. @ConditionalOnMissingBean
  73. public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  74. ExecutorType executorType = this.properties.getExecutorType();
  75. if (executorType != null) {
  76. return new SqlSessionTemplate(sqlSessionFactory, executorType);
  77. } else {
  78. return new SqlSessionTemplate(sqlSessionFactory);
  79. }
  80. }
  81. public static class AutoConfiguredMapperScannerRegistrar
  82. implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  83. private BeanFactory beanFactory;
  84. private ResourceLoader resourceLoader;
  85. @Override
  86. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  87. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  88. try {
  89. if (this.resourceLoader != null) {
  90. scanner.setResourceLoader(this.resourceLoader);
  91. }
  92. List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
  93. if (logger.isDebugEnabled()) {
  94. for (String pkg : packages) {
  95. logger.debug("Using auto-configuration base package '{}'", pkg);
  96. }
  97. }
  98. scanner.setAnnotationClass(Mapper.class);
  99. scanner.registerFilters();
  100. scanner.doScan(StringUtils.toStringArray(packages));
  101. } catch (IllegalStateException ex) {
  102. logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
  103. }
  104. }
  105. @Override
  106. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  107. this.beanFactory = beanFactory;
  108. }
  109. @Override
  110. public void setResourceLoader(ResourceLoader resourceLoader) {
  111. this.resourceLoader = resourceLoader;
  112. }
  113. }
  114. @org.springframework.context.annotation.Configuration
  115. @Import({ AutoConfiguredMapperScannerRegistrar.class })
  116. @ConditionalOnMissingBean(MapperFactoryBean.class)
  117. public static class MapperScannerRegistrarNotFoundConfiguration {
  118. @PostConstruct
  119. public void afterPropertiesSet() {
  120. logger.debug("No {} found.", MapperFactoryBean.class.getName());
  121. }
  122. }
  123. }

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

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

  • @ConditionalOnClass 配置了只有包含SqlSessionFactory.class和SqlSessionFactoryBean.class,该配置类才生效。
  • @ConditionalOnBean 配置了只有包含dataSource实例时,该配置类才生效。
  • @EnableConfigurationProperties 该注解会自动填充MybatisProperties实例中的属性。
  • AutoConfigureAfter 配置了该配置类在DataSourceAutoConfiguration类之后自动配置。

这些注解都是一些辅助功能,决定Configuration是否生效,当然这些注解不是必须的。

接下来,重点看看spring.factories文件有啥内容:

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

里面只有一行配置,即keyEnableAutoConfigurationvalueMybatisAutoConfiguration

好了,介绍了这么多东西,现在我们来总结一下,

starter几个要素如下图所示:

1d9717f770dec09eb852c3aaa1ff7994.png

那么,编写starter需要哪些步骤?

  • 1.需要定义一个名称为xxx-spring-boot-starter的空项目,里面不包含任何代码,可以有pom.xml和pom.properties文件。
  • 2.pom.xml文件中包含了名称为xxx-spring-boot-autoconfigure的项目。
  • 3.xxx-spring-boot-autoconfigure项目中包含了名称为xxxAutoConfiguration的类,该类可以定义一些bean实例。当然,Configuration类上可以打一些如:ConditionalOnClass、ConditionalOnBean、EnableConfigurationProperties等注解。
  • 4.需要在spring.factories文件中增加key为EnableAutoConfiguration,value为xxxAutoConfiguration。

我们试着按照这四步,自己编写一个starter看看能否成功,验证一下总结的内容是否正确。

3 如何定义自己的starter?

3.1 先创建一个空项目

该项目名称为id-generate-starter,注意为了方便我把项目重命名了,原本应该是叫id-generate-spring-boot-starter的,如下图所示:

18eea554ba7c4869ed800432492fe02f.png

pom.xml文件定义如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <version>1.3.1</version>
  5. <groupId>com.sue</groupId>
  6. <artifactId>id-generate-spring-boot-starter</artifactId>
  7. <name>id-generate-spring-boot-starter</name>
  8. <dependencies>
  9. <dependency>
  10. <groupId>com.sue</groupId>
  11. <artifactId>id-generate-spring-boot-autoconfigure</artifactId>
  12. <version>1.3.1</version>
  13. </dependency>
  14. </dependencies>
  15. </project>

我们看到,它只引入了id-generate-spring-boot-autoconfigure。当然如果有需要这里还可以引入多个autoconfigure或者多个其他jar包或者。

3.2 创建id-generate-autoconfigure

同样为了方便我把项目重命名了,原本是叫id-generate-spring-boot-autoconfigure,如下图所示:

62b59c1683a47a7d53bae84b7f6020c0.png

该项目当中包含:pom.xml、spring.factories、IdGenerateAutoConfiguration、IdGenerateService 和 IdProperties 这5个关键文件,下面我们逐一看看。

先从pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <parent>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-parent</artifactId>
  6. <version>2.0.4.RELEASE</version>
  7. </parent>
  8. <modelVersion>4.0.0</modelVersion>
  9. <version>1.3.1</version>
  10. <groupId>com.sue</groupId>
  11. <artifactId>id-generate-spring-boot-autoconfigure</artifactId>
  12. <name>id-generate-spring-boot-autoconfigure</name>
  13. <dependencies>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-autoconfigure</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-configuration-processor</artifactId>
  25. <optional>true</optional>
  26. </dependency>
  27. </dependencies>
  28. <build>
  29. <plugins>
  30. <plugin>
  31. <groupId>org.apache.maven.plugins</groupId>
  32. <artifactId>maven-compiler-plugin</artifactId>
  33. <configuration>
  34. <source>1.8</source>
  35. <target>1.8</target>
  36. </configuration>
  37. </plugin>
  38. </plugins>
  39. </build>
  40. </project>

我们可以看到,这个文件比较简单就引入了:

  • spring-boot-starter:springboot的相关jar包。
  • spring-boot-autoconfigure:springboot自动配置相关jar包。
  • spring-boot-configuration-processor:springboot生成IDE提示功能相关jar包。

重点看看spring.factories文件:

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.sue.IdGenerateAutoConfiguration

它里面只包含一行配置,其中key是EnableAutoConfiguration,value是IdGenerateAutoConfiguration。

再重点看一下IdGenerateAutoConfiguration

  1. @ConditionalOnClass(IdProperties.class)
  2. @EnableConfigurationProperties(IdProperties.class)
  3. @Configuration
  4. public class IdGenerateAutoConfiguration {
  5. @Autowired
  6. private IdProperties properties;
  7. @Bean
  8. public IdGenerateService idGenerateService() {
  9. return new IdGenerateService(properties.getWorkId());
  10. }
  11. }

该类是一个使用了@Configuration注解标记为了配置类,生效的条件是@ConditionalOnClass注解中检测到包含IdProperties.class。并且使用@EnableConfigurationProperties注解会自动注入IdProperties的实例。

此外,最关键的点是该类里面创建了idGenerateService的bean实例,这是自动配置的精髓。

再看看IdGenerateService

  1. public class IdGenerateService {
  2. private Long workId;
  3. public IdGenerateService(Long workId) {
  4. this.workId = workId;
  5. }
  6. public Long generate() {
  7. return new Random().nextInt(100) + this.workId;
  8. }
  9. }

我们可以看到它是一个普通的类,甚至都没有使用@Service注解,里面有个generate方法,根据workId的值和随机数动态生成id。

最后看看IdProperties

  1. @ConfigurationProperties(prefix = IdProperties.PREFIX)
  2. public class IdProperties {
  3. public static final String PREFIX = "sue";
  4. private Long workId;
  5. public Long getWorkId() {
  6. return workId;
  7. }
  8. public void setWorkId(Long workId) {
  9. this.workId = workId;
  10. }
  11. }

它是一个配置实体类,里面包含了相关的配置文件。使用@ConfigurationProperties注解,会自动把application.properties文件中以sue开通的,参数名称跟IdProperties中一样的参数值,自动注入到IdProperties对象中。

3.3 创建id-generate-test

这个项目主要用于测试。

46c63b02664b84d0016f8995743e7710.png

该项目里面包含:pom.xml、application.properties、Application 和 TestRunner 文件。

先看看pom.xml文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <version>1.3.1</version>
  5. <groupId>com.sue</groupId>
  6. <artifactId>spring-boot-id-generate-test</artifactId>
  7. <name>spring-boot-id-generate-test</name>
  8. <dependencies>
  9. <dependency>
  10. <groupId>com.sue</groupId>
  11. <artifactId>id-generate-spring-boot-starter</artifactId>
  12. <version>1.3.1</version>
  13. </dependency>
  14. </dependencies>
  15. </project>

由于只测试刚刚定义的id生成功能,所以只引入的id-generate-spring-boot-starter jar包。

application.properties配置资源文件

  1. sue.workId=123

只有一行配置,因为我们的IdProperties中目前只需要这一个参数。

Application是测试程序启动类

  1. @SpringBootApplication
  2. public class Application {
  3. public static void main(String[] args) {
  4. SpringApplication.run(Application.class, args);
  5. }
  6. }

很简单,就是一个普通的springboot启动类

TestRunner是我们的测试类

  1. @Component
  2. public class TestRunner implements ApplicationRunner {
  3. @Autowired
  4. private IdGenerateService idGenerateService;
  5. public void run(ApplicationArguments args) throws Exception {
  6. Long sysNo = idGenerateService.generate();
  7. System.out.println(sysNo);
  8. }
  9. }

它实现了ApplicationRunner接口,所以在springboot启动的时候会调用该类的run方法。

好了,所有自定义starter的代码和测试代码都已经就绪。接下,运行一下Application类的main方法。

运行结果:

  1. 176

完美,验证成功了。

接下来,我们分析一下starter的底层实现原理。

4 starter的底层原理是什么?

通过上面编写自己的starter的例子,相信大家对starter的认识更进一步了,现在跟大家一起看看starter的底层是如何实现的。

id-generate-starter.jar其实是一个空项目,依赖于id-generate-autoconfiguration.jar。

id-generate-starter.jar是一个入口,我们给他取一个更优雅的名字:门面模式,其他业务系统想引入相应的功能,必须要通过这个门面。

我们重点分析一下 id-generate-autoconfiguration.jar

该jar包核心内容是:IdGenerateConfiguration,这个配置类中创建了IdGenerateService对象,IdGenerateService是我们所需要自动配置的具体功能。

接下来一个最重要的问题: IdGenerateConfiguration为什么会自动加载的呢?

还记得我们定义的spring.factories文件不?

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.sue.IdGenerateAutoConfiguration

它里面只包含一行配置,其中keyEnableAutoConfigurationvalueIdGenerateAutoConfiguration

要搞明白这个过程,要从Application类的@SpringBootApplication注解开始:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(excludeFilters = {
  8. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  9. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  10. public @interface SpringBootApplication {
  11. @AliasFor(annotation = EnableAutoConfiguration.class)
  12. Class<?>[] exclude() default {};
  13. @AliasFor(annotation = EnableAutoConfiguration.class)
  14. String[] excludeName() default {};
  15. @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  16. String[] scanBasePackages() default {};
  17. @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  18. Class<?>[] scanBasePackageClasses() default {};
  19. }

从上面可以看出该注解里面包含了@EnableAutoConfiguration注解。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import(AutoConfigurationImportSelector.class)
  7. public @interface EnableAutoConfiguration {
  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9. Class<?>[] exclude() default {};
  10. String[] excludeName() default {};
  11. }

@EnableAutoConfiguration注解会引入AutoConfigurationImportSelector类。

该类的selectImports方法一个关键方法:

  1. @Override
  2. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  3. //配置有没有配置spring.boot.enableautoconfiguration开关,默认为true
  4. //如果为false,则不执行自动配置的功能,直接返回
  5. if (!isEnabled(annotationMetadata)) {
  6. return NO_IMPORTS;
  7. }
  8. //找spring-autoconfigure-metadata.properties中的元素
  9. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
  10. .loadMetadata(this.beanClassLoader);
  11. //获取EnableAutoConfiguration注解中的属性
  12. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  13. //获取工程下所有配置key为EnableAutoConfiguration的值,即IdGenerateConfiguration等类。
  14. List<String> configurations = getCandidateConfigurations(annotationMetadata,
  15. attributes);
  16. //删除重复的值
  17. configurations = removeDuplicates(configurations);
  18. //获取需要排除的规则列表
  19. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  20. //检查
  21. checkExcludedClasses(configurations, exclusions);
  22. //删除需要排除的值
  23. configurations.removeAll(exclusions);
  24. //根据配置文件中配置的开关,过滤一部分不满足条件的值
  25. configurations = filter(configurations, autoConfigurationMetadata);
  26. fireAutoConfigurationImportEvents(configurations, exclusions);
  27. return StringUtils.toStringArray(configurations);
  28. }

这里就是starter能够自动配置的秘密

此外,有些朋友看其他人定义的springboot starter可能会有疑惑。

先看看druid-spring-boot-starter

4cbc94c6b9175a5bcee4900a2780b070.png

alibaba定义的druid-spring-boot-starter只有xxx-spring-boot-starter.jar文件,而没有xxx-spring-boot-autoconfigure.jar文件。

再看看spring-boot-starter-jdbc

33c0d30bc2b6d2dffb503e8eda3259f6.png

更神奇的是这个文件中连pom.xml都没有,一脸懵逼。。。。。。。

是不是我讲错了?

答:其实没有。

SpringBoot的原则是约定优于配置

从spring-boot-starter-jdbc内部空实现来看,它的约定是要把xxx-spring-boot-starter.jar和xxx-spring-boot-autoconfigure.jar区分开的。个人认为,alibaba定义得并不好,没有遵照springboot的约定,虽然功能不受影响。(这个地方欢迎一起探讨一下)

而springboot自己定义的spring-boot-starter-jdbc为什么连pom.xml文件也没有呢?

它不需要依赖xxx-spring-boot-autoconfigure.jar文件吗?

因为springboot把所有的自动配置的类都统一放到spring-boot-autoconfigure.jar下面了:

cf9cef22ffd7103a358ea53ab6a4aa24.png

spring.factories文件内容如下:SpringBoot这样集中管理自动配置,而不需要从各个子包中遍历,我个人认为是为了查找效率。

我们最后再看看spring-cloud-starter-openfegin

cb614502f29cee17af431c10f5ceac96.png

明显看到,它是遵循了我们说的原则的。

除此之外,还有一个原则一顺便提一下。

SpringBootSpringCloud系列定义jar包的名称是:

  • spring-boot-starter-xxx.jar
  • spring-cloud-starter-xxx.jar

而我们自己的项目定义的jar应该是:

  • xxx-spring-boot-starter.jar

发表评论

表情:
评论列表 (有 0 条评论,59人围观)

还没有评论,来说两句吧...

相关阅读

    相关 到底vuex什么

    关于vuex类的新闻最近很多,看到眼热就去查了下资料,然后扯出来一堆flux、redux、state、state之类的概念,以及大型工程必要性之类的。看官方手册也是昏昏然。

    相关 EJB到底什么

    1. 我们不禁要问,什么是"服务集群"?什么是"企业级开发"?  既然说了EJB 是为了"服务集群"和"企业级开发",那么,总得说说什么是所谓的"服务集群"和"企业级开

    相关 到底Redis什么

    Redis数据库 Redis数据库,隶属于NoSql类型数据库,又称非关系类型数据库。那到底什么非关系类型数据库呢? 俗话说,没有对比就没有伤害,这个时候就要掏出我们M

    相关 Servlet到底什么

    从单词本身来看,servlet可以拆分为“server缩写+英语后缀-let”,server当然指的是服务器,英语后缀-let表示“小”,整个单词就是“小服务”;