springboot中的两个基本jar包
spring-boot-starter-parent (非必需)
作为parent之后所有官方定义的starter包都适配自己的version,减少jar包冲突,
里边引用了dependencies,在dependencyManage中定义了所有版本,可以直接用
如果不以这个包作为parent,需要自己指定版本,或者自己定义dependencyManage
spring-boot-starter (必需)
springboot 必须的jar包,核心启动器,包含自动配置,日志,spring-core,yaml
其中 spring-boot 包含了启动类 run 方法的相关包
spring-boot-autoconfigure 包含了 @SpringBootApplication 相关包(自动配置)
springboot 两大特性:
- 起步依赖
起步依赖本质上是一个Maven项目对象模型(Project Object Model),即pom,定义了对其他库的传递依赖;
简单来说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。
- 自动配置
springboot在我们引入对应的starter时,会自动的将一些配置类的bean注册进ioc容器,并且自动为我们配置一些组件的相关配置,
1、在需要的地方可以直接使用@Autowired或者@Resource等注解来使用
2、无需配置或者只需要少量配置就能运行编写的项目。
依赖管理
-
版本依赖(为什么jar包不需要指定版本?)
在springboot项目中,都会有一个spring-boot-starter-parent的依赖
<!-- Spring Boot父项目依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent<11./artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
在上述配置中,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并将项目版本号统一为2.2.2.RELEASE,该版本号根据实际开发需求是可以修改的。
点进spring-boot-starter-parent去查看,发现其引用了一个spring-boot-dependencies的parent
在spring-boot-dependencies里边有一个properties节点维护了所有的版本号(只是针对于spring官方维护的starter,第三方定义的不在其内),有一个dependencyManagement节点,配置了所有的jar包依赖,后期引用jar包时只需要写groupId和artifactId即可。
<properties>
<activemq.version>5.15.11</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.77</appengine-sdk.version>
<artemis.version>2.10.1</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
...
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
...
</dependencies>
</dependencyManagement>
例如引入web相关依赖,只需要在dependencies节点下引入如下依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-
jar包依赖(为什么引入一个jar包就可以直接启动?)
每一个starter中都封装了当前starter的所有依赖jar包,所以只需要引入对应的starter即可应用该组件,以spring-boot-starter-web为例:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
在pom文件中dependencies节点下引入了web环境依赖的所有jar包,包括web-mvc和tomcat等,以及spring-boot-starter中自带的spring的ioc和aop相关jar包,这样在引入当前starter时就可以实现web场景开发。
springboot官方除了提供web依赖容器外,还提供了其他场景相关的starter,从官方文档中可以看到如下34种,根据版本不同,可能还会有对应的增减。
这些依赖启动器(starter)适用于不同的场景开发,使用时只要在pom.xml文件中导入对应的依赖启动器即可,不需要特别指定version;
需要说明的是,Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据 库操作框架MyBatis、阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器。为 了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,MyBatis、 Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如 mybatis-spring-boot-starter、druid-spring-boot-starter等。我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。
自动配置(启动类分析)
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
代码包含两部分,一个是@SpringBootApplication注解,一个是run()方法。
@SpringBootApplication注解
点进去发现@SpringBootApplication注解是一个组合注解,上边是注解的元数据信息,下边引入了其他注解:
- @Target 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
- @Retention 表示注解的生命周期,Runtime运行时
- @Documented 表示注解可以记录在javadoc中
- @Inherited 表示可以被子类继承该注解
- @SpringBootConfiguration 标明该类为配置类
- @EnableAutoConfiguration 启动自动配置功能
- @ComponentScan 包扫描器
@SpringBootConfiguration
@SpringBootConfiguration 注解表示该类是springboot的配置类,它是一个组合注解,引入了一个 @Configuration 的注解(Spring的注解,本身是 @Component 注解的一个封装,相当于起了个别名),标识是一个配置类
@EnableAutoConfiguration
@EnableAutoConfiguration 注解表示开启自动配置功能,它是一个组合注解,引入了一个 @AutoConfigurationPackage 注解(会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中) 和 一个 @Import 注解(帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中)
@AutoConfigurationPackage
@AutoConfigurationPackage 注解,引入了一个 @Import 注解(spring框架的底层注解,它的作用就是给容器中导入某个组件类,此处就是将Registrar这个组件类导入到容器中)
对应实现 org.springframework.beans.factory.support.DefaultListableBeanFactory
跟代码发现,@Import(AutoConfigurationPackages.Registrar.class) 实际上是将 org.springframework.boot.autoconfigure.AutoConfigurationPackages 注册到 beanDefinitionMap 中
@Import(AutoConfigurationImportSelector.class)
将 AutoConfigurationImportSelector这个类导入到spring容器中, AutoConfigurationImportSelector可以帮助springboot应用将所有符合条件的@Configuration配置 都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
点进去发现 AutoConfigurationImportSelector 类主要是实现了DeferredImportSelector 接口,其他 Aware 结尾的接口是获取一些配置信息,ordered 进行排序,当前功能
DeferredImportSelector 接口继承了 ImportSelector 接口,有一个默认的方法 getImportGroup() ,返回了内部接口 Group 的一个实现类,Group 有 process 和 selectImports 两个方法需要实现,核心逻辑都在这里边
在spring加载 ioc 容器时,AbstractApplicationContext 类里边的核心方法 refresh() 会调用 ConfigurationClassParser 类的 getImports() 方法,getImports 方法会执行所有实现了DeferredImportSelector 接口的实现
在 AutoConfigurationImportSelector 的实现中,process 方法会去调用 getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) 方法,读取所有符合条件的配置并返回 AutoConfigurationEntry ,并调用 selectImports 方法将 AutoConfigurationEntry 转换成 Iterable<Entry>,最终通过 loadBeanDefinitionsForConfigurationClass 方法注册到 BeanDefinationMap中
上边提到的DeferredImportSelector 接口继承的 ImportSelector 接口,需要实现 String[] selectImports(AnnotationMetadata var1) 方法;
在AutoConfigurationImportSelector的实现中,selectImports 方法会去调用getAutoConfigurationEntry 方法,返回所有的配置类(断点调试未走到该方法)
默认情况下,有一个 CacheConfigurationImportSelector ,实现了该方法,返回对应的配置类,此处cache默认返回了十个springboot定义的缓存配置类
后边通过 processImports 方法将配置类注册到 BeanDefinationMap中
跟代码发现,@Import(AutoConfigurationImportSelector.class) 实际上是将 resources/META-INF/spring.factories 文件中读取到的 EnableAutoConfiguration 根据条件筛选后的配置以及实现 ImportSelector 接口中 String[] selectImports(AnnotationMetadata var1) 方法获取到的配置类注册到 beanDefinitionMap 中
Run方法
分为 new SpringApplication(primarySources) 和 run(args) 两步,整个执行流程如图所示:
-
new 方法
指定主类,判断servlet容器类型,后边run方法会根据类型创建对应容器,设置了bootstrapRegistryInitializers,initializers,listeners,并确定了启动类
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details). The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 根据加载的类路径是否存在对应类型来判断 web 容器的类型:servlet,reactive,none,没设置容器默认为 none
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 从 resources/META-INF/spring.factories 文件中读取配置的BootstrapRegistryInitializer并实例化
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 从 resources/META-INF/spring.factories 文件中读取配置的ApplicationContextInitializer并实例化
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 从 resources/META-INF/spring.factories 文件中读取配置的ApplicationListener并实例化
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 从堆栈跟踪对象数组中,找到main方法对应的类作为启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
-
run 方法
加载配置,启动监听器,打印banner,根据servlet类型创建了ioc容器并加载实例刷新容器,执行 callRunners 方法(springboot如何在启动结束后执行方法?)
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
// 根据从spring.factory 中读取到bootstrapRegistryInitializers初始化启动上下文容器
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 获取并启动监听器,从 resources/META-INF/spring.factories 文件中读取配置的 SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听器,发布事件,invokeListener,onApplicationEvent
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 根据监听器以及启动参数来准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 排除不需要的运行环境
configureIgnoreBeanInfo(environment);
// 根据bannerMode判断是否打印,读取resource目录下的banner文件并打印
Banner printedBanner = printBanner(environment);
// 根据 new SpringApplication 时判断的servlet(webApplicationType)类型,创建IOC容器
context = createApplicationContext();
// 设置 DefaultApplicationStartup
context.setApplicationStartup(this.applicationStartup);
// spring ioc 容器前置处理,将environment,listeners,applicationArguments,printedBanner 加载进容器
// 将springApplicationArguments,springBootBanner加载进单例池,设置是否允许循环依赖,是否允许覆盖注册,是否允许懒加载
// 将来源文件(启动类)注册进 AnnotatedBeanDefinitionReader ,后边根据对应的来源文件去进行加载
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 注册关闭勾子,在jvm关闭时关闭上下文;调用 spring 的refresh方法,刷新ioc容器,
// 这一步执行完之后,beanDefinationMap 和 singletonObject 中就会加载进去所有扫描到的类
refreshContext(context);
// 后置处理,空模板,可重写扩展
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 观察者模式,设置回调事件,发布 SpringApplicationRunListeners 的 started 监听事件
listeners.started(context, timeTakenToStartup);
// 启动完成之后执行 ApplicationRunner 和 CommandLineRunner,都是执行run方法,没指定order的情况下,限制性ApplicationRunner,再执行CommandLineRunner
// ApplicationRunner 参数是 kv 格式 ApplicationArguments args
// CommandLineRunner 参数是数组格式 String... args
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 返回创建完的容器
return context;
}
run 方法中最核心的两步 prepareContext 和 refreshContext
prepareContext 方法
做了spring ioc 容器前置处理,
将environment,listeners,applicationArguments,printedBanner 加载进容器,
将springApplicationArguments,springBootBanner加载进单例池,设置是否允许循环依赖,是否允许覆盖注册,是否允许懒加载 ,
将来源文件(启动类)注册进 AnnotatedBeanDefinitionReader ,后边根据对应的来源文件去进行加载
refreshContext 方法
注册关闭勾子,在jvm关闭时关闭上下文;
调用 spring 的refresh方法,刷新ioc容器,AbstractApplicationContext 类的 refresh() 方法,是spring 容器的核心方法,ioc,aop 都会在这一步处理,需要单独去分析
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 将前置加载的配置信息放到 context 中
context.setEnvironment(environment);
// 对 context 进行后置处理,配置beanName生成器,配置加载器、类加载器,转换服务
postProcessApplicationContext(context);
// 在 context 容器中,对前边加载的 initializers 进行初始化
applyInitializers(context);
// 观察者模式,设置回调事件,发布 SpringApplicationRunListeners 的 context-prepared 监听事件
listeners.contextPrepared(context);
// 通过 ApplicationEventMulticaster 发布启动上下文关闭事件 BootstrapContextClosedEvent
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 注册 springApplicationArguments 进单例池 singletonObjects
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
// 注册 springBootBanner 进单例池 singletonObjects
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
// 设置是否允许循环引用
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
// 设置是否允许 bean 覆盖注入
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
// 如果是懒加载,设置对应的后置处理器
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// DefaultPropertiesPropertySource 设置为最低优先级,最后执行
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// 获取所有的来源文件,主要是启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 将对应的源注册进 AnnotatedBeanDefinitionReader,后边根据对应的来源文件去进行加载
load(context, sources.toArray(new Object[0]));
// 观察者模式,设置回调事件,发布 SpringApplicationRunListeners 的 context-loaded 监听事件
listeners.contextLoaded(context);
}
prepareContext 中的 postProcessApplicationContext(context) 方法
/**
* Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
* apply additional processing as required.
* @param context the application context
*/
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
// 如果当前容器名字的生成器不为空,往单例池中注入配置beanName生成器 internalConfigurationBeanNameGenerator
context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
if (this.resourceLoader != null) {
// 如果配置的加载器不为空,将配置加载器、类加载器都保存到容器工厂中
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
}
}
if (this.addConversionService) {
// 将环境中配置的的转换服务保存到容器中
context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
}
}