SpringBoot启动流程总结

一直很好奇SpringBoot这么一个大怪物,启动的时候做了哪些事情,然后看了很多老师讲的教学视频,然后自己想好好整理一下,做下学习笔记下次也方便自己阅读

1、首先从main方法开始看

public static void main(String[] args) {
    //代码很简单SpringApplication.run();
	SpringApplication.run(ConsumerApp.class, args);
}
public static ConfigurableApplicationContext run(Class<?> primarySource,
			String... args) {
        //这个里面调用了run() 方法,我们转到定义
		return run(new Class<?>[] { primarySource }, args);
	}



//这个run方法代码也很简单,就做了两件事情
//1、new了一个SpringApplication() 这么一个对象
//2、执行new出来的SpringApplication()对象的run()方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

 2、上面代码主要分以下两步

  • 第一步new了一个SpringApplication对象 
  • 第二部调用了run()方法

3、接下来我们来看看new SpringApplication()代码

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
        //1、先把主类保存起来
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

        //2、判断运行项目的类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();

        //3、扫描当前路径下META-INF/spring.factories文件的
             ApplicationContextInitializer并加载
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));

        //4、同样也是扫描当前路径下META-INF/spring.factories文件下的
             ApplicationListener并加载
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这里面还是要说一下ApplicationContextInitializerApplicationListener

  • ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
  • ApplicationListener 当springboot启动时事件change后都会触发

我们来看一个案例,就更好理解上面这两个类

/**
 * Context初始化后调用类
 * @author ShiMinChen
 *
 */
public class StarterApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		System.out.println("applicationContext 初始化完成 ... ");
	}

}
public class StarterApplicationListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println(event.toString());
        System.out.println("ApplicationListener .... " + System.currentTimeMillis());
    }

}

我们需要把这两个类集成到springboot里面去,其实操作也挺简单的

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

org.springframework.context.ApplicationContextInitializer=\
org.admin.starter.test.listener.StarterApplicationContextInitializer

org.springframework.context.ApplicationListener=\
  org.admin.starter.test.listener.StarterApplicationListener

4、我们代码DEBUG一下,在loadSpringFactories() 方法打一个断点

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null)
			return result;
		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			cache.put(classLoader, result);

            // 端点打在这里就行了
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

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

5、接下来看看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;
	}

  我们还是重点来看refreshContext(context) 这个方法,这个方法启动spring的代码加载了bean,还启动了内置web容器

private void refreshContext(ConfigurableApplicationContext context) {
        // 转到定义看看
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        //看看refresh()方法去
		((AbstractApplicationContext) applicationContext).refresh();
	}

 转到AbstractApplicationContext - >refresh()方法里面发现这是spring容器启动代码

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

 spring容器启动代码就不说了,这里主要看一下onRefresh() 这个方法,转到定义发现这个方法里面啥都没有,因为这个AbstractApplicationContext是一个抽象类,所以我们要找到继承AbstractApplicationContext的子类,去看子类里面的onRefresh()

protected void onRefresh() throws BeansException {
    //这是一个空方法,AbstractApplicationContext 这个类是一个抽象类,
    //所以我们要找到集成AbstractApplicationContext的子类,去看子类里面的onRefresh()
	// For subclasses: do nothing by default.
}

 我们这里是一个Web项目,所以我们就去看 ServletWebServerApplicationContext 这个类 ,我还是把类的关系图贴一下

我们就去看 ServletWebServerApplicationContext 这个类下面的 onRefresh() 方法

protected void onRefresh() {
		super.onRefresh();
		try {
            //看到内置容器的影子了,进去看看
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}
private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
            //1、这个获取webServerFactory还是要进去看看
			ServletWebServerFactory factory = getWebServerFactory();
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}

 我们继续看下getWebServletFactory() 这个方法,这个里面其实就是选择出哪种类型的web容器了

protected ServletWebServerFactory getWebServerFactory() {
		// Use bean names so that we don't consider the hierarchy
		String[] beanNames = getBeanFactory()
				.getBeanNamesForType(ServletWebServerFactory.class);
		if (beanNames.length == 0) {
			throw new ApplicationContextException(
					"Unable to start ServletWebServerApplicationContext due to missing "
							+ "ServletWebServerFactory bean.");
		}
		if (beanNames.length > 1) {
			throw new ApplicationContextException(
					"Unable to start ServletWebServerApplicationContext due to multiple "
							+ "ServletWebServerFactory beans : "
							+ StringUtils.arrayToCommaDelimitedString(beanNames));
		}
		return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
	}

 

我们再回头去看factory.getWebServer(getSelfInitializer()) ,转到定义就会看到很熟悉的名字tomcat

public WebServer getWebServer(ServletContextInitializer... initializers) {
        //tomcat这位大哥出现了
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null ? this.baseDirectory
				: createTempDir("tomcat"));
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

Tomcat 就在这里启动的

总结:1、run() 方法主要调用了spring容器启动方法扫描配置,加载bean到spring容器中

           2、启动的内置Web容器

本人也是一个刚入到的小白,也是看了很多大神写的这类文章,按照自己思路整理一下,就是为了加深下印象,萤火之光也不敢与日月争辉,但也希望对大家有帮助

发布了196 篇原创文章 · 获赞 212 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/kangbin825/article/details/105014001