浏览博客时,若发现作者有描述错误或不清的地方,请私信或者留言讨论,共同进步
SpringBoot 是最近几年趋于流行的生产级别的应用框架,它并不是 Spring Framework 的升级版,而是让我们更好的使用 Spring Framework 的工具,其核心是以下四点:
- 自动配置 - Auto Configuration
- 起步依赖 - Starter Dependency
- 命令行界面 - Spring Boot CLI
- actuator
自动配置
SpringBoot 使用起来非常便利,不像 SpringMVC 那样需要经历“配置地狱”,一个不小心就可能导致项目启动报错,对于一些初学者来说这可能让人非常抓狂。SpringBoot 提供了自动配置,只需要你在启动类上标注 @SpringBootApplication
注解并且在 main 方法中调用 SpringApplication.run 方法就能够启动一个最简单的 SpringBoot 项目。
但是 SpringBoot 的自动配置实际上也没有你想的那么 “高大上”, 更多的是开发人员的“黑科技”,首先了解 SpringBoot 自动配置的一个突破点就是启动注解 SpringBootApplication
//省略非必要注解
// 实际就是 @Configuration 注解 但每个应用程序只能存在一个 @SpringBootConfiguration
// doc 里说明了它的作用: 替代以前的 applicationContext.xml 文件,完成 spring 容器的初始化
@SpringBootConfiguration
@EnableAutoConfiguration
// 组件路径扫描 主要是你的包路径下
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}
需要单独拎出来讲解的是 @EnableAutoConfiguration
注解。 顾名思义,它的存在就是告诉 Spring 容器开启自动配置:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
// ......
}
标注了 @EnableAutoConfiguration
的项目,就可以直接通过 @Import 引入 AutoConfigurationImportSelector
类, 该类就是专门用于处理自动配置的类,了解过源码后你就能大致明白自制的 starter 为何要做这样那样的配置。
流程
其实关键流程是可以浓缩成一句话来解释,Spring 通过约定俗成的方式将需要自动配置的类类路径放到指定的目录下通过对应的类加载器加载生成 Bean 最后注入到容器中去。
看到 AutoConfigurationImportSelector#getAutoConfigurationEntry
方法:
// 有一说一 学好英语非常重要 有时可以直接根据方法名理解功能
// this.annotationMetadata 就是你的启动类对象
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 是否开启自动配置
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 拿到配置时排除的对象 比如配置多数据源时 就要移除默认的datasource
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 = getConfigurationClassFilter().filter(configurations);
// 发布自动配置对象事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
进入到getCandidateConfigurations
方法内部后,可以发现是通过工具类 SpringFactoriesLoader.loadFactoryNames 来换取自动配置类路径集合的,看到如下:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 省略非关键代码
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 省略非关键代码
result = new HashMap<>();
try {
// FACTORIES_RESOURCE_LOCATION -> META-INF/spring.factories
// 你可以看到很多自定义的 starter 都会将需要自动配置的类放到该路径下的 spring.factories 文件里的原因就在这里,spring是通过该文件获取 URL 的
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
// 迭代遍历获取类路径
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// 用包含唯一元素的不可修改列表替换所有列表
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 塞入缓存
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
// 返回结果 获取类路径就可以获取对应的 class 文件
return result;
}
你可能还需要了解 @Import
如果你现在直接在 AutoConfigurationImportSelector#selectImports
打上断点并且启动 debug 准备大展拳脚,你会发现实际上 idea 并没有进入这个方法,这就好像跟我们之前理解的不太一样,但是你再在 AutoConfigurationImportSelector#getAutoConfigurationEntry
打上断点时,可以发现居然进去了!
了解起步依赖
实际上是非常简单的东西,起步依赖指的就是 maven 依赖管理。早期的开发可能异常艰辛,例如曾经的SSH/SSM/SSI开发组合,需要自己将工程中需要的Jar文件手动的放入工程中的lib目录中,随着功能的增多,不同jar的版本问题、循环依赖问题、后期由于安全或者jar bug升级版本而带来的一系列灾难,可能没有接触过的同志很难体会。而这些问题往往随着系统历史原因功能的堆积而变得牵一发而动全身,升级难、扩展难。
于是 Spring Boot 通过起步依赖为项目的依赖管理提供帮助,如果应用程序需要 Web 功能,可以直接向项目中添加 spring-boot-starter-web 起步依赖,如果有安全需要,就添加 spring-boot-starter-security 起步依赖,如果我们需要添加自己的起步依赖,一般是 xxx-spring-boot-starter 这种格式。