文章目录
一、 前言
二、 @SpringBootApplication原理解析
1. @SpringBootApplication组合注解剖析
2. @SpringBootConfiguration
3. @ComponentScan
4. @EnableAutoConfiguration
三、 SpringApplication.run()原理解析
1. 创建SpringApplicaiton
1.1 获取应用类型
1.2 获取初始化器
1.3 获取初监听器
1.4 定位main方法
2. 调用run方法
2.1 run方法代码总览
2.2 监听器
2.3 引入注解
2.4内置tomcat
三. springboot总结
3.1 springboot原理
3.2 springboot启动流程
一、 前言
我们启动一个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 ToAutoConfiguration {
@Bean
public ToService toService() {
return new ToService();
}
}
作用等同于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="toService" class="ToService"></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的工作原理,大家后面看的应该会更清晰:
它主要就是通过内部的方法,扫描classpath的META-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(),
继续跟踪,进入getAutoConfigurationEntry()方法
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
可以看到这里有个List集合,那这个List集合又是干嘛的?没事,我们继续跟踪getCandidateConfigurations()方法!
可以看到这里有个方法
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;这个方法后面打个断点,可以在返回的集合里找到我们自定义的配置文件路径!
说明成功引入我们自定义的依赖包!
三、 SpringApplication.run()原理解析
SpringApplication.run()原理相对于前面注解的原理,会稍微麻烦点,为了方便我会适当贴出一些注解代码。
首先我们点击查看run方法的源码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[]{ primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[]args) {
return new SpringApplication(primarySources).run(args);
}
可以看出,其实SpringApplication.run()包括两个部分,一部分就是创建SpringApplicaiton实例,另一部分就是调用run()方法,那他们又是怎么运行的?
1. 创建SpringApplicaiton
继续跟踪SpringApplication实例的源码
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
继续跟踪进入,到如下这个方法中
@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方法
从返回结果我们可以看出应用类型一共有三种,分别是
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出现了很多次,调用了start,running等方法。这时候你可能会问,那他们又有什么区别呢?首先,我们先跟踪源码看看这个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寻找关于key为SpringApplicationRunListener的value配置,ok,那我们找找,可以发现在这里存在
看样子这个方法最后返回的是org.springframework.boot.context.event.EventPublishingRunListener这个类,那我们就打开这个类看看是啥。
这个方法它实现了SpringApplicationRunListener接口,那么,这个接口是干啥的呢?没错,他就是用来加载我们配置文件用的。接下来我弄个简单的例子,大家就知道怎么用了。
2.EventPublishingRunListener接口举例
主要实现自定义监听器并且读取我们配置文件内容,先献上我的文件结构
创建一个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
自定义监听器,这里主要是对starting、environmentPrepared、started、running方法进行实现
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);
}
}
启动!可以看到启动信息
然后我们再调用controller方法
成功读取我们自定义的配置文件,现在再回头看看run方法,是不是就清晰了!
2.3 引入注解
springboot的启动分为两部分,一部分是注解,一部分是SpringApplication.run(Springboot.class, args),那么我们的注解又是如何嵌入到程序中呢?靠的就是refreshContext方法,同理,我们跟踪源码进入refreshContext方法
@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的工厂处理器)方法
进入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;
}
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这个类
ok,我们点开这个类,跟踪源码
我们知道,springboot其实有三种内容服务器,分别是Tomcat,Jetty,Undertow。默认内置tomcat,。我们继续跟踪EmbeddedTomcat.class
可以看到,其实这里的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. 返回上下文
https://blog.csdn.net/weixin_40496191/article/details/109098491