• (面试)SpringBoot启动原理-源码(深入)


    目录

    SpringBoot启动过程

    运行run()方法


     

    最近再疯狂复习刷八股文,今天总结一下SpringBoot

    SpringBoot总的来说(个人认为),大概分为5个模块

    1.Spring原理(注解)2,SpringMVC原理 3,自动配置原理 4,SpringBoot启动原理

    5,第三方配置的框架

    SpringBoot启动过程

    首先我们SpringBoot启动主要分为两步骤:1.创建SpringApplication 2.运行SpringApplication

     看看SpringApplication里面的结构:

     解释:1.里面保存了很多信息——>比如:自定义的环境,handless(缺少键盘等外部情况),懒加载,加载器等等

    2.通过断言机制Assert判断当前类是否为空

    注意会将我们启动的主类保存再LinkedHashSet中,然后运行通过deduceFromClassPath()方法判断运行项目的类型

    1. public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    2. this.resourceLoader = resourceLoader;
    3. Assert.notNull(primarySources, "PrimarySources must not be null");
    4. //1、先把主类保存起来
    5. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    6. //2、判断运行项目的类型
    7. this.webApplicationType = WebApplicationType.deduceFromClasspath();
    8. //3、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationContextInitializer接口实例
    9. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    10. //4、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationListener接口实例
    11. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    12. this.mainApplicationClass = deduceMainApplicationClass();
    13. }

    2.deduceFromClassPath()方法->判断运行项目的类型

     3.然后就是getSpringFactoriesInstances(ApplicationContextInitializer.class)) getSpringFactoriesInstances(ApplicationListener.class))方法

    目的时去Spring.factories中寻找ApplicationContextInitializer初始化器和ApplicationListener监听器

    1、ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用

    2、ApplicationListener 当springboot启动时事件change后都会触发

    我们来说说ApplicationContextInitializer的概念:

    ApplicationContextInitializer也是Spring框架原有的概念,这个类的主要目的就是 在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理。(剪短说就是在容器刷新之前调用该类的 initialize 方法。并将 ConfigurableApplicationContext 类的实例传递给该方法)

    场景:

    • 通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
    1. public class DemoApplicationContextInitializer implements ApplicationContextInitializer {
    2. @Override
    3. public void initialize(ConfigurableApplicationContext applicationContext) {
    4. // do whatever you want with applicationContext,
    5. // e.g. applicationContext.registerShutdownHook();
    6. }
    7. }

    如果我们真的需要自定义一个ApplicationContextInitializer,那么只要像上面这样,通过SpringFactoriesLoader机制进行配置,或者通过SpringApplication.addInitializers(..)设置即可

    下面我们来自定义ApplicationListener

    1. public class StarterApplicationListener implements ApplicationListener {
    2. @Override
    3. public void onApplicationEvent(ApplicationEvent event) {
    4. System.out.println(event.toString());
    5. System.out.println("ApplicationListener .... " + System.currentTimeMillis());
    6. }
    7. }

     然后在META-INF/spring.factories 文件配置那两个类

    1. org.springframework.context.ApplicationContextInitializer=\
    2. org.admin.starter.test.listener.StarterApplicationContextInitializer
    3. org.springframework.context.ApplicationListener=\
    4. org.admin.starter.test.listener.StarterApplicationListener

    然后我们继续debug,因为我们之前时到getSpringFactoriesInstances()方法加载ApplicationInitializer和ApplicationListener的实例(很明显通过类对象反射得到我们的类信息)

    这里再提一下反射真的重要,结合JVM来思考,简而言之这里就是通过类加载器加载字节码文件产生类对象,我们类对象中含有instanceKlass的地址(Class对象也就是我们常说的mirror,暴露给我,的Java层,也就是开发者,可以通过反射得到),了解JVM就知道每一个Java类都会创建一个C++实例,也就是Klass实例(里面有instanceKlass专门描述Java的)->存储了Java类中描述的方法字段等等,我们的Class对象与方法区中的klass互指,所以可以得到方法区中的类信息

    然后之前人们所说的new一个对象得到信息,那是因为对象头里面含有klassword,这个指针指向了方法区的Klass从而得到类信息

    (30条消息) 【JVM】底层实现(一):浅谈 OOP-Klass 对象模型_A minor的博客-CSDN博客

    再回到我们的getSpringFactoriesInstances()方法

    1. private Collection getSpringFactoriesInstances(Class type) {
    2. //返回指定类型(ApplicationListener)的实例
    3. return getSpringFactoriesInstances(type, new Class[] {});
    4. }
    5. //真正执行的方法
    6. private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
    7. //得到类加载器
    8. ClassLoader classLoader = getClassLoader();
    9. // 得到对应的bean的名字
    10. Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    11. //利用反射生成实例化对象
    12. List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    13. AnnotationAwareOrderComparator.sort(instances);
    14. //添加到启动的listeners中
    15. return instances;
    16. }

     上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,利用SPI机制主要加载了META-INF/spring.factories 下面定义的事件监听器接口实现类

    运行run()方法

     1.StopWatch:创建一个Stopwatch实例,方便记录时间以及任务名字

     2.然后start()记录启动时间

     3.让应用进入headless模式:缺少显示设备的情况

     4.获取所有 RunListener(运行监听器【为了方便所有Listener进行事件感知】,然后遍历它们执行 starting() 方法

    然后我们进入getRunListener()方法看看

    发现是不是调用getSpringFactoriesInstances()方法在Spring.factories寻找事件监听器

    1. private SpringApplicationRunListeners getRunListeners(String[] args) {
    2. Class[] types = new Class[]{SpringApplication.class, String[].class};
    3. return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    4. }

    5.然后就是准备环境prepareEnvironment()

    6.然后就是printBanner()打印我们的一个图标

    7.this.createApplicationContext()根据项目类型创建我们的IOC容器,里面会注入几个核心组件类

     8.然后就是refreshContext()刷新我们的容器

    1. private void refreshContext(ConfigurableApplicationContext context) {
    2. // 转到定义看看
    3. refresh(context);
    4. if (this.registerShutdownHook) {
    5. try {
    6. context.registerShutdownHook();
    7. }
    8. catch (AccessControlException ex) {
    9. // Not allowed in some environments.
    10. }
    11. }
    12. }
    1. protected void refresh(ApplicationContext applicationContext) {
    2. Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    3. //看看refresh()方法去
    4. ((AbstractApplicationContext) applicationContext).refresh();
    5. }

    其实也就是Spring容器的启动代码

    1. @Override
    2. public void refresh() throws BeansException, IllegalStateException {
    3. synchronized (this.startupShutdownMonitor) {
    4. // Prepare this context for refreshing.
    5. prepareRefresh();
    6. // Tell the subclass to refresh the internal bean factory.
    7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    8. // Prepare the bean factory for use in this context.
    9. prepareBeanFactory(beanFactory);
    10. try {
    11. // Allows post-processing of the bean factory in context subclasses.
    12. postProcessBeanFactory(beanFactory);
    13. // Invoke factory processors registered as beans in the context.
    14. invokeBeanFactoryPostProcessors(beanFactory);
    15. // Register bean processors that intercept bean creation.
    16. registerBeanPostProcessors(beanFactory);
    17. // Initialize message source for this context.
    18. initMessageSource();
    19. // Initialize event multicaster for this context.
    20. initApplicationEventMulticaster();
    21. // Initialize other special beans in specific context subclasses.
    22. onRefresh();
    23. // Check for listener beans and register them.
    24. registerListeners();
    25. // Instantiate all remaining (non-lazy-init) singletons.
    26. finishBeanFactoryInitialization(beanFactory);
    27. // Last step: publish corresponding event.
    28. finishRefresh();
    29. }
    30. catch (BeansException ex) {
    31. if (logger.isWarnEnabled()) {
    32. logger.warn("Exception encountered during context initialization - " +
    33. "cancelling refresh attempt: " + ex);
    34. }
    35. // Destroy already created singletons to avoid dangling resources.
    36. destroyBeans();
    37. // Reset 'active' flag.
    38. cancelRefresh(ex);
    39. // Propagate exception to caller.
    40. throw ex;
    41. }
    42. finally {
    43. // Reset common introspection caches in Spring's core, since we
    44. // might not ever need metadata for singleton beans anymore...
    45. resetCommonCaches();
    46. }
    47. }
    48. }

    refresh()这里面调用了onRefresh()方法,进入一看发现调用了createWebServer()方法

    1. protected void onRefresh() {
    2. super.onRefresh();
    3. try {
    4. //看到内置容器的影子了,进去看看
    5. createWebServer();
    6. }
    7. catch (Throwable ex) {
    8. throw new ApplicationContextException("Unable to start web server", ex);
    9. }
    10. }

     createWebServer()中的getWebServerFactory()方法选择出了哪种web容器也就是tomcat

    1. private void createWebServer() {
    2. WebServer webServer = this.webServer;
    3. ServletContext servletContext = getServletContext();
    4. if (webServer == null && servletContext == null) {
    5. //1、这个获取webServerFactory还是要进去看看
    6. ServletWebServerFactory factory = getWebServerFactory();
    7. this.webServer = factory.getWebServer(getSelfInitializer());
    8. }
    9. else if (servletContext != null) {
    10. try {
    11. getSelfInitializer().onStartup(servletContext);
    12. }
    13. catch (ServletException ex) {
    14. throw new ApplicationContextException("Cannot initialize servlet context",
    15. ex);
    16. }
    17. }
    18. initPropertySources();
    19. }
    1. protected ServletWebServerFactory getWebServerFactory() {
    2. // Use bean names so that we don't consider the hierarchy
    3. String[] beanNames = getBeanFactory()
    4. .getBeanNamesForType(ServletWebServerFactory.class);
    5. if (beanNames.length == 0) {
    6. throw new ApplicationContextException(
    7. "Unable to start ServletWebServerApplicationContext due to missing "
    8. + "ServletWebServerFactory bean.");
    9. }
    10. if (beanNames.length > 1) {
    11. throw new ApplicationContextException(
    12. "Unable to start ServletWebServerApplicationContext due to multiple "
    13. + "ServletWebServerFactory beans : "
    14. + StringUtils.arrayToCommaDelimitedString(beanNames));
    15. }
    16. return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    17. }
    1. public WebServer getWebServer(ServletContextInitializer... initializers) {
    2. //tomcat这位大哥出现了
    3. Tomcat tomcat = new Tomcat();
    4. File baseDir = (this.baseDirectory != null ? this.baseDirectory
    5. : createTempDir("tomcat"));
    6. tomcat.setBaseDir(baseDir.getAbsolutePath());
    7. Connector connector = new Connector(this.protocol);
    8. tomcat.getService().addConnector(connector);
    9. customizeConnector(connector);
    10. tomcat.setConnector(connector);
    11. tomcat.getHost().setAutoDeploy(false);
    12. configureEngine(tomcat.getEngine());
    13. for (Connector additionalConnector : this.additionalTomcatConnectors) {
    14. tomcat.getService().addConnector(additionalConnector);
    15. }
    16. prepareContext(tomcat.getHost(), initializers);
    17. return getTomcatWebServer(tomcat);
    18. }

     

    所以说内置的Servlet容器就是在onRefresh()方法中启动的,至此一个servlet容器就启动ok

     9.然后就是stopWatch.stop()获取运行的时间

    在这里插入图片描述

     10.listeners.started(context):监听器调用,监听容器

    1. void started(ConfigurableApplicationContext context) {
    2. Iterator var2 = this.listeners.iterator();
    3. while(var2.hasNext()) {
    4. SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
    5. listener.started(context);
    6. }
    7. }
    1. default void started(ConfigurableApplicationContext context) {
    2. }
    1. public void started(ConfigurableApplicationContext context) {
    2. context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
    3. }

    然后我们跟进一下publishEvent()方法

    1. public interface ApplicationEventPublisher {
    2. default void publishEvent(ApplicationEvent event) {
    3. this.publishEvent((Object)event);
    4. }
    5. void publishEvent(Object var1);
    6. }
    1. protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    2. Assert.notNull(event, "Event must not be null");
    3. Object applicationEvent;
    4. if (event instanceof ApplicationEvent) {
    5. applicationEvent = (ApplicationEvent)event;
    6. } else {
    7. applicationEvent = new PayloadApplicationEvent(this, event);
    8. if (eventType == null) {
    9. eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
    10. }
    11. }

    这里介绍一下ApplicationEventPublisher

    (31条消息) ApplicationEventPublisher的使用学习_谦虚使人发胖的博客-CSDN博客_applicationeventpublisher

    1.ApplicationEventPublisherAware
      ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。
      用户注册后,不再是显示地调用其他的业务 Service,而是发布一个用户注册事件。
    2.ApplicationListener
      ApplicationListener接口是由 Spring 提供的事件订阅者必须实现的接口,我们一般把该 Service 关心的事件类型作为泛型传入。处理事件,通过 event.getSource() 即可拿到事件的具体内容

    3.ApplicationEventPublisher

    (31条消息) 观察者模式Spring之publishEvent事件处理_懒虫虫~的博客-CSDN博客_publishevent
      ApplicationEventPublisher是ApplicationContext的父接口之一。这接口的作用是:Interface that encapsulates event publication functionality.
      功能就是发布事件,也就是把某个事件告诉的所有与这个事件相关的监听
    在这里插入图片描述

  • 相关阅读:
    聊聊秒杀系统的设计(一)
    keepalived配置高可用web服务器
    嵌入式学习笔记(57)LCD如何显示图像
    FreeRTOS自我救赎1之基本知识
    基于编码策略的电网假数据注入攻击检测
    vue3+vite在线预览pdf
    JavaScript模块化:提高代码可维护性和可重用性的利器
    局部特征匹配(LoFTR) & 基于全局匹配的光流学习(GMFlow)
    会话技术之Coookie && Session详解
    Sentinel 部署 配置
  • 原文地址:https://blog.csdn.net/weixin_57128596/article/details/125880985