我们在使用 Spring Boot 的过程中,往往都是在pom.xml里加了一系列的依赖,然后启支一个包含main方法的Application,一切就OK啦。给你我的感觉,就像是 自己要动手做个菜,自己不再需要准备每一部分的原材料,直接购买包装好的一份菜的原料,下锅即可 。
那我们详细看下,这份「包装好」的原料中,到底做了些什么。
添加Starter依赖
这里添加的依赖,除了我们之前在Maven中熟悉的之外,还有一些都是长这个样子:
名为xxx-starter,比如
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency>
具体这些starter是怎么起作用的呢,他们什么时候开始工作的?
一切都要从入口处说起。我们以上面的starter为例,看到这个mybatis的starter,其对应的pom中,包含这些依赖
<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>
我们看到,相当于我们添加了一个Starter的依赖,其背后会引入许多其定义的其他依赖,通过 Maven 的传递依赖,这些都会被自动添加了进来。
自动配置
相比传统的依赖,我们看到其中包含这样一个: mybatis-spring-boot-autoconfigure
,这也是每个Starter的秘密所在:「AutoConfigure」
它会在实现时,考虑应用中的其他部分因素,「推断」你所需要的 Spring 配置。
在Spring Boot中,我们最大的感受是配置仿佛都被做好了,直接使用即可,这就是
spring-boot-autoconfigure
. 每个starter都有一个名为
spring.factories
的文件,存放在META-INF目录下,其中的内容类似下面这个样子:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
所有需要自动配置的Class,都需要配置成key是 EnableAutoConfiguration
的。
我们来看类的内部
@Configuration @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) @ConditionalOnBean({DataSource.class}) @EnableConfigurationProperties({MybatisProperties.class}) @AutoConfigureAfter({DataSourceAutoConfiguration.class}) public class MybatisAutoConfiguration {
Class 之上, 有不少注解来标识,有几点需要关注的:
其中有标准的 Spring 配置 注解 @Configuration
几个 @ConditionalXX
标识执行顺序的 @AutoConfigureAfter
其中, @ConditionalOnClass
标识 SqlSessionFactory
类存在时,执行该配置, @ConditionalOnBean
标识 DataSource
Bean 在 Spring Context时,执行配置。
这些 spring.factories
是怎么被识别的呢? 这就得夸下 Spring 的 FactoriesLoader
了。
看下官方文档说明
Auto-configuration classes are regular Spring {@link Configuration} beans. They are located using the {@link SpringFactoriesLoader} mechanism (keyed against this class). Generally auto-configuration beans are {@link Conditional @Conditional} beans (most often using {@link ConditionalOnClass @ConditionalOnClass} and {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
启动的时候,根据ClassLoader中的jar,扫描所有 spring.factories
,将其中符合条件的过滤出来,执行对应的配置。重点可以关注下
AutoConfigurationImportFilter
类,
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); }
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } return new ArrayList<>(result); } public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }
经过这里的执行之后, filter方法把符合条件的过滤出来了。
创建自定义Starter
经过上面两步,我们大概知道 Starter的工作原理。有时候,我们需要对外提供一些工具组件时,也想以 Starter 的形式提供出来,供别人使用。步骤也还算清晰,照葫芦画瓢。
先创建自己的模块
增加需要用到的依赖
创建对应的 AutoConfiguration
类
创建 META-INF/spring.factories
文件
此时,就不需要再将 Spring Boot 做为 Parent依赖,在单独的依赖中增加
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.0.6.RELEASE</version> </dependency>
AutoConfiguration类也简单,照上面的创建一个
@Configuration @ConditionalOnClass(HelloService.class) public class HelloServiceAutoConfiguration {
然后,增加文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.HelloServiceAutoConfiguration
在需要这个服务的地方,直接引入依赖就OK啦。
相关阅读:
Spring 的自定义Schema是如何解析生效的
Tomcat 是怎样处理 SpringBoot应用的?
关注『 Tomcat那些事儿 』 ,发现更多精彩文章!了解各种常见问题背后的原理与答案。深入源码,分析细节,内容原创,欢迎关注。
转发是最大的支持 ,谢谢
更多精彩内容:
一台机器上安装多个Tomcat 的原理(回复001)
监控Tomcat中的各种数据 (回复002)
启动Tomcat的安全机制(回复003)
乱码问题的原理及解决方式(回复007)
Tomcat 日志工作原理及配置(回复011)
web.xml 解析实现(回复 012)
线程池的原理( 回复 014)
Tomcat 的集群搭建原理与实现 (回复 015)
类加载器的原理 (回复 016)
类找不到等问题 (回复 017)
代码的热替换实现(回复 018)
Tomcat 进程自动退出问题 (回复 019)
为什么总是返回404? (回复 020)
...
PS: 对于一些 Tomcat常见问 题 ,在公众号的【 常见问题 】菜单中,有需要的朋友欢迎关注查看。