简介
springboot源码介绍。
启动流程相关的源码
springboot只是提供了一种快速使用spring的方式,它并没有增加spring的功能,这里介绍springboot是如何启动spring的。
springboot在spring容器的启动之外,额外做了一些工作,方便用户使用spring:
- springboot的启动过程中,会实例化初始化器、监听器,它们是springboot特有的,用于集成第三方框架,基于SPI机制。
- springboot会默认开启自动装配,自动装配相关的bean会和特定的条件相关联,如果条件满足,就把bean注入到spring容器中。
- springboot会把web应用部署在内嵌的服务器上,通常是tomcat,所以springboot项目可以独立部署、独立运行。
这三个点应该是springboot启动过程中最重要的事情,具体流程随后讲解。
相关组件
@SpringBootApplication
被@SpringBootApplication注解标注的类是springboot项目的引导类,通过引导类来启动springboot项目
@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) })
public @interface SpringBootApplication {
}
SpringApplication
启动类,存储了springboot的启动逻辑
public class SpringApplication {
// 主类,main方法所在的类,也就是引导类
private Class<?> mainApplicationClass;
// 应用类型,可选值有 普通web应用、reactor类型的web应用
private WebApplicationType webApplicationType;
// 初始化器的集合
private List<ApplicationContextInitializer<?>> initializers;
// 监听器的集合
private List<ApplicationListener<?>> listeners;
}
springboot使用的spring容器
AnnotationConfigServletWebServerApplicationContext,如果当前springboot应用是一个普通的web应用的话,就使用这个spring容器。
AnnotationConfigServletWebServerApplicationContext的继承体系:
ServletWebServerApplicationContext:继承了GenericWebApplicationContext,它提供了运行在web环境下的spring容器的基本实现,当前类做出了扩展,支持内嵌的web服务器
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
// 内置的web服务器
private volatile WebServer webServer;
private ServletConfig servletConfig;
private String serverNamespace;
}
AnnotationConfigServletWebServerApplicationContext:springboot使用的spring容器
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
implements AnnotationConfigRegistry {
// 读取注解信息
private final AnnotatedBeanDefinitionReader reader;
// 扫描注解信息
private final ClassPathBeanDefinitionScanner scanner;
private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();
private String[] basePackages;
}
基本流程
用户通过编写引导类来启动springboot。引导类的案例:
@SpringBootApplication
public class Demo21App {
public static void main(String[] args) {
SpringApplication.run(Demo21App.class, args);
}
}
从引导类中可以看出,springboot的启动流程的逻辑全部都在run方法中
run方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
// 调用重载的run方法
return run(new Class<?>[] { primarySource }, args);
}
// 重载的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 在重载的run方法中,先调用SpringApplication的构造方法,创建SpringApplication
// 的实例,然后通过SpringApplication的实例,调用成员run方法
return new SpringApplication(primarySources).run(args);
}
springboot的启动流程,代码上可以分为两步,SpringApplication的构造方法、SpringApplication的成员run方法。
SpringApplication的构造方法
整体流程:
public SpringApplication(Class<?>... primarySources) {
// 调用重载的构造方法
this(null, primarySources);
}
// 重载的构造方法,参数primarySources是用户编写的引导类
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
// 校验主类不为null
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));
// 设置主类
this.mainApplicationClass = deduceMainApplicationClass();
}
从源码中可以看出,SpringApplication的构造方法主要做了三件事情:
- 推断当前应用程序的类型
- 加载初始化器和监听器
- 设置主类
推断当前应用程序的类型
调用WebApplicationType中的代码来推断当前程序的类型:
public enum WebApplicationType {
// none类型,表示应用程序不应该运行在一个web容器中
NONE,
// servlet类型,表示应用程序应该运行在一个web容器中
SERVLET,
// reactive类型,表示应用程序是一个反应式的web应用,并且应用运行在一个web容器中
REACTIVE;
// 推断程序类型
static WebApplicationType deduceFromClasspath() {
// 如果当前类路径下可以加载到DispatcherHandler,并且无法加载到DispatcherServlet
// 和ServletContainer,证明当前应用是reactive类型
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// 如果当前应用不是reactive类型,并且类路径下加载不到Servlet
// 和ConfigurableWebApplicationContext,证明当前应用是none类型
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 如果当前应用不是reactive类型并且不是none类型,那么就是servlet类型
return WebApplicationType.SERVLET;
}
// 省略代码
}
在类路径下加载一个类的方法:
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
try {
// 使用类加载器来加载类,如果可以加载到,返回true
forName(className, classLoader);
return true;
}
catch (IllegalAccessError err) {
throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
className + "]: " + err.getMessage(), err);
}
catch (Throwable ex) {
// Typically ClassNotFoundException or NoClassDefFoundError...
return false;
}
}
加载初始化器和监听器
初始化器和监听器被配置在META-INF/spring.factories文件中,这个文件中存储了一系列的键值对,键是接口名,值是接口实现类的名称。
初始化器的接口是ApplicationContextInitializer,监听器的接口是ApplicationListener
META-INF/spring.factories文件的案例:这是spring-boot的jar包中的文件:
# 初始化器
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# 监听器
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
加载spring.factories文件:
private static Map<String, List<String>> loadSpringFactories(
@Nullable ClassLoader classLoader) {
// 从缓存中获取数据
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 加载类路径下所有的META-INF/spring.factories文件
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 properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// 将文件内容存储到集合中
result.add(factoryClassName, factoryName.trim());
}
}
}
// 将结果放入缓存
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
加载初始化器和监听器的方法:
// 参数type表示获取什么类型的实例,例如,初始化器都实现类ApplicationContextInitializer
// 接口,想要获取初始化器的实例,传入ApplicationContextInitializer的类对象即可,监听器也
// 类似,传入ApplicationListener接口的类对象即可
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes,
Object... args) {
// 获取类加载器
ClassLoader classLoader = getClassLoader();
// 根据类对象,从spring.factories中获取配置信息,获取类的全限定名
// 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;
}
设置主类
从堆栈信息中,获取main方法所在的类,把它设置为主类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
// main方法所在的类
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
StackTraceElement类:封装类堆栈信息
public final class StackTraceElement implements java.io.Serializable {
// 类名
// Normally initialized by VM (public constructor added in 1.5)
private String declaringClass;
// 方法名
private String methodName;
// 文件名
private String fileName;
// 代码行号
private int lineNumber;
}
总结
构造方法中做的事情:
- 推断当前应用程序的类型,默认是servlet类型,一个普通的web应用
- 加载初始化器和监听器,这是配置在spring.factories文件中的,这里是SPI机制的实现
- 设置主类,从堆栈信息中找到main方法所在的类,把它作为主类
SpringApplication的成员run方法
成员run方法:
public ConfigurableApplicationContext run(String... args) {
// 计时器,记录程序启动时花费的时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置一个特殊的属性:java.awt.headless,因为有时候在服务器环境下可能没有图形界面
configureHeadlessProperty();
// 获取spring运行时监听器(SpringApplicationRunListener),它用于处理当前run方法执行过程中发生的事件
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布事件:run方法执行事件,可以做一些较早的初始化逻辑
listeners.starting();
try {
// 解析用户从命令行传入的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备environment实例,在这一步,会解析application.properties和用户传入的参数,
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置要忽略的bean,这是主要是设置一个属性
configureIgnoreBeanInfo(environment);
// 打印spring图标
Banner printedBanner = printBanner(environment);
// 创建spring容器的实例
context = createApplicationContext();
// 根据spring.factories中的配置,获取异常报告器 SpringBootExceptionReporter
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备spring容器,包括设置environment组件、执行初始化器、注册引导类的bean信息
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新spring容器,在这里执行refresh方法,启动spring容器和web容器
refreshContext(context);
// 扩展点
afterRefresh(context, applicationArguments);
// 停止计时
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发布应用启动完成事件
listeners.started(context);
// 调用ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
run方法中做的事情:
- 解析用户传入的参数和application.properties
- 打印spring图标
- 调用初始化器
- 启动spring容器
- 调用ApplicationRunner和CommandLineRunner
解析用户传入的配置信息
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 创建environment实例
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置environment实例,这里主要是把用户从命令行传入的参数添加到environment中
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 发布环境准备就绪事件,在事件监听器中加载自定义配置文件 application.properties
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
根据应用类型创建environment实例
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
// servlet类型的容器,创建StandardServletEnvironment类型的实例
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
解析参数的逻辑:environment实例中会存储用户传入的参数,这里简单提一下参数是如何解析的。
public CommandLineArgs parse(String... args) {
CommandLineArgs commandLineArgs = new CommandLineArgs();
for (String arg : args) {
if (arg.startsWith("--")) {
// 参数是 --key=value 的形式
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = null;
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
}
else {
// 普通参数
commandLineArgs.addNonOptionArg(arg);
}
}
return commandLineArgs;
}
从这里看springboot应该不支持处理单横杆开头的参数
打印spring图标
图标所在位置:
class SpringBootBanner implements Banner {
private static final String[] BANNER = {
"",
" . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\",
"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", "
' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/" };
// 省略代码
}
实例化spring容器
protected ConfigurableApplicationContext createApplicationContext() {
// 根据应用类型,创建spring容器,servlet类型的应用,默认创建
// AnnotationConfigServletWebServerApplicationContext类型的spring容器
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
准备spring容器
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments,
Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 应用初始化器,初始化器最经常做的事情,就是向spring容器中添加后置处理器和监听器
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
准备spring容器的过程中做的最重要的事情:应用初始化器,它可以在容器启动前执行自定义的初始化逻辑。
刷新spring容器
private void refreshContext(ConfigurableApplicationContext context) {
// 调用父类的refresh方法
refresh(context);
// 注册关闭jvm结束时需要执行的回调函数
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
这里就是调用spring中的refresh方法,来刷新spring容器,基本流程在spring中都学过了,只有几个扩展点不一样
扩展点1:onRefresh()方法,创建web容器
// ServletWebServerApplicationContext类
@Override
protected void onRefresh() {
super.onRefresh();
try {
// 创建web容器
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
// 这里以tomcat为例,创建一个内嵌进应用中的tomcat实例
public WebServer getWebServer(ServletContextInitializer... initializers) {
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);
}
运行ApplicationRunner和CommandLineRunner
它们是springboot启动后就会立刻调用的bean,用于在springboot启动后第一时间执行某些任务。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 从spring容器中获取ApplicationRunner和CommandLineRunner
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
// 执行
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
总结
springboot启动过程中,最重要的事情,是自动装配,它默认向spring容器中注入了很多bean,同时,它会启动一个内嵌的web容器。
springboot内置的tomcat
实现原理:Tomcat本身提供的外部接口,使其它应用程序能够非常方便的将Tomca嵌入到自身的应用,通过调用Tomcat提供的外部类org.apache.catalina.startup.Tomcat