springboot启动过程原理分析

news/2024/11/30 1:27:44/

前言

现在绝大多数java项目都上了Springboot框架, 因此深入理解Springboot框架的运行原理,能帮助我们更好的在Springboot框架下进行业务开发,同时能学习框架中优秀的设计思想, 本文主要是通过对Springboot源码的分析, 来理解整个springboot项目的启动流程. 因为Springboot不同版本的源码有差异, 因此特别声明, 本文是基于2.2.10.RELEASE版本进行的原理分析.

1. 导入依赖

 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.10.RELEASE</version></parent>

1.1 Springboot项目在spring官网已经作为顶级项目存在, 官网描述非常清楚,如果只是想搭建一个springboot项目, 只需要导入spring-boot-starter-parent, 如果是做web开发, 则还需导入spring-boot-starter-web, 本文只分析Springboot的启动流程,因此只需要导入spring-boot-starter-parent

2. 创建启动类

@SpringBootApplication
public class SpringbootApplication{public static void main(String[] args) {SpringApplication.run(SpringbootApplication.class, args);}
}

2.1 一个Springboot项目只需要在启动类上加@SpringBootApplicatio注解就可以, 可能很多人会有疑问,为什么加入了这个注解,就是一个springboot项目了,回答这个问题之前,我们首先要知道什么是一个Springboot项目, 接下来我会从源码角度为大家深入分析.

3. springboot启动流程图

在这里插入图片描述

3.1 以上是整个Springboot的run方法的执行流程, 这只是一个总体流程图, 接下里对每一步我将通过源码深入分析.

4. 实例化SpringAppliaciton

4.1 实例化SpringAppliaciton的核心是实例化了两种类型的class, 一个是ApplicationContextInitializer.class, 另一个是 ApplicationListener.class

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

4.2 getSpringFactoriesInstances该方法的核心是实例化所有依赖下META-INF/spring.factories文件里ApplicationContextInitializer和ApplicationListener类型, 并放入initializers和listeners属性中

//将META-INF/spring.factories目录下对应类型的全类名加入到set集合中
Set<String> names = new LinkedHashSet<>
(SpringFactoriesLoader.loadFactoryNames(type, classLoader));//通过反射实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

4.3 SpringFactoriesLoader.loadFactoryNames方法就是去加载META-INF/spring.factories, 请对这个方法有些印象, 因为这个方法在Springboot自动装配的时候也会调用.

 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {//对应类型的全类名,这里是org.springframework.context.ApplicationContextInitializer和org.springframework.context.ApplicationListenerString factoryTypeName = factoryType.getName(); //通过key筛选对应的value集合return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {//这个集合在web项目的源码中经常出现,它和普通的map区别在于一个key可以对应多个valueMultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);if (result != null) {return result;} else {try {//核心是加载所有类路径下META-INF/spring.factories文件Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");LinkedMultiValueMap result = new LinkedMultiValueMap();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[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryImplementationName = var9[var11];result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);}}
}

4.4 createSpringFactoriesInstances是通过反射实例化所有的对象

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {List<T> instances = new ArrayList<>(names.size());for (String name : names) {try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);T instance = (T) BeanUtils.instantiateClass(constructor, args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;
}

4.5 因为是加载所有类路径下的spring.factories文件, 因此会加载spring-boot-starter-parent依赖的所有模块的spring.factories文件, 默认是加载8个initializer对象和12个listener对象.

5. 配置headless模式

5.1 headless模式springboot是默认开启的,通过System.setProperty(“java.awt.headless”,”true”)设置, 因为对于服务端而言,可能缺少显示屏、键盘或者鼠标等设备.

private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

6. 获取所有的SpringApplicationRunListeners

6.1 同样是加载META-INF/spring.factories文件下所有的SpringApplicationRunListener类型的全类名,通过反射实例化.
因为我们只导入了spring-boot-starter-parent包,在子模块spring-boot下的META-INF/spring.factories文件中只有一个EventPublishingRunListener实现了SpringApplicationRunListener,因此只实例化一个SpringApplicationRunListener类型

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

7. starting(), 发布ApplicationStartingEvent事件

void starting() {for (SpringApplicationRunListener listener : this.listeners) {listener.starting();}
}

7.1 会遍历所有的SpringApplicationRunListener类型, 因为上面我们说只有一个EventPublishingRunListener实现了SpringApplicationRunListener,因此我们进入EventPublishingRunListener的starting()

@Override
public void starting() {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

7.2 这里的initialMulticaster是SimpleApplicationEventMulticaster类型, 读过Spring源码的都知道, spring的时间派发器就是SimpleApplicationEventMulticaster, 通过这个对象发布事件, 因为spring事件监听是通过观察者模式实现的, 我们只需要实现ApplicationListener接口监听对应的事件就行.这里是发布了一个ApplicationStartingEvent事件, 其实在整个Springboot启动过程会发布各个节点事件, 都是通过SimpleApplicationEventMulticaster派发器派发.

8. 准备环境prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {//检测对应的环境,因为我们是开发web,所以是web环境, 会示例化StandardServletEnvironment对象ConfigurableEnvironment environment = getOrCreateEnvironment();//将所有的环境参数加载到environment对象中,这样我们就可以用environment获取配置configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);listeners.environmentPrepared(environment);bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;
}

9. 配置是否忽略beaninfo

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());}
}

9.1 spring.beaninfo.ignore值默认是true, 表示跳过对BeanInfo类的搜索, 这些bean并不会被spring容器管理

10. 打印printBanner

private Banner printBanner(ConfigurableEnvironment environment) {if (this.bannerMode == Banner.Mode.OFF) {return null;}ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(getClassLoader());SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);if (this.bannerMode == Mode.LOG) {return bannerPrinter.print(environment, this.mainApplicationClass, logger);}return bannerPrinter.print(environment, this.mainApplicationClass, System.out);}

10.1 因为springboot的默认banner模式是CONSOLE, 因此会走bannerPrinter.print(environment, this.mainApplicationClass, System.out)

Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {//获取bannerBanner banner = getBanner(environment);//打印bannerbanner.printBanner(environment, sourceClass, out);return new PrintedBanner(banner, sourceClass);
}
private Banner getBanner(Environment environment) {Banners banners = new Banners();//如果项目配置了spring.banner.image.location路径下的图片文件会被加载banners.addIfNotNull(getImageBanner(environment));//如果项目配置了spring.banner.location路径下的文件会被加载, 或则在类路径下添加了banner.txt文件也会被加载banners.addIfNotNull(getTextBanner(environment));if (banners.hasAtLeastOneBanner()) {return banners;}if (this.fallbackBanner != null) {return this.fallbackBanner;}return DEFAULT_BANNER;}

10.2 如果图片banner和文本banner都没有配置,那么就使用默认的DEFAULT_BANNER 也就是 SpringBootBanner

@Overridepublic void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {for (String line : BANNER) {printStream.println(line);}String version = SpringBootVersion.getVersion();version = (version != null) ? " (v" + version + ")" : "";StringBuilder padding = new StringBuilder();while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {padding.append(" ");}printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),AnsiStyle.FAINT, version));printStream.println();}}

10.3 这里的BANNER就是我们常见的springboot标志的log

 .   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/

11. 创建Spring容器,createApplicationContext


http://www.ppmy.cn/news/168502.html

相关文章

富士通scan按钮自动扫描设置

1、设置中&#xff0c;去设备中找到扫描仪&#xff0c;属性&#xff0c;设置scan事件为启动scandall pro 程序。 2、scandall pro程序中&#xff0c;扫描&#xff0c;成批扫描设置&#xff0c;详细设置扫描参数。 完毕。

富士通Fujitsu LPK-888T 打印机驱动

富士通Fujitsu LPK-888T 打印机驱动是官方提供的一款打印机驱动&#xff0c;本站收集提供高速下载&#xff0c;用于解决打印机与电脑连接不了&#xff0c;无法正常使用的问题&#xff0c;本动适用于&#xff1a;Windows XP / Windows 7 / Windows 8 / Windows 10 32/64位操作系…

富士通Fujitsu DPK9500GA Pro 打印机驱动

富士通Fujitsu DPK9500GA Pro 打印机驱动是官方提供的一款打印机驱动&#xff0c;本站收集提供高速下载&#xff0c;用于解决打印机与电脑连接不了&#xff0c;无法正常使用的问题&#xff0c;本动适用于&#xff1a;Windows XP / Windows 7 / Windows 8 / Windows 10 32/64位操…

富士施乐Fuji Xerox DocuPrint M255 z 驱动

富士施乐Fuji Xerox DocuPrint M255 z 驱动是官方提供的一款一体机&#xff08;打印/扫描&#xff09;驱动&#xff0c;本站收集提供高速下载&#xff0c;用于解决一体机与电脑连接不了&#xff0c;无法正常使用的问题&#xff0c;本动适用于&#xff1a;Windows XP / Windows …

富士通ScanSnap的S1500文档扫描仪 - 最佳的方式去无纸化

富士通ScanSnap的S1500文档扫描仪 - 最佳的方式去无纸化 富士通ScanSnap的S1500文档扫描仪是清理所有这些文件是很重要的一个好办法&#xff0c;但都塞满了你的家或办公室。这种扫描仪可以让你保持这些重要文件&#xff0c;而在同一时间让你的工作区域干净整洁。 该扫描仪的功能…

m115b linux 驱动下载,富士施乐m115b驱动

富士施乐m115b驱动是一款docuprin系列非常受用喜爱的打印机驱动&#xff0c;用户下载此驱动能解决打印时出现的问题&#xff0c;帮助用户有效的工作&#xff0c;此产品有着低成本高效打印&#xff0c;打印输出迅速&#xff0c;图片清晰度高等&#xff0c;有感兴趣的用户快来巴士…

富士施乐p355d_富士施乐p355d驱动下载

富士施乐p355d打印机参数 基本参数 黑白打印速度&#xff1a;35ppm 其它打印速度&#xff1a;双面&#xff1a;21ppm 处理器&#xff1a;600MHz 内存&#xff1a;标配&#xff1a;256MB&#xff0c;最大&#xff1a;768MB 网络打印&#xff1a;支持有线网络打印 双面打印&#…

扫描智能捕获 富士通ScanSnap iX1500新品试用

【新品试用】 谈到企业级领域&#xff0c;大家讨论最多的就是如何实现数字化转型。所以我们可以看到不同的厂商带来了丰富的解决方案&#xff0c;而最后其实大部分都变成了简单的IT系统升级&#xff0c;并没有解决企业的实际问题&#xff0c;反而将简单的事情变得复杂化。而在办…