当前位置:   article > 正文

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

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 <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
  2. //返回指定类型(ApplicationListener)的实例
  3. return getSpringFactoriesInstances(type, new Class<?>[] {});
  4. }
  5. //真正执行的方法
  6. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  7. //得到类加载器
  8. ClassLoader classLoader = getClassLoader();
  9. // 得到对应的bean的名字
  10. Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  11. //利用反射生成实例化对象
  12. List<T> 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.
  功能就是发布事件,也就是把某个事件告诉的所有与这个事件相关的监听
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/125581?site
推荐阅读
相关标签
  

闽ICP备14008679号