Spring源码学习(八):Spring Boot原理

目录

1.Spring Boot简介

2.Spring Boot启动原理 —— 构造方法

2.1 deduceFromClasspath方法

2.2 配置ApplicationContextInitializer、ApplicationListener

3.Spring Boot启动原理 —— run方法

3.1 SpringApplicationRunListener

3.2 环境准备

3.2.1 prepareEnvironment

3.2.2 ApplicationEnvironmentPreparedEvent事件的处理

3.2.3 配置是否忽略Bean信息

3.3 打印Banner

3.4 创建容器

3.5 准备容器

3.5.1 准备

3.5.2 加载

3.6 容器刷新

3.7 启动Runners

3.8 异常处理

4. Starter机制的实现

4.1 @SpringBootApplication

4.2 selectImports方法

4.2.1 读取注解属性

4.2.2 读取并处理配置

4.2.3 激活过滤器

4.2.4 触发事件

4.3 配置类的解析和装载

5. 重要注解

5.1 @ComponentScan

5.2 @Conditional

5.3 @Value


1.Spring Boot简介

Spring MVC非常强大,但是也存在以下问题:

  1. 配置繁琐,哪怕一个最小项目,也要配置至少三个XML文件
  2. 依赖Tomcat等Web容器运行,增加了维护难度
  3. 与常用组件集成起来不方便,例如与Mybatis集成,每个项目都要配置数据源、事务等,但是这些配置实际都是通用的,完全可以按照约定提供默认情况

Spring Boot就是为了解决这一情况的,它具有如下特点(来源于百度百科):

  1. 创建独立的Spring应用程序
  2. 嵌入的Tomcat,无需部署WAR文件
  3. 简化Maven配置
  4. 自动配置Spring
  5. 提供生产就绪型功能,如指标,健康检查和外部配置
  6. 绝对没有代码生成并且对XML也没有配置要求

一个最简单的Spring Boot项目组成如下:

pom.xml(部分):

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.1.0.RELEASE</version>
        </plugin>
    </plugins>
</build>

主类:

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

Controller:

@RestController
public class TestController {
    @GetMapping("/")
    public String hello(){ return "hello"; }
}

运行主类后,在浏览器访问localhost:8080就可以看到打印出了“hello”。

2.Spring Boot启动原理 —— 构造方法

Spring Boot项目是通过SpringApplication.run方法作为入口启动的,它接受了两个参数:一个是Class类型,另一个是String[]类型,run方法做了两次跳转:第一次将Class对象封装为Class[]数组,转发给自身的重载;第二次用Class[]对象实例化SpringApplication对象,然后将String[]对象传入run成员方法(main函数调用的是静态方法):

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);
}

首先来看它的构造函数,resourceLoader默认为null,主要是一些成员属性的设置:

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));
    this.mainApplicationClass = deduceMainApplicationClass();
}

2.1 deduceFromClasspath方法

从方法名和上下文不难看出,deduceFromClasspath的作用就是推断Web程序类型:

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;
}
  • Reactive类型:这是Spring 5新增的类型,需要有DispatcherHandler类实现,且没有DispatcherServlet和ServletContainer实现
  • Servlet类型:传统Web应用,需要有ConfigurableWebApplicationContext、DispatcherServlet/ServletContainer实现
  • None类型:不是Web应用

2.2 配置ApplicationContextInitializer、ApplicationListener

这一步由两个方法构成:setInitializers/setListeners和getSpringFactoriesInstances,前者就是赋值操作,主要来看后者。getSpringFactoriesInstances方法源码如下,显而易见,它的核心方法是loadFactoryNames和createSpringFactoriesInstances:

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

首先来看loadFactoryNames:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

loadSpringFactories源码如下,其实就是从properties文件中加载配置:

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()) {
				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);
	}
}

FACTORIES_RESOURCE_LOCATION常量的字面值是“META-INF/spring.factories”。也就是说,这里查找了以ApplicationContextInitializer和ApplicationListener的全限定名为键的配置值。

在org.springframework.boot:spring-boot包下,可以看到如下配置:

# 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-boot-autoconfigure下还配置了一些:

# 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

这6+10个类就是Spring Boot默认加载的加载器和监听器。

spring.factories文件的配置生效需要靠createSpringFactoriesInstances。该方法实际就是反射实例化spring.factories中配置的类,并将实例添加到List中: 

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

3.Spring Boot启动原理 —— run方法

run方法是Spring Boot的核心,在这里完成了ApplicationContext的启动和初始化。它的流程如下:

3.1 SpringApplicationRunListener

首先获取和启动了SpringApplicationRunListener,实际也借助了getSpringFactoriesInstances,Spring Boot默认提供的实现是EventPublishingRunListener:

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

starting方法广播了一个ApplicationStartingEvent。

3.2 环境准备

先上源码,一共做了三件事:构造ApplicationArguments对象、准备环境、配置是否忽略Bean信息:

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);

DefaultApplicationArguments构造方法很简单:

public DefaultApplicationArguments(String... args) {
	Assert.notNull(args, "Args must not be null");
	this.source = new Source(args);
	this.args = args;
}

Source是其内部类,是一个PropertySource实现类。

3.2.1 prepareEnvironment

这里的核心逻辑是获取并配置Environment对象,首先调用getOrCreateEnvironment方法,获取已有实例或根据Web程序类型创建新实例:

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
	}
}

一般来说创建的都是StandardServletEnvironment,它会初始化四个PropertySource:

  • servletContextInitParams
  • servletConfigInitParams
  • System.getenv()
  • System.getProperties()

后两个来自其父类StandardEnvironment。如果JNDI环境可用,还会额外增加jndiProperties。

然后调用configureEnvironment进行配置,实际做了三件事:配置ConversionService、配置PropertySource、配置Profile,这三个都是很熟悉的概念了,不做赘述:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

之后就是一些后处理操作,主要就是绑定和转换操作:

bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);

isCustomEnvironment默认是false,所以会进行转换,这里是检查经过一番处理后,environment还是不是创建时的类型。

3.2.2 ApplicationEnvironmentPreparedEvent事件的处理

此时容器环境已经准备完毕,通过SpringApplicationRunListener发出ApplicationEnvironmentPreparedEvent事件:

listeners.environmentPrepared(environment);

该事件会触发ConfigFileApplicationListener进行处理:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值