前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot运行源代码分析

SpringBoot运行源代码分析

作者头像
程序新视界
发布2019-05-26 14:21:37
1.1K0
发布2019-05-26 14:21:37
举报
文章被收录于专栏:丑胖侠丑胖侠

我们知道SpringBoot是基于“约定优于配置”,也知道可以根据starters自动加载和配置相应的服务,那么SpringBoot底层是怎么实现这些操作呢?这篇文章带大家通过源码分析,了解SpringBoot运行原理。

SpringApplication的拆解

通常创建SpringBoot项目之后,默认的启动代码只有一行,通过默认的配置基本上可以完成大多数的功能,但如果需要对启动流程的扩展,就需要对SpringBoot的启动方法进行拆解。其实这个操作在《SpringBoot基础之banner玩法解析》这篇文章中已经实践过。通过扩展来设置banner相关操作。我们还可以通过其他set操作来设置其他扩展。

代码语言:javascript
复制
@SpringBootApplication
public class SpringLearnApplication {

	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(SpringLearnApplication.class);
		app.setXXX(...);// 自定义扩展
		app.run(args);
	}
}

SpringBoot实例化

先来看看SpringBoot实例化时都做了些什么:

代码语言:javascript
复制
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 1.判断应用的类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// 2.加载扫描到的Initializer
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		// 3.设置**ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 4.推断并设置main方法的定义类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

通过代码中的注释,可以了解到SpringApplication初始化可分为四部:

  • 判断应用的类型;
  • 加载扫描到的Initializer;
  • 加载扫描到的Listener;
  • 推断并设置main方法的定义类。

下面针对四个步骤再看一下相应的源代码实现。

判断应用的类型

此功能在枚举类WebApplicationType中实现,根据INDICATOR_CLASSES的类型来判断当前web应用是什么类型的,分别有:REACTIVE(reactive web应用)、SERVLET(基于servlet的web应用)、NONE(不启动web应用)。

代码语言:javascript
复制
static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

加载扫描到的Initializer

使用SpringFactoriesLoader扫描加载classpath下的META-INF/spring.factories文件中所有可用的Initializer。

代码语言:javascript
复制
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

其中spring.factories默认在spring-boot-autoconfigure包下,文件的内容格式为:

代码语言:javascript
复制
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
# 省略其他的

加载扫描到的ApplicationListener

同样是使用SpringFactoriesLoader扫描加载classpath下的META-INF/spring.factories文件中所有可用的Listener。对应文件类的Listener为:

代码语言:javascript
复制
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

推断并设置main方法的定义类

代码语言:javascript
复制
private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

run方法的基本操作

当实例化对象之后,会调用run方法。下面通过源代码来了解一下run方法都做了些什么。

代码语言:javascript
复制
public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		// 创建SpringApplicationRunListener
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 遍历并调用SpringApplicationRunListener的starting方法
		listeners.starting();
		try {
		    // 创建参数配置
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			// 环境参数准备
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			// 打印banner
			Banner printedBanner = printBanner(environment);
			// 创建ApplicationContext
			context = createApplicationContext();
			// 获得SpringFactories实例
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// 设置Environment,加载相关配置
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			// 刷新ApplicationContext
			refreshContext(context);
			// 刷新context之后调用此方法
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			// 刷新context,启动application
			listeners.started(context);
			// 启动程序
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
		    // 发出running消息,告知程序已启动
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

如果查看当前自动配置

如果想查看当前启动了哪些自动配置有以下几种方式。

(1)运行jar时增加–debug参数。

代码语言:javascript
复制
java -jar xx.jar --debug

(2)在application.properties进行配置。

代码语言:javascript
复制
debug=true

(3)在IDE运行程序中添加VM参数。

代码语言:javascript
复制
-Ddebug

控制台会输出对应日志。已启动的自动配置日志如下:

代码语言:javascript
复制
Positive matches:
-----------------

   CodecsAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)

   CodecsAutoConfiguration.JacksonCodecConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)

未启动的自动配置日志为:

代码语言:javascript
复制
Negative matches:
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

   AopAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)

小结

本篇文章我们简单介绍了springboot启动时都做了些什么,后面我们会逐步讲到整个的运作原理。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringApplication的拆解
  • SpringBoot实例化
    • 判断应用的类型
      • 加载扫描到的Initializer
        • 加载扫描到的ApplicationListener
          • 推断并设置main方法的定义类
          • run方法的基本操作
          • 如果查看当前自动配置
          • 小结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档