深入剖析Springboot启动原理的底层源码,再也不怕面试官问了!

  • As of Spring 3.0, an {@code ApplicationListener} can generically declare

  • the event type that it is interested in. When registered with a Spring

  • {@code ApplicationContext}, events will be filtered accordingly, with the

  • listener getting invoked for matching event objects only.

  • @author Rod Johnson

  • @author Juergen Hoeller

  • @param the specific {@code ApplicationEvent} subclass to listen to

  • @see org.springframework.context.ApplicationEvent

  • @see org.springframework.context.event.ApplicationEventMulticaster

  • @see org.springframework.context.event.EventListener

*/

@FunctionalInterface

public interface ApplicationListener extends EventListener {

/**

  • Handle an application event.

  • @param event the event to respond to

*/

void onApplicationEvent(E event);

}

总结

在这里使用到了观察者模式,有一个被观察者和许多观察者,当被观察者的状态发生改变时,要通知所有的观察者做一些操作。

3. 推断主应用入口类

源码剖析

private Class<?> deduceMainApplicationClass() {

try {

// 构造一个异常类

StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();

for (StackTraceElement stackTraceElement : stackTrace) {

// 通过main的栈帧推断出入口类的名字

if (“main”.equals(stackTraceElement.getMethodName())) {

return Class.forName(stackTraceElement.getClassName());

}

}

}

catch (ClassNotFoundException ex) {

// Swallow and continue

}

return null;

}

三、run() 方法源码剖析

源码剖析

/**

  • 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) {

// 记时器,统计应用启动的时间

StopWatch stopWatch = new StopWatch();

stopWatch.start();

// 初始化应用上下文和异常报告集合

ConfigurableApplicationContext context = null;

// SpringBootExceptionReporter 是异常处理器,启动的时候通过它把异常信息展示出来

Collection exceptionReporters = new ArrayList<>();

// 设置系统属性java.awt.headless的值,默认为true

configureHeadlessProperty();

// 监听器,SpringApplicationRunListeners实际上是一个集合

SpringApplicationRunListeners listeners = getRunListeners(args);

// 回调所有的获取SpringApplicationRunListener.starting()方法

listeners.starting();

try {

// 初始化默认参数

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

// 准备 Spring 环境

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

// 创建环境完成后回调,配置bean

configureIgnoreBeanInfo(environment);

// 打印器,springboot启动的时候会打印springboot的标志以及对应的版本

Banner printedBanner = printBanner(environment);

// 创建Spring应用上下文,来决定创建web的ioc还是普通的ioc

context = createApplicationContext();

// 实例化异常报告器

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,

new Class[] { ConfigurableApplicationContext.class }, context);

//准备上下文环境

// Spring上下文前置处理

prepareContext(context, environment, listeners, applicationArguments, printedBanner);

// prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();

// Spring上下文刷新,表示刷新完成,进行后续的一些操作

refreshContext(context);

// Spring上下文后置处理

afterRefresh(context, applicationArguments);

// 停止计时器

stopWatch.stop();

// 输出日志记录的类名、时间信息

if (this.logStartupInfo) {

new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);

}

// 发布应用上下文启动完成事件

listeners.started(context);

// 执行所有 Runner 运行器

callRunners(context, applicationArguments);

}

catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, listeners);

throw new IllegalStateException(ex);

}

try {

// 发布应用上下文就绪事件

listeners.running(context);

}

catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, null);

throw new IllegalStateException(ex);

}

// 返回应用上下文

return context;

}

1. 开启计时器

开启计时器,用来统计应用启动的时间

public void start() throws IllegalStateException {

// 传入一个空字符串作为当前任务的名称

this.start(“”);

}

public void start(String taskName) throws IllegalStateException {

if (this.currentTaskName != null) {

// 如果当前任务名字不为空,抛出异常

throw new IllegalStateException(“Can’t start StopWatch: it’s already running”);

} else {

// 否则,记录当前任务的开始时间

this.currentTaskName = taskName;

this.startTimeNanos = System.nanoTime();

}

}

  • 首先,传入一个空字符串作为当前任务的名称
  • 其次,判断当前任务名是否空,如果为空,则记录当前应用启动的开始时间
2. 设置系统属性的值

系统属性的值默认是true,系统属性的值来源于System.getProperty()。

private void configureHeadlessProperty() {

System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,

System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));

}

3. 监听器

private SpringApplicationRunListeners getRunListeners(String[] args) {

// 类加载对应的监听器

Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };

// 创建SpringApplicationRunListener实例

return new SpringApplicationRunListeners(logger,

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));

}

4. 初始化默认参数

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

5.创建 Spring 环境

private ConfigurableEnvironment prepareEnvironment(

SpringApplicationRunListeners listeners,

ApplicationArguments applicationArguments) {

// 获取环境。如果存在就直接返回,否则先创建一个再返回

ConfigurableEnvironment environment = getOrCreateEnvironment();

// 配置环境

configureEnvironment(environment, applicationArguments.getSourceArgs());

// 准备监听器环境

listeners.environmentPrepared(environment);

// 将环境绑定到SpringApplication上面

bindToSpringApplication(environment);

// 如果不是web应用环境,将环境转换成StandardEnvironment

if (this.webApplicationType == WebApplicationType.NONE) {

environment = new EnvironmentConverter(getClassLoader())

.convertToStandardEnvironmentIfNecessary(environment);

}

ConfigurationPropertySources.attach(environment);

// 返回环境

return environment;

}

总结

  • 获取环境。如果存在就直接返回,否则先创建一个再返回
  • 配置环境
  • 准备监听器环境
  • 将环境绑定到SpringApplication上面
  • 如果不是web应用环境,将环境转换成StandardEnvironment
  • 最后返回环境
6. 打印器

springboot启动的时候会打印springboot的标志以及对应的版本

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);

}

7. 创建Spring应用上下文

protected ConfigurableApplicationContext createApplicationContext() {

// 首先进行判断有没有指定的实现类

Class<?> contextClass = this.applicationContextClass;

// 如果没有,则根据应用类型选择

if (contextClass == null) {

try {

// 根据webApplicationType的类型去反射创建ConfigurableApplicationContext的具体实例

switch (this.webApplicationType) {

case SERVLET:

contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);

break;

case REACTIVE:

contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);

break;

default:

contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);

}

}

catch (ClassNotFoundException ex) {

throw new IllegalStateException(

“Unable create a default ApplicationContext, please specify an ApplicationContextClass”, ex);

}

}

// 通过反射,得到创建的对象

return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);

}

总结

  • 首先进行判断有没有指定的实现类; 如果没有,则根据应用类型选择;
  • 根据webApplicationType的类型去反射创建ConfigurableApplicationContext的具体实例;
  • 最后通过反射,得到创建的对象

对于Web应用,上下文类型是DEFAULT_WEB_CONTEXT_CLASS。

8. 实例化异常报告器

用 getSpringFactoriesInstances() 方法,获取配置的异常类名称,并实例化所有的异常类。

源码剖析

private Collection getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object… args) {

ClassLoader classLoader = getClassLoader();

// Use names and ensure unique to protect against duplicates

// 使用名称并确保唯一,以防止重复

Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

AnnotationAwareOrderComparator.sort(instances);

return instances;

}

9. Spring上下文前置处理

源码剖析

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,

SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {

// 给IOC容器设置一些环境属性

context.setEnvironment(environment);

// 给IOC容器注册一些组件

postProcessApplicationContext(context);

// 调用初始化方法

applyInitializers(context);

// 监听器,触发contextPrepared 事件

listeners.contextPrepared(context);

// 记录启动过程中的日志

if (this.logStartupInfo) {

logStartupInfo(context.getParent() == null);

logStartupProfileInfo(context);

}

// Add boot specific singleton beans

// 添加特定的单例beans

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

beanFactory.registerSingleton(“springApplicationArguments”, applicationArguments);

if (printedBanner != null) {

beanFactory.registerSingleton(“springBootBanner”, printedBanner);

}

if (beanFactory instanceof DefaultListableBeanFactory) {

((DefaultListableBeanFactory) beanFactory)

.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);

}

if (this.lazyInitialization) {

context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());

}

// Load the sources

// 加载所有资源

Set sources = getAllSources();

Assert.notEmpty(sources, “Sources must not be empty”);

// 加载启动类,将启动类注入到容器中去

load(context, sources.toArray(new Object[0]));

// 触发contextLoaded 事件

listeners.contextLoaded(context);

}

10. Spring上下文刷新

刷新完成以后,会进行后续的一些操作

源码剖析

private void refreshContext(ConfigurableApplicationContext context) {

// 调用父类的refresh操作

refresh(context);

if (this.registerShutdownHook) {

try {

// 注册一个关闭容器时的钩子函数,在JVM关机的时候关闭这个上下文。

context.registerShutdownHook();

}

catch (AccessControlException ex) {

// Not allowed in some environments.

}

}

}

调用了registerShutdownHook()方法

/**

  • Register a shutdown hook {@linkplain Thread#getName() named}

  • {@code SpringContextShutdownHook} with the JVM runtime, closing this

  • context on JVM shutdown unless it has already been closed at that time.

  • Delegates to {@code doClose()} for the actual closing procedure.

  • @see Runtime#addShutdownHook

  • @see ConfigurableApplicationContext#SHUTDOWN_HOOK_THREAD_NAME

  • @see #close()

  • @see #doClose()

*/

@Override

public void registerShutdownHook() {

if (this.shutdownHook == null) {

// No shutdown hook registered yet.

this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {

@Override

public void run() {

synchronized (startupShutdownMonitor) {

// 调用doClose方法,进行容器销毁时的清理工作

doClose();

}

}

};

Runtime.getRuntime().addShutdownHook(this.shutdownHook);

}

}

11. Spring上下文后置处理

在Spring容器刷新上下文后进行调用,依次调用注册的Runners。

/**

  • Called after the context has been refreshed.

  • @param context the application context

  • @param args the application arguments

*/

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

image.png

image.png

ontext the application context

  • @param args the application arguments

*/

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-tMspblxY-1711178170433)]
[外链图片转存中…(img-HFVpdrzp-1711178170434)]
[外链图片转存中…(img-7hMf27rK-1711178170434)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-YnOeTHP3-1711178170435)]

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

[外链图片转存中…(img-9Ncxmp0q-1711178170435)]

[外链图片转存中…(img-tevBAoFy-1711178170436)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值