目录
1.pom.xml
1.1父依赖
1.2启动器
2.主程序类@SpringBootApplication分析
2.1介绍
2.2@SpringBootConfiguration
2.2.1@Configuration
2.3@EnableAutoConfiguration
2.3.1@AutoConfigurationPackage
2.3.2@Import({AutoConfigurationImportSelector.class})
spring.factories
2.4@ComponentScan
3.SpringApplication
3.1 SpringApplication职责
3.2 run方法执行流程分析
结语
1.pom.xml
1.1父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
</parent>
我们点击进去探以下究竟。
发现里面还有依赖一个父项目。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.0</version>
</parent>
我们继续把代码一层一层剥掉,继续点击进去往里面探究。
果真!发现了一个宝藏!
<properties>
<activemq.version>5.16.3</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.92</appengine-sdk.version>
<artemis.version>2.19.0</artemis.version>
<aspectj.version>1.9.7</aspectj.version>
<assertj.version>3.21.0</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.1.1</awaitility.version>
<build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
.......
</properties>
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
问题:为什么用SpringBoot有的依赖需要写版本号,有的不需要写?
答案: 因为我们项目的pom里引入了parent节点,所以将最终的spring-boot-dependencies-2.6.0.pom也继承过来,而spring-boot-dependencies-2.6.0.pom里面管理了大部分的jar包的版本,所以我们依赖jar包的时候无需写版本号;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
我们回过头去看我们注入的另一个依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
是否发现在这个依赖中就不需要我们写版本号,因为在父节点中已经管理了这个jar包的版本,我们去spring-boot-dependencies-2.6.0.pom查看是否有管理这个jar包。
果然,这个jar包有被SpringBoot所管理,所以不需要我们写配置文件。
问题:为什么启动main函数就能访问url?
答案:因为SpringBoot内嵌了一个tomcat。
1.2启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
分析:
springboot-boot-starter-xxx:就是spring-boot的场景启动器
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可?
2.主程序类@SpringBootApplication分析
@SpringBootApplication
public class SpringBootIdeaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootIdeaApplication.class, args);
}
}
2.1介绍
@SpringBootApplication: 标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
我们来分析@SpringBootApplication这个注解到底做了什么?
点击进入@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 {
//......
}
看到这么多注解,不要慌,其实这里面只有三个关键的注解:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
我们来对这三个注解一一进行讲解,看下这三个注解到底做了什么?
2.2@SpringBootConfiguration
介绍:SpringBoot配置类。标注在某个类上,表示这是一个SpringBoot的配置类 我们进入这个注解进行查看:?
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
2.2.1@Configuration
@Configuration:说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件,也是一个组件(因为内部标注了@Component注解); ?
为了验证其也是一个组件,我们继续进入@Configuration的源码进行查看,看下是否有@Component注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
果然看到了@Component ,这就说明该配置类也是容器中的一个组件,负责启动应用!
2.3@EnableAutoConfiguration
介绍:开启自动配置功能,那么以前我们需要配置的东西,Spring Boot帮我们自动配置,再也不需要写一堆繁琐的配置文件。
我们点击进去查看代码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
发现里面有两个重要的注解:
- @AutoConfigurationPackage
- @Import({AutoConfigurationImportSelector.class})
2.3.1@AutoConfigurationPackage
源码分析:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
分析:
@Import({Registrar.class}):给Spring容器中导入了Registrar组件
作用:将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器。
2.3.2@Import({AutoConfigurationImportSelector.class})
作用:快速给容器中导入一些组件。
AutoConfigurationImportSelector:
介绍:导入哪些组件的选择器。
作用:将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。
使用场景:会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
我们去查看一下它会导入哪些组件的选择器,进入AutoConfigurationImportSelector源码进行分析一下。
(1)该类有这样一个方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
(2)这个方法又调用了 ?SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//这里又调用了loadSpringFactories方法
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
(3)我们继续点击查看 loadSpringFactories 方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
//去获取一个资源 "META-INF/spring.factories"
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//将读取到的资源遍历,封装成为一个Properties
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
(4)发现一个多次出现的文件:spring.factories,全局搜索它
spring.factories
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
2.4@ComponentScan
Spring的注解,相当于配置文件的
<context:component-scan base-package="xxx">
<context:exclude-filter type="annotation" expression="xxxx"/>
</context:component-scan>
以上就是对@SpringBootApplication注解的运行原理进行讲解,我们用一张图来概括以上的知识点,将其串接起来,方便我们理解。
3.SpringApplication
我们再来看一下这个启动类:
@SpringBootApplication
public class SpringBootIdeaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootIdeaApplication.class, args);
}
}
分析该方法主要分两部分:
(1)SpringApplication的实例化
(2)run方法的执行;
3.1 SpringApplication职责
(1)推断应用的类型是普通的项目还是Web项目
(2)查找并加载所有可用初始化器 , 设置到initializers属性中
(3)找出所有的应用程序监听器,设置到listeners属性中
(4)推断并设置main方法的定义类,找到运行的主类
3.2 run方法执行流程分析
结语
以上就是一心同学对SpringBoot的核心组件进行的基本解析,综合来看,大部分都是Spring框架背后的一些概念和实践方式,SpringBoot只是在这些概念和实践上对特定的场景事先进行了固化和升华,而也恰恰是这些固化让我们开发基于Sping框架的应用更加方便高效。
|