@Catyee
2021-08-09T21:04:18.000000Z
字数 11912
阅读 438
Spring
Springboot是Spring组件的一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,使开发者能快速上手。
SpringBoot采用了一个父工程来对版本进行统一的管理,所以大部分第三方包,我们无需关注版本,个别没有纳入SpringBoot管理的,才需要设置版本号。
这个父工程就是spring-boot-starter-parent,往上溯源,这个父工程的顶部就是一个pom文件,在这个pom文件中定义了各种依赖的jar包和版本。
SpringBoot将所有的常见开发功能,分成了一个个场景启动器(starter),这样我们需要开发什么功能,就导入什么场景启动器依赖即可。
比如我们需要开发一个web项目,就只需要把web starter引入即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
这些starter也会引用其它依赖的的starter,比如web starter依赖spring mvc,但是在web starter中已经将这些依赖关系设置好了,我们只需要引入一个web starter就可以了。
SpringBoot项目都会有一个启动类,这个启动类上面会加一个@SpringBootApplication的注解:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@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
@SpringBootConfiguration这个注解往上溯源,会发现就是Spring的@Configuration注解,而Spring的@Configuration注解又可以溯源到@Component这个注解,所以@SpringBootConfiguration实际上就是一个标注,表示这是一个SpringBoot的配置类。
SpringBoot实现自动配置的关键还是@EnableAutoConfiguration这个注解。
@EnableAutoConfiguration注解往上溯源有两个比较重要的注解:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class}):导入自动配置的组件。
这里出现了一个@Import注解,另外要给@AutoConfigurationPackage往上溯源,依然是@Import注解:
@Import(AutoConfigurationPackages.Registrar.class)
所以最终@EnableAutoConfiguration可以溯源为两个@Import注解:
@Import(AutoConfigurationPackages.Registrar.class)
@Import({AutoConfigurationImportSelector.class})
第一个@Import注解将AutoConfigurationPackages.Registrar的实例加载进Spring的容器,这个Register会扫描@ComponentScan注解中标注的包之中的类,如果发现是符合条件的bean,就会被Spring加载。
第二个@Import注解是用来将所有符合条件的@Configuration配置都加载到容器中:
// AutoConfigurationImportSelector类中的selectImport方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 最终会调用SpringFactoriesLoader去加载符合条件的配置类
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
上面这段代买最终会调用SpringFactoriesLoader去加载符合条件的配置类:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// ... 省略部分代码
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 取得资源文件的URL
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 遍历所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 根据资源文件URL解析properties文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// 组装数据
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
这段代码会从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。看看META-INF/spring.factories的文件内容:
// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
// 配置的key = EnableAutoConfiguration,与代码中一致
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
.....
回顾一下,@EnableAutoConfiguration中导入了AutoConfigurationImportSelector.class类,这个类的selectImports()方法通过SpringFactoriesLoader加载到大量的配置类,而每一个配置类则根据条件化配置来做出决策,以实现自动配置。
@EnableAutoConfiguration注解最终就是借助@Import注解,将所有符合自动配置条件的bean定义加载到IoC容器,注意@Import是Spring的注解。
SpringBoot自动配置的核心是源于条件注解,就是指在不同的条件下生成不同的bean,或者是在某个bean创建完成后,才去生成其他的bean,诸如此类,在特定的条件下创建bean的行为。比较常见的条件注解有:
@Conditional 依赖的条件
@ConditionalOnBean 在某个Bean存在的条件下
@ConditionalOnMissingBean 在某个Bean不存在的条件下
@ConditionalOnClass 在某个Class存在的条件下
@ConditionalOnMissingClass 在某个Class不存在的条件下
首先要说明Springboot应用通过idea启动和通过java命令去启动流程是不一样的。
一般我们会通过spring-boot-maven-plugin插件将整个应用打成一个FatJar,但这个FatJar和普通的FatJar不太一样,普通的FatJar构建时会将依赖的jar包中的类展平,放到一个同一个目录。但是Spring构建的FatJar不会将依赖的jar包展平,而是完整的保留了依赖的那些jar包,我们可以通过java命令解压spring构建的jar包来看:
jar -xvf <jar name>
解压后的结果:
[root@temp lib]# tree
.
├── BOOT-INF
│ ├── classes // 项目中编写的类
│ └── lib // 依赖的jar包
├── META-INF
│ ├── MANIFEST.MF // 修改过后的启动文件
│ └── maven
├── org
│ └── springframework
│ └── boot
│ └── loader // spring boot加载器
...
项目依赖的第三方jar包完整的存在lib目录中,但是这种打包方式不符合java标准,java标准的类加载器无法加载这种FatJar,所以SpringBoot自己实现了一套类加载器,包括JarLauncher 、WarLauncher 、PropertiesLauncher,这些Launcher都可以加载嵌套在FatJar中的jar包,相应的启动类也就变了,我们看MANIFEST.MF中的内容:
Implementation-Title: Catyee Test Service
Implementation-Version: 1.5.0
Built-By: root
Implementation-Vendor-Id: com.catyee.test
Spring-Boot-Version: 2.0.9.RELEASE
Implementation-Vendor: Catyee
Main-Class: org.springframework.boot.loader.PropertiesLauncher
Start-Class: com.catyee.test.TestBootApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.3
Build-Jdk: 1.8.0_131
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
ot-starter-parent/test/test-service
注意看Main-Class,这是这个jar包的入口类,可以看到入口类并不是我们用@SpringBootApplication注解标注的类,而是SpringBoot的PropertiesLauncher,MANIFEST.MF中还新增了一个Start-Class,这个里面才保存了我们用@SpringBootApplication注解标注的类,所以当我们用java命令来启动springboot应用的时候,实际上是先启动spring boot的launcher,通过latcher来加载FatJar中的资源,最终会调用到Start-Class中定义的程序启动入口,从这个入口开始启动Spring。
SpringBoot整个启动流程分为两个步骤:初始化一个SpringApplication对象、执行该对象的run方法。
当我们运行SpringApplication的main方法时,调用静态方法run()首先是实例化,SpringApplication初始化的时候主要做主要做三件事:
初始化流程中最重要的就是通过SpringFactoriesLoader找到spring.factories文件中配置的ApplicationContextInitializer和ApplicationListener两个接口的实现类名称,以便后期构造相应的实例。
ApplicationContextInitializer的主要目的是在ConfigurableApplicationContext做refresh之前,对ConfigurableApplicationContext实例做进一步的设置或处理。
ConfigurableApplicationContext继承自ApplicationContext,其主要提供了对ApplicationContext进行设置的能力。
实现一个ApplicationContextInitializer非常简单,因为它只有一个方法,但大多数情况下我们没有必要自定义一个ApplicationContextInitializer,即便是Spring Boot框架,它默认也只是注册了两个实现,毕竟Spring的容器已经非常成熟和稳定,你没有必要来改变它。
而ApplicationListener的目的就没什么好说的了,它是Spring框架对Java事件监听机制的一种框架实现,具体内容在前文Spring事件监听机制这个小节有详细讲解。这里主要说说,如果你想为Spring Boot应用添加监听器,该如何实现?
Spring Boot提供两种方式来添加自定义监听器:
通过SpringApplication.addListeners(ApplicationListener... listeners)或者SpringApplication.setListeners(Collection> listeners)两个方法来添加一个或者多个自定义监听器
既然SpringApplication的初始化流程中已经从spring.factories中获取到ApplicationListener的实现类,那么我们直接在自己的jar包的META-INF/spring.factories文件中新增配置即可:
org.springframework.context.ApplicationListener=\ cn.moondev.listeners.xxxxListener
关于SpringApplication的初始化,我们就说这么多。
run方法定义了启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块
// SpringApplication中的run方法:
public ConfigurableApplicationContext run(String... args) {
<!--1、这个是一个计时器,没什么好说的-->
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
<!--2、这个也不是重点,就是设置了一些环境变量-->
configureHeadlessProperty();
<!--3、获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法-->
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
<!--4、把参数args封装成DefaultApplicationArguments,这个了解一下就知道-->
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
<!--5、这个很重要准备环境了,并且把环境跟spring上下文绑定好,并且执行environmentPrepared()方法-->
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
<!--6、判断一些环境的值,并设置一些环境的值-->
configureIgnoreBeanInfo(environment);
<!--7、打印banner-->
Banner printedBanner = printBanner(environment);
<!--8、创建上下文,根据项目类型创建上下文-->
context = createApplicationContext();
<!--9、获取异常报告事件监听-->
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
<!--10、准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法-->
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
<!--11、这个是spring启动的代码了,这里就回去里面就回去扫描并且初始化单实列bean了-->
//这个refreshContext()加载了bean,还启动了内置web容器,需要细细的去看看
refreshContext(context);
<!--12、啥事情都没有做-->
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
<!--13、执行ApplicationRunListeners中的started()方法-->
listeners.started(context);
<!--执行Runner(ApplicationRunner和CommandLineRunner)-->
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
listeners.running(context);
return context;
}
利用SPI机制扫描 META-INF/spring.factories 这个文件,并且加载 ApplicationContextInitializer、ApplicationListener 接口实例。
1、ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
2、ApplicationListener 当springboot启动时事件change后都会触发
Spring容器最终会调用referesh方法,这个方法用于真正启动一个Spring容器,这是一个模板方法的设计模式,里面定义好了启动流程:
// Spring 启动启动容器的方法:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 1、容器启动前的准备工作,这里面有第一个容器扩展点initPropertySources,用户在自定义IOC容器时可以重写,完成一些环境变量属性的初始化工作。
prepareRefresh();
// 2、获取BeanFactory;默认实现是DefaultListableBeanFactory,在创建容器的时候创建的
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3、BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器,BeanPostProcessor和XXXAware自动装配等)
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// 4、BeanFactory准备工作完成后进行的后置处理工作
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
//5、执行BeanFactoryPostProcessor的方法
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// 6、注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// 7、初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
// Initialize message source for this context.
initMessageSource();
//8、初始化事件派发器
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// 9、子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
// Initialize other special beans in specific context subclasses.
onRefresh();
// 10、注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean,这些监听器是注册到ApplicationEventMulticaster中的
// Check for listener beans and register them.
registerListeners();
// 11、初始化所有剩下的非懒加载的单例bean
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// 12、完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
...
} finally {
...
}
}
}