深入分析 Spring Boot 的启动原理

 2022-08-11
原文作者:懒惰の天真热 原文地址:https://blog.csdn.net/weixin_40496191/article/details/109098491

一、 前言

我们启动一个springboot项目,最简单的就是配置一个springboot启动类,然后运行即可

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

通过上面的代码,我们可以看出springboot启动的关键主要有两个地方,第一个就是@SpringBootApplication注解,第二个就是 SpringApplication.run(SpringBoot.class, args);这个方法,那么他们内部究竟是如何运作的呢?

二、 @SpringBootApplication原理解析

1. @SpringBootApplication组合注解剖析

首先,我们直接追踪@SpringBootApplication的源码,可以看到其实@SpringBootApplication是一个组合注解,他分别是由底下这些注解组成。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

这些注解虽然看起来很多,但是除去元注解,真正起作用的注解只有以下三个注解:

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan

那这三个注解是有啥用?其实在Spring Boot 1.2版之前,或者我们初学者刚开始接触springboot时,都还没开始使用@SpringBootApplication这个注解,而是使用以上三个注解启动项目。如果有兴趣的,也可以手动敲敲代码,就会发现这样也可以正常启动项目!

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan
    public class SpringBoot {
    	public static void main(String[] args) {
    		SpringApplication.run(SpringBoot.class, args);
    	}
    }

所以说这三个注解才是背后的大佬,@SpringBootApplication只是个空壳子。接下来,我来说明下这三个注解各自的作用。

2. @SpringBootConfiguration

同样,我们跟踪下@SpringBootConfiguration的源代码,看下他由哪些注解组成

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration

可以看到,除去元注解,剩下的@Configuration注解我相信大家应该都很熟了!我们springboot为什么可以去除xml配置,靠的就是@Configuration这个注解。所以,它的作用就是将当前类申明为配置类,同时还可以使用@bean注解将类以方法的形式实例化到spring容器,而方法名就是实例名,看下代码你就懂了!

    @Configuration
    public class TokenAutoConfiguration {
    	@Bean
    	public TokenService tokenService() {
    		return new TokenService();
    	}
    }

作用等同于xml配置文件的

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:cache="http://www.springframework.org/schema/cache" xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
    	 					http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 
    	 					http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
    	 					http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
    	 					http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd ">
        <!--实例化bean-->
      <bean id="tokenService" class="TokenService"></bean>
    </beans>

3. @ComponentScan

我们先说下@ComponentScan作用。他的作用就是扫描当前包以及子包,将有@Component@Controller@Service@Repository等注解的类注册到容器中,以便调用。
注:大家第一眼见到@ComponentScan这个注解的时候是否有点眼熟?之前,一些传统框架用xml配置文件配置的时候,一般都会使用<context:component-scan>来扫描包。以下两中写法的效果是相同的`

    @Configuration
    @ComponentScan(basePackages="XXX")
    public class SpringBoot {
    
    }
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:cache="http://www.springframework.org/schema/cache" xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
    	 					http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 
    	 					http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
    	 					http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
    	 					http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd ">
    
    	<!-- 扫描需要被调用的注解文件包 -->
    	<context:component-scan base-package="XXX"></context:component-scan>
    </beans>

注:如果@ComponentScan不指定basePackages,那么默认扫描当前包以及其子包,而@SpringBootApplication里的@ComponentScan就是默认扫描,所以我们一般都是把springboot启动类放在最外层,以便扫描所有的类。

4. @EnableAutoConfiguration

这里先总结下@EnableAutoConfiguration的工作原理,大家后面看的应该会更清晰:
它主要就是通过内部的方法,扫描classpathMETA-INF/spring.factories配置文件(key-value),将其中的
org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项实例化并且注册到spring容器。

ok,我们同样打开@EnableAutoConfiguration源码,可以发现他是由以下几个注解组成的

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)

除去元注解,主要注解就是@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)
我们springboot项目为什么可以自动载入应用程序所需的bean?就是因为这个神奇的注解@Import。那么这个@Import怎么这么牛皮?没关系!我们一步一步的看下去!
首先我们先进入AutoConfigurationImportSelector类,可以看到他有一个方法selectImports()

202208112254323531.png
继续跟踪,进入getAutoConfigurationEntry()方法

    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

可以看到这里有个List集合,那这个List集合又是干嘛的?没事,我们继续跟踪getCandidateConfigurations()方法!

202208112254340062.png

可以看到这里有个方法

    SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());

这个方法的作用就是读取classpath下的META-INF/spring.factories文件的配置,将key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项读取出来,通过反射机制实例化为配置文件,然后注入spring容器。

注:假如你想要实例化一堆bean,可以通过配置文件先将这些bean实例化到容器,等其他项目调用时,在spring.factories中写入这个配置文件的路径即可!我前面的文章有这个例子https://blog.csdn.net/weixin_40496191/article/details/109065430
主要是实现自己创建的starter依赖包,然后由其他项目引入使用,这是我的spring.factories文件

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=zzk.config.TokenAutoConfiguration

然后直接在SpringFactoriesLoader.loadFactoryNames;这个方法后面打个断点,可以在返回的集合里找到我们自定义的配置文件路径!

202208112254353493.png

说明成功引入我们自定义的依赖包!

三、 SpringApplication.run()原理解析

SpringApplication.run()原理相对于前面注解的原理,会稍微麻烦点,为了方便我会适当贴出一些注解代码。
首先我们点击查看run方法的源码

202208112254365334.png

202208112254373485.png
可以看出,其实SpringApplication.run()包括两个部分,一部分就是创建SpringApplicaiton实例,另一部分就是调用run()方法,那他们又是怎么运行的?

1. 创建SpringApplicaiton

继续跟踪SpringApplication实例的源码

202208112254380956.png
继续跟踪进入,到如下这个方法中

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    	this.resourceLoader = resourceLoader;
    	Assert.notNull(primarySources, "PrimarySources must not be null");
    	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    	//获取应用类型
    	this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	//获取所有初始化器
    	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    	//获取所有监听器
    	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    	//定位main方法
    	this.mainApplicationClass = deduceMainApplicationClass();
    }

springboot在创建SpringApplicaiton实例的时候,主要是做了以上四个事情,ok,继续拆分一一讲解!

1.1 获取应用类型

跟踪deduceFromClasspath方法

202208112254389297.png
从返回结果我们可以看出应用类型一共有三种,分别是
NONE: 非web应用,即不会启动服务器
SERVLET: 基于servlet的web应用
REACTIVE: 响应式web应用(暂未接触过)

判断一共涉及四个常量:
WEBFLUX_INDICATOR_CLASS
WEBMVC_INDICATOR_CLASS
JERSEY_INDICATOR_CLASS
SERVLET_INDICATOR_CLASSES

springboot在初始化容器的时候,会对以上四个常量所对应的class进行判断,看看他们是否存在,从而返回应用类型!至于常量代表哪些class,大家可以自己跟踪看看,也在当前类中!

1.2 获取初始化器

跟踪进入getSpringFactoriesInstances方法

    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    		return getSpringFactoriesInstances(type, new Class<?>[] {});
    	}
    	
    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    		ClassLoader classLoader = getClassLoader();
    		// Use names and ensure unique to protect against duplicates
    		//获取所有初始化器的名称集合
    		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    		//根据名称集合实例化这些初始化器
    		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    		//排序		
    		AnnotationAwareOrderComparator.sort(instances);
    		return instances;
    	}

从代码可以看出是在META-INF/spring.factories配置文件里获取初始化器,然后实例化、排序后再设置到initializers属性中。

1.3 获取初监听器

同样跟踪源码,发现其实监听器和初始化的操作是基本一样的

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    		return getSpringFactoriesInstances(type, new Class<?>[] {});
    	}
    
    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    		ClassLoader classLoader = getClassLoader();
    		// Use names and ensure unique to protect against duplicates
    		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    		AnnotationAwareOrderComparator.sort(instances);
    		return instances;
    	}

这里就不多做解释了!

1.4 定位main方法

跟踪源码进入deduceMainApplicationClass方法

    private Class<?> deduceMainApplicationClass() {
    		try {
    		    //通过创建运行时异常的方式获取栈
    			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
    			//遍历获取main方法所在的类并且返回
    			for (StackTraceElement stackTraceElement : stackTrace) {
    				if ("main".equals(stackTraceElement.getMethodName())) {
    					return Class.forName(stackTraceElement.getClassName());
    				}
    			}
    		}
    		catch (ClassNotFoundException ex) {
    			// Swallow and continue
    		}
    		return null;
    	}

2. 调用run方法

2.1 run方法代码总览
    // 开启计时类进行计时
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		//声明应用上下文
    		ConfigurableApplicationContext context = null;
    		// 记录sprongboot启动异常日志
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		//设置系统java.awt.headless属性,默认为true(跟踪代码可以看到)
    		configureHeadlessProperty();
    		// 获取监听器,它的作用是为后期一些环境参数进行赋值,就是加载配置文件
    		// 获取到org.springframework.boot.context.event.EventPublishingRunListener
    		// implements SpringApplicationRunListener
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		//********* 遍历调用监听器,表示监听器已经开始初始化容器**********
    		listeners.starting();
    		try {
    			// 将args包装厂ApplicationArguments类
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    			// ********监听器开始对对环境参数进行赋值***********
    			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    			configureIgnoreBeanInfo(environment);
    			//打印banner图,就是我们springboot启动时,前面几行图形
    			Banner printedBanner = printBanner(environment);
    			// 初始化上下文对象AnnotationConfigServletWebServerApplicationContext
    			context = createApplicationContext();
    			// 异常采集
    			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    			// 部署上下文
    			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    			// springbootApplication生效
    			// 刷新上下文
    			refreshContext(context);
    			//刷新后的方法,空方法,给用户自定义重写
    			afterRefresh(context, applicationArguments);
    			//结束计时
    			stopWatch.stop();
    			//输出日志记录执行主类名、时间信息
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    			}
    			//********* 使用广播和回调机制告诉监听者springboot容器已经启动化成功**********
    			listeners.started(context);
    			//做一些调整顺序操作
    			callRunners(context, applicationArguments);
    		} catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
    
    		try {
    			//********* 使用广播和回调机制告诉已经可以运行springboot了**********
    			listeners.running(context);
    		} catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, null);
    			throw new IllegalStateException(ex);
    		}
    		//返回上下文
    		return context;
2.2 监听器

1.跟踪监听器==>EventPublishingRunListener
run方法代码总览在这里面,listeners出现了很多次,调用了startrunning等方法。这时候你可能会问,那他们又有什么区别呢?首先,我们先跟踪源码看看这个listeners到底是什么玩意儿…
我们进入getRunListeners方法,可以看到

    private SpringApplicationRunListeners getRunListeners(String[] args) {
    		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    		return new SpringApplicationRunListeners(logger,
    				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    	}

getSpringFactoriesInstances方法大家看了前面的现在应该知道了,这段代码的意思就是要去所有的META-INF文件下的spring.factorie寻找关于keySpringApplicationRunListenervalue配置,ok,那我们找找,可以发现在这里存在

202208112254399098.png
看样子这个方法最后返回的是org.springframework.boot.context.event.EventPublishingRunListener这个类,那我们就打开这个类看看是啥。

202208112254411809.png
这个方法它实现了SpringApplicationRunListener接口,那么,这个接口是干啥的呢?没错,他就是用来加载我们配置文件用的。接下来我弄个简单的例子,大家就知道怎么用了。

2.EventPublishingRunListener接口举例
主要实现自定义监听器并且读取我们配置文件内容,先献上我的文件结构

2022081122544209510.png

创建一个maven项目,pom配置只需要添加web依赖即可

    <parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.1.8.RELEASE</version>
      </parent>
      <dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    	</dependencies>
      <build>

resource自定义配置文件my.properties

    tzr.name=zzk

自定义监听器,这里主要是对startingenvironmentPreparedstartedrunning方法进行实现

    package zzk;
    
    import java.io.IOException;
    import java.util.Properties;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.SpringApplicationRunListener;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.core.Ordered;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.MutablePropertySources;
    import org.springframework.core.env.PropertiesPropertySource;
    import org.springframework.core.env.PropertySource;
    /**
     * 集成监听器加载我们的配置文件
     * @author zhanzhk
     *
     */
    public class MyListener implements SpringApplicationRunListener,Ordered {
    
    	private SpringApplication application;
    	private String[] args;
    	
    	
    	@Override
    	public void starting() {
    		System.out.println("表示准备开始使用监听器");
    
    	}
    
    	public MyListener(SpringApplication application, String[] args) {
    		this.application = application;
    		this.args = args;
    	}
    	
    	@Override
    	public void environmentPrepared(ConfigurableEnvironment environment) {
    		System.out.println("表示已经开始读取配置文件");
    		//配置文件到程序,再然后放入springboot容器
    		Properties properties=new Properties();
    		try {
    			//读取properties容器
    			properties.load(this.getClass().getClassLoader().getResourceAsStream("my.properties"));
    			//读取名字为my
    			PropertySource propertySource=new PropertiesPropertySource("my",properties) ;
    			//加载资源到springboot容器
    			MutablePropertySources propertySources=environment.getPropertySources();
    			propertySources.addLast(propertySource);
    			//换种思路,如果你配置文件是放在网络上,可以直接读取放入我们的项目中
    		
    		} catch (IOException e) {
    			System.out.println("出错");
    		}
    
    	}
    
    	@Override
    	public void contextPrepared(ConfigurableApplicationContext context) {
    		// TODO Auto-generated method stub
    
    	}
    
    	@Override
    	public void contextLoaded(ConfigurableApplicationContext context) {
    		// TODO Auto-generated method stub
    
    	}
    
    	@Override
    	public void started(ConfigurableApplicationContext context) {
    		System.out.println("表示初始化容器已经结束");
    
    	}
    
    	@Override
    	public void running(ConfigurableApplicationContext context) {
    		System.out.println("表示可以使用springboot了");
    	}
    
    	@Override
    	public void failed(ConfigurableApplicationContext context, Throwable exception) {
    		// TODO Auto-generated method stub
    
    	}
    
    	//读取优先级
    	@Override
    	public int getOrder() {
    		// TODO Auto-generated method stub
    		return -1;
    	}
    
    }

然后编写controller文件对我们的配置参数进行调用

    package zzk.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class Application {
    
    	@Value("${tzr.name}")
    	private String name;
    
    	@RequestMapping("test")
    	@ResponseBody
    	public String test() {
    		String x = name;
    		return x;
    	}
    }

ok,那我们自定义的监听器springboot程序又是如何获取的?前面我们代码里讲过了,它主要是读取META-INF底下的spring.factories文件,然后获取监听器,ok那就简单了,我们直接照着EventPublishingRunListener一样在resource增加METAA_INF/spring.factories文件

    org.springframework.boot.SpringApplicationRunListener=\
    zzk.MyListener

最后设置spring启动器

    package zzk;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Springboot {
    	public static void main(String[] args) {
    		SpringApplication.run(Springboot.class, args);
    	}
    }

启动!可以看到启动信息

2022081122544294611.png
然后我们再调用controller方法

2022081122544405912.png
成功读取我们自定义的配置文件,现在再回头看看run方法,是不是就清晰了!

2.3 引入注解

springboot的启动分为两部分,一部分是注解,一部分是SpringApplication.run(Springboot.class, args),那么我们的注解又是如何嵌入到程序中呢?靠的就是refreshContext方法,同理,我们跟踪源码进入refreshContext方法

2022081122544495213.png

2022081122544594114.png

    @Override
    	public void refresh() throws BeansException, IllegalStateException {
    		synchronized (this.startupShutdownMonitor) {
    			//  准备这个上下文来刷新。
    			prepareRefresh();
    
    			// 告诉子类刷新内部bean工厂。
    			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    			//  准备bean在此上下文中使用。
    			prepareBeanFactory(beanFactory);
    
    			try {
    				//  允许在上下文子类中对bean工厂进行后处理。
    				postProcessBeanFactory(beanFactory);
    
    				//  调用在上下文中注册为bean的工厂处理器。
    				invokeBeanFactoryPostProcessors(beanFactory);
    
    				//  注册拦截bean创建的bean处理器。
    				registerBeanPostProcessors(beanFactory);
    
    				//  初始化此上下文的消息源。
    				initMessageSource();
    
    				//  为此上下文初始化事件多播。
    				initApplicationEventMulticaster();
    
    				//  在特定的上下文子类中初始化其他特殊bean。
    				onRefresh();
    
    				//  检查侦听器bean并注册它们。
    				registerListeners();
    
    				//  实例化所有剩余的(非拉齐-init)单例。
    				finishBeanFactoryInitialization(beanFactory);
    
    				//  最后一步:发布相应事件.
    				finishRefresh();
    			}
    
    			catch (BeansException ex) {
    				if (logger.isWarnEnabled()) {
    					logger.warn("Exception encountered during context initialization - " +
    							"cancelling refresh attempt: " + ex);
    				}
    
    				//  销毁已经创建的单例以避免悬空资源。
    				destroyBeans();
    
    				//  重置“actiove”标志。
    				cancelRefresh(ex);
    
    				// 向调用者传播异常。
    				throw ex;
    			}
    
    			finally {
    				//  重置Spring核心中常见的内省缓存,因为我们可能不再需要单例bean的元数据了。。。
    				resetCommonCaches();
    			}
    		}
    	}

到这里,就可以看到一系列bean的操作,继续跟踪进入invokeBeanFactoryPostProcessors(调用在上下文中注册为bean的工厂处理器)方法

2022081122544679815.png

2022081122544781616.png

2022081122544906317.png
进入ConfigurationClassParser这个类后,方法调用也是挺绕的,这里就不深究了…进入这个类主要是想看下它的一些方法,因为对于springboot注解的引用就是在这个类进行的,比如doProcessConfigurationClass

        @Nullable
    	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    			throws IOException {
    
    		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
    			// Recursively process any member (nested) classes first
    			processMemberClasses(configClass, sourceClass);
    		}
    
    		//处理 @PropertySource 注解
    		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    				sourceClass.getMetadata(), PropertySources.class,
    				org.springframework.context.annotation.PropertySource.class)) {
    			if (this.environment instanceof ConfigurableEnvironment) {
    				processPropertySource(propertySource);
    			}
    			else {
    				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
    						"]. Reason: Environment must implement ConfigurableEnvironment");
    			}
    		}
    
    		// 处理 @ComponentScan 注解
    		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    		if (!componentScans.isEmpty() &&
    				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    			for (AnnotationAttributes componentScan : componentScans) {
    				// The config class is annotated with @ComponentScan -> perform the scan immediately
    				Set<BeanDefinitionHolder> scannedBeanDefinitions =
    						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    				// Check the set of scanned definitions for any further config classes and parse recursively if needed
    				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    					if (bdCand == null) {
    						bdCand = holder.getBeanDefinition();
    					}
    					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    						parse(bdCand.getBeanClassName(), holder.getBeanName());
    					}
    				}
    			}
    		}
    
    		 
    		 //处理 @Import 注解
    		processImports(configClass, sourceClass, getImports(sourceClass), true);
    
    		
    		//处理 @ImportResource 注解
    		AnnotationAttributes importResource =
    				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    		if (importResource != null) {
    			String[] resources = importResource.getStringArray("locations");
    			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    			for (String resource : resources) {
    				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    				configClass.addImportedResource(resolvedResource, readerClass);
    			}
    		}
    
    		//处理 @Bean 注解
    		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    		for (MethodMetadata methodMetadata : beanMethods) {
    			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    		}
    
    		// Process default methods on interfaces
    		processInterfaces(configClass, sourceClass);
    
    		// Process superclass, if any
    		if (sourceClass.getMetadata().hasSuperClass()) {
    			String superclass = sourceClass.getMetadata().getSuperClassName();
    			if (superclass != null && !superclass.startsWith("java") &&
    					!this.knownSuperclasses.containsKey(superclass)) {
    				this.knownSuperclasses.put(superclass, configClass);
    				// Superclass found, return its annotation metadata and recurse
    				return sourceClass.getSuperClass();
    			}
    		}
    
    		// No superclass -> processing is complete
    		return null;
    	}

ok,过…下一个

2.4内置tomcat

其实,内置tomcat应该要归在refreshContext讲的,因为tomcat就是在注解引入的类中生成的,而refreshContext可以引入注解。不过为了更清楚,这里分开吧。前面说了,我们refreshContext是刷新上下文,那如果想要知道上下文中是否存在生成tomcat的类,我们直接去最后返回的上下文中找对应的类即可!
ok,我们在main方法写获取上下文的代码,并且打印出对应的name

    package zzk;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    @SpringBootApplication
    public class Springboot {
    	public static void main(String[] args) {
    		ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(Springboot.class, args);
    		String[] xs = configurableApplicationContext.getBeanDefinitionNames();
    		for (String x : xs) {
    			System.out.println(x);
    		}
    
    	}
    }

直接启动,可以看到有org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration这个类

2022081122545023218.png
ok,我们点开这个类,跟踪源码

2022081122545125619.png
我们知道,springboot其实有三种内容服务器,分别是Tomcat,Jetty,Undertow。默认内置tomcat,。我们继续跟踪EmbeddedTomcat.class

2022081122545230120.png

2022081122545330421.png
可以看到,其实这里的tomcat服务器是内部通过java代码实现的。到这里,run()方法就算结束了。如果后续想深入了解的可以自己看看源码。其实,run()方法总结起来并不多,大方向无非是配置环境参数,引入注解刷新上下文。其他的一些捕获异常、计时操作都是非重点操作。

三. springboot总结

3.1 springboot原理

包装spring核心注解,使用springmvc无xml进行启动,通过自定义starter和maven依赖简化开发代码,开发者能够快速整合第三方框架,通过java语言内嵌入tomcat

3.2 springboot启动流程

    --------------------------------创建springbootApplication对象---------------------------------------------
    1. 创建springbootApplication对象springboot容器初始化操作
    2. 获取当前应用的启动类型。
    	注1:通过判断当前classpath是否加载servlet类,返回servlet web启动方式。
    	注2:webApplicationType三种类型:
    		1.reactive:响应式启动(spring5新特性)
    		2.none:即不嵌入web容器启动(springboot放在外部服务器运行 )
    		3.servlet:基于web容器进行启动
    3. 读取springboot下的META-INFO/spring.factories文件,获取对应的ApplicationContextInitializer装配到集合
    4. 读取springboot下的META-INFO/spring.factories文件,获取对应的ApplicationListener装配到集合
    5. mainApplicationClass,获取当前运行的主函数
    6. 
    ------------------调用springbootApplication对象的run方法,实现启动,返回当前容器的上下文----------------------------------------------
    7. 调用run方法启动
    8. StopWatch stopWatch = new StopWatch(),记录项目启动时间
    9. getRunListeners,读取META-INF/spring.factores,将SpringApplicationRunListeners类型存到集合中
    10. listeners.starting();循环调用starting方法
    11. prepareEnvironment(listeners, applicationArguments);将配置文件读取到容器中
    		读取多数据源:classpath:/,classpath:/config/,file:./,file:./config/底下。其中classpath是读取编译后的,file是读取编译前的
    		支持yml,yaml,xml,properties
    12. Banner printedBanner = printBanner(environment);开始打印banner图,就是sprongboot启动最开头的图案
    13. 初始化AnnotationConfigServletWebServerApplicationContext对象
    14. 刷新上下文,调用注解,refreshContext(context);
    15. 创建tomcat
    16. 加载springmvc
    17. 刷新后的方法,空方法,给用户自定义重写afterRefresh()
    18. stopWatch.stop();结束计时
    19. 使用广播和回调机制告诉监听者springboot容器已经启动化成功,listeners.started(context);
    20. 使用广播和回调机制告诉监听者springboot容器已经启动化成功,listeners.started(context);
    21. 返回上下文

注:该原理、流程由每特教育&&蚂蚁课堂余老师总结!