spring_boot_cloud(5)SpringApplication源码分析与作用详解
SpringApplication的初始化流程源码分析
SpringApplication入口
1 | @SpringBootApplication |
我们的spring boot的启动都是从这个main方法开始的,,那么这个方法的主要的一行代码就是SpringApplication.run(MyApplication.class,args);我们本次要看一下他的执行流程。
run方法点进去之后:
1 | public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { |
ConfigurableApplicationContext是一个即使不是所有应用上下文 也是大部分的应用上下文实现的spi接口,提供了一些设施用来配置应用上下文,配置和生命周期方法封装在这里,避免他们出现在应用上下文的客户端代码里边,ConfigurableApplicationContext罗列的方法用来启动和关闭的代码里边。
run方法的入参是一个Class类型的参数,然后掉了另一个重载的run方法,这个run方法的第一个入参是Class类型的数组,第二个参数是传进来的args,这种变成风格很常见,一般一个方法是通用的,接受数组类型的,一个方法是只接受一个参数的,一个参数的方法是接受数组参数方法的特例,那么接受一个参数的方法会调用通用的那个方法。跟进去:
1 | public static ConfigurableApplicationContext run(Class<?>[] primarySources, |
这个方法new 了一个SpringApplication的实例执行了SpringApplication实例的run方法,返回值就是SpringApplication的run方法的返回值。
接着往里走:
1 | public SpringApplication(Class<?>... primarySources) { |
SpringApplication构造器的主流程
最终来到了这个构造器:
1 | /** |
ResourceLoader
ResourceLoader:用来加载资源的策略接口(class path,类路劲,文件资源),org.springframework.context.ApplicationContext被要求提供这个功能,再加上org.springframework.core.io.support.ResourcePatternResolver这样一个支持。DefaultResourceLoader是一个独立的实现,他被用在ApplicationContext之外,也是被资源编辑器ResourceEditor所使用。
ResourceLoader结构:
1
2
3
4
5ResourceLoader
//加载资源都离不开类加载器
getClassLoader
getResource
CLASSPATH_URL_PREFIX
WebApplicationType探测应用类型逻辑
this.webApplicationType = WebApplicationType.deduceFromClasspath();这一行是确定应用类型,进到枚举WebApplicationType看一下:
1 | public enum WebApplicationType { |
ApplicationContextInitializer的加载和初始化逻辑
ApplicationContextInitializer介绍
接下来是 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
getSpringFactoriesInstances的作用就是加载实现了ApplicationContextInitializer接口的这些工厂的实例。
首先看一下getSpringFactoriesInstances的参数ApplicationContextInitializer.class
看一下doc:
1 | Callback interface for initializing a Spring ConfigurableApplicationContext prior to being refreshed. |
在bean刷新之前初始化 Spring ConfigurableApplicationContext的一个回调接口。
通常用于在web应用当中,需要编程的方式初始化应用的上下文。
比如我们注册 一个属性源,激活profile这对于上下文的环境。
ApplicationContextInitializer被鼓励探测spring排序接口是不是被实现或者@Order
注解是不是存在,存在的话用来排序。
ApplicationContextInitializer主要用来完成初始化工作。
加载所有工厂并缓存
那为什么getSpringFactoriesInstances方法要传入初始化器呢,跟进去看一下:
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { |
getClassLoader()方法是获取类的加载器:
1 | //如果resourceLoader不是空,就返回resourceLoader的类加载器,此处resourceLoader是空的,if语句不会执行 |
然后看一下ClassUtils.getDefaultClassLoader():
1 | 逻辑很简单,默认返回线程上下文类加载器 |
我们的程序返回的是系统类加载器(线程上下文类加载器中的)
Set
type是ApplicationContextInitializer.class,
这里SpringFactoriesLoader加载了工厂的名字:
SpringFactoriesLoader解析:
doc:
General purpose factory loading mechanism for internal use within the framework.
SpringFactoriesLoader loads and instantiates factories of a given type from “META-INF/spring.factories” files which may be present in multiple JAR files in the classpath. The spring.factories file must be in Properties format, where the key is the fully qualified name of the interface or abstract class, and the value is a comma-separated list of implementation class names. For example:
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
where example.MyService is the name of the interface, and MyServiceImpl1 and MyServiceImpl2 are two implementations.
在框架内部所使用的的一种通用工厂加载机制。
SpringFactoriesLoader从文件 “META-INF/spring.factories” 里边指定的类型工厂进行加载和实例化。
这个文件可能位于classpath下多个jar文件当中。spring.factories文件必须要是Properties格式的,其中key就是接口或者抽象类的完全限定的名字,值是逗号分隔的实现类名字的列表。举例:
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
其中example.MyService是接口的名字,example.MyServiceImpl1,example.MyServiceImpl2是2个实现类。
1 | public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { |
factoryClass.getName扩展
factoryClass.getName():
这里做一下扩展结合之前jvm字节码的章节,我们看一下这个方法的doc:
返回调用getName的class对象的实体(类,接口,数组,原生类型,或者void)的名字。并且作为字符串的形式返回
如果class对象代表的是引用类新型,并且不是数组类型,就会返回这个类的二进制的名字,名字是java 语言规范规定的格式返回的。
如果class的对象代表的是原生类型或者是void,调用getName返回的名称就是原生类型的名字或者void。
如果class对象表示的是数组类,那么返回的名字的内部形式就会包含元素类型的名字,其中”[“代表的是数组的维度数量。
接下来回到主流程,进入到loadSpringFactories():
1 | //进入loadSpringFactories |
classLoader.getResources扩展:
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) 解析:
1 | 根据名称得到资源(图片、音频、文本等),这些资源可以被class字节码访问,并且和字节码在位置上是独立存在的。 |
加载三个spring jar的工厂
我们debug可以看到url的位置
!(url.png)[url.png]
jar:file:/D:/gradlerepo/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/2.1.3.RELEASE/58e07f69638a3ca13dffe8a2b68d284af376d105/spring-boot-autoconfigure-2.1.3.RELEASE.jar!/META-INF/spring.factories
然后打开这个spring.factories
里边有七个主要的类(接口):
- org.springframework.context.ApplicationContextInitializer
- org.springframework.context.ApplicationListener
- org.springframework.boot.autoconfigure.AutoConfigurationImportListener
- org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
- org.springframework.boot.autoconfigure.EnableAutoConfiguration
- org.springframework.boot.diagnostics.FailureAnalyzer
- org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider
接下往下走就会看到这七个接口:
最后loadSpringFactories返回的result是一个map,key是接口的名称,value是接口的实现类:
比如刚才的spring.factories里边 org.springframework.boot.autoconfigure.EnableAutoConfiguration有118个实现,那么map的key就是 org.springframework.boot.autoconfigure.EnableAutoConfiguration,value是她的118个实现。
然后再看一下第二个工厂的jar位置:
jar:file:/D:/gradlerepo/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/2.1.3.RELEASE/92bb92cd73212cefc1e5112e3bbf1f31c154c3fd/spring-boot-2.1.3.RELEASE.jar!/META-INF/spring.factories
看一下它里边的结构:
主要接口如下:
- org.springframework.boot.diagnostics.FailureAnalysisReporter
- org.springframework.boot.diagnostics.FailureAnalyzer
- org.springframework.boot.env.EnvironmentPostProcessor
- org.springframework.boot.env.PropertySourceLoader
- org.springframework.boot.SpringApplicationRunListener
- org.springframework.boot.SpringBootExceptionReporter
- org.springframework.context.ApplicationContextInitializer
- org.springframework.context.ApplicationListener
我们看到有一个org.springframework.context.ApplicationListener但是在spring-boot-autoconfigure-2.1.3.RELEASE.jar里边也有一个org.springframework.context.ApplicationListener,这个时候就能看到MultiValueMap<String, String>所起的作用,相同key的value会进行合并。
继续放下看下一个工厂jar的位置:
jar:file:/D:/gradlerepo/caches/modules-2/files-2.1/org.springframework/spring-beans/5.1.5.RELEASE/58b10c61f6bf2362909d884813c4049b657735f5/spring-beans-5.1.5.RELEASE.jar!/META-INF/spring.factories
然后打开对应的spring.factories文件:
这里边只有一行:
1 | org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory |
所有工厂的合并结果
由此我们可以得出结论spring boot启动的时候会初始化三个jar包的工厂。
得到的result是三个jar包所有的数据进行了合并,合并之后一共有13个工厂接口:
1 | 0 = {LinkedHashMap$Entry@1746} "org.springframework.boot.autoconfigure.AutoConfigurationImportFilter" -> " size = 3" |
得到ApplicationContextInitializer的所有实现
loadSpringFactories(classLoader)的结果就是上边的13个实例,然后loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
里边getOrDefault方法的参数是factoryClassName是interface org.springframework.context.ApplicationContextInitializer,
意思是从13个接口里边找到ApplicationContextInitializer这个接口,是能找到的,一共有6个ApplicationContextInitializer的实现类:
1 | 0 = "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer" |
创建ApplicationContextInitializer所有实现的实例
接下来的逻辑是:
List
type:interface org.springframework.context.ApplicationContextInitializer
parameterTypes:是空数组。
classLoader:应用类加载器
args:空数组
names:即我们上边得到的有6个对象的数组。
逻辑如下:
1 | private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, |
BeanUtils.instantiateClass扩展
BeanUtils.instantiateClass:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
/**
ReflectionUtils.makeAccessible实现,即如果不可访问的改为可访问的。
public static void makeAccessible(Constructor<?> ctor) {
if ((!Modifier.isPublic(ctor.getModifiers()) ||
!Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) {
ctor.setAccessible(true);
}
}
**/
ReflectionUtils.makeAccessible(ctor);
//对java和kotlin类型的进行生成。
return KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ? BeanUtils.KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args);
} catch (InstantiationException var3) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", var3);
} catch (IllegalAccessException var4) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", var4);
} catch (IllegalArgumentException var5) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", var5);
} catch (InvocationTargetException var6) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", var6.getTargetException());
}
}
创建出来的对象:
setInitializers和排序
回到getSpringFactoriesInstances,返回的map类型的result放到LinkedHashSet里边:
Set
接下来是 List
创建工厂的实例。
AnnotationAwareOrderComparator.sort(instances);是进行排序,然后这个方法就返回了。
回到了SpringApplication的构造器,执行到了:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setInitializers的操作:
1 | public void setInitializers( |
只是添加到集合当中。
注意:【setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));】和
【setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));】的执行流程是一模一样的。
ApplicationListener
org.springframework.context @FunctionalInterface
public interface ApplicationListener
extends EventListener
Interface to be implemented by application event listeners. Based on the standard java.util.EventListener interface for the Observer design pattern.
As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.
一个用来被应用事件监听实现的接口,基于标准的java.util.EventListener接口,是一种观察者模式。
在Spring 3.0版本,一个应用监听器可以声明一个它感兴趣的事件类型,当注册到spring的应用上下文里边,事件将相应的过滤,当注册的事件发生的时候,监听器就会被触发。
1 | ApplicationListener是一个函数式接口 |
它最终初始化的所有监听器:
1 | 0 = {BackgroundPreinitializer@1931} |
主应用类获取deduceMainApplicationClass
SpringApplication的SpringApplication(ResourceLoader resourceLoader, Class<?>… primarySources) 方法最后一行代码:
1 | this.mainApplicationClass = deduceMainApplicationClass(); |
即获取主应用类的逻辑,我们进去看一下:
1 | private Class<?> deduceMainApplicationClass() { |
这种获取主应用类的凡是非常讨巧,首先new一个运行时异常,同时得到它的堆栈信息,然后遍历堆栈信息,当某一条的堆栈信息调用的方式是main方法的时候,得到main方法所在的类就是主应用类。
小结
到目前为止SpringApplication的【public SpringApplication(ResourceLoader resourceLoader, Class<?>… primarySources)】方法已经分析完毕,返回到上一层的逻辑:
SpringApplication(primarySources).run(args);
接下来会分析run方法的执行逻辑,,run方法逻辑分析完毕整个spring boot的启动流程也就完毕了。
SpringApplication的run方法源码分析
run方法逻辑:
1 | /** |
StopWatch
看一下它的doc
Simple stop watch, allowing for timing of a number of tasks, exposing total running time and running time for each named task.
Conceals use of System.currentTimeMillis(), improving the readability of application code and reducing the likelihood of calculation errors.
Note that this object is not designed to be thread-safe and does not use synchronization.
This class is normally used to verify performance during proof-of-concepts and in development, rather than as part of production applications.
Since:
May 2, 2001
这个类的作者是Rod Johnson在20015月2号写的,时间飞逝,现在已经是2019年了,18年过去了,我也写了快5年的代码了,希望每个人技术爱好者能够坚持自己的初衷,也希望你们早日找到女朋友,闲言少叙看一下StopWatch的介绍。
一个简单的计数器,它支持一些列任务的计时工作,公开总的运行时间,还有具名任务的时间。
可以不去让我们使用 System.currentTimeMillis()得到系统时间,目的是为了应用代码的可读性以及可能的计算错误。
注意和这个对象没有设计为线程安全的,不能用于同步。
这个类通常用于在 proof-of-concepts阶段验证性能,并不是产品应用的一部分。
这里就用于统计我们应用的启动时间。
它的内部有一个private final List
ConfigurableApplicationContext
ApplicationContext
ConfigurableApplicationContext继承了ApplicationContext,我们现在看到的应用启动过程都是围绕ApplicationContext进行的,有必要看一下ApplicationContext的doc:
org.springframework.context public interface ApplicationContext
extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver
Central interface to provide configuration for an application. This is read-only while the application is running, but may be reloaded if the implementation supports this.
An ApplicationContext provides:
- Bean factory methods for accessing application components. Inherited from ListableBeanFactory.
- The ability to load file resources in a generic fashion. Inherited from the org.springframework.core.io.ResourceLoader interface.
- The ability to publish events to registered listeners. Inherited from the ApplicationEventPublisher interface.
- The ability to resolve messages, supporting internationalization. Inherited from the MessageSource interface.
- Inheritance from a parent context. Definitions in a descendant context will always take priority. This means, for example, that a single parent context can be used by an entire web application, while each servlet has its own child context that is independent of that of any other servlet.
In addition to standard org.springframework.beans.factory.BeanFactory lifecycle capabilities, ApplicationContext implementations detect and invoke ApplicationContextAware beans as well as ResourceLoaderAware, ApplicationEventPublisherAware and MessageSourceAware beans.
See Also:
ConfigurableApplicationContext,
org.springframework.beans.factory.BeanFactory,
org.springframework.core.io.ResourceLoader
一个用于提供应用配置的中心接口,当应用运行的时候是只读的,但是如果他的实现支持刷新,它也是会被刷新的。
一个应用上下文提供如下:
- bean工厂的方法,用于访问应用的组件,来自于ListableBeanFactory。
- 以一种通用的风格加载文件资源的能力,从org.springframework.core.io.ResourceLoader接口沿袭下来的能力
- 向注册的监听器发布事件的能力,来自于ApplicationEventPublisher接口
- 解析消息的能力,支持国际化,来自于MessageSource接口
- 可以从父的上下文继承能力,定义的后代的上下文拥有更高的优先级,这意味着,比如,一个单例的父上下文,可以被整个web应用使用,这样每个servlet的和其他的都是独立的。
ConfigurableApplicationContext
org.springframework.context public interface ConfigurableApplicationContext
extends ApplicationContext, Lifecycle, Closeable
SPI interface to be implemented by most if not all application contexts. Provides facilities to configure an application context in addition to the application context client methods in the ApplicationContext interface.
Configuration and lifecycle methods are encapsulated here to avoid making them obvious to ApplicationContext client code. The present methods should only be used by startup and shutdown code.
被大部分,但不是全部的应用上下文实现的spi接口,在ApplicationContext接口的基础上通过附加的应用上下文客户端方法的形式提供了一些配置应用上下文的基础设施。
配置与生命周期方法被封装在这里,以避免显式的公开给客户端代码,只能被启动和关闭代码使用。
SpringBootExceptionReporter
SpringBootExceptionReporter是一个函数式接口。
1 | /** |
它有唯一一个实现类FailureAnalyzers,构造器如下:
1 | FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) { |
configureHeadlessProperty()方法
1 | private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless"; |
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS属性表达意图:这是一个服务器应用,没有显示器,没有键盘,没有鼠标的应用。
getRunListeners()方法以及监听器模式的经典应用
1 | private SpringApplicationRunListeners getRunListeners(String[] args) { |
最后返回的是SpringApplicationRunListeners,SpringApplicationRunListeners里边有一个集合【 private final List
因为有必要看那一下 SpringApplicationRunListener的doc
SpringApplicationRunListener类
org.springframework.boot public interface SpringApplicationRunListener
Listener for the SpringApplication run method. SpringApplicationRunListeners are loaded via the SpringFactoriesLoader and should declare a public constructor that accepts a SpringApplication instance and a String[] of arguments. A new SpringApplicationRunListener instance will be created for each run.
针对SpringApplication run方法的监听器,SpringApplicationRunListeners通过SpringFactoriesLoader加载的,而且声明一个public的构造器,并且接受一个SpringApplication类型的参数,和一个string类型的数组(这也是为什么上面介绍的getRunListeners方法携带【Class>[] types = new Class>[] { SpringApplication.class, String[].class };】参数的原因,SpringApplicationRunListener实例将会针对于运行一个新的SpringApplication的run被创建。
主要是对run方法的监听的作用。观察者模式的体现。
由于是一个监听器,它的方法接口都体现了声明周期的,SpringApplicationRunListener方法如下:
1 | //ApplicationContext已经被加载,但是它还没有被刷新之前调用 |
SpringApplicationRunListeners
代码结构:
1 | private final List<SpringApplicationRunListener> listeners; |
这些方法和SpringApplicationRunListener方法是一致的,SpringApplicationRunListeners对所有的SpringApplicationRunListener统一管理。
EventPublishingRunListener
我们断点一下看一下有几个监听器:
通过断点可以看到只有一个EventPublishingRunListener,用于发布SpringApplicationEvent,通过ApplicationEventMulticaster在上下文发布之前发布事件。
EventPublishingRunListener构造器如下:
1 | private final SpringApplication application; |
SimpleApplicationEventMulticaster
构造器里边初始化了一个SimpleApplicationEventMulticaster,看一下介绍:
org.springframework.context.event public class SimpleApplicationEventMulticaster
extends AbstractApplicationEventMulticaster
Simple implementation of the ApplicationEventMulticaster interface.
Multicasts all events to all registered listeners, leaving it up to the listeners to ignore events that they are not interested in. Listeners will usually perform corresponding instanceof checks on the passed-in event object.
By default, all listeners are invoked in the calling thread. This allows the danger of a rogue listener blocking the entire application, but adds minimal overhead. Specify an alternative task executor to have listeners executed in different threads, for example from a thread pool.
ApplicationEventMulticaster接口的简单实现。
广播所有的事件给所有注册的监听器,对于不感兴趣的事件的过滤的决定权留给每个监听器自己去处理。监听器对于传递过来的时间对象通常会进行instanceof检查,默认情况,所有的监听器都会在被调用的线程去执行,这样就会存在一种危险,某一些监听器会阻塞整个的应用,但确是最小的成本,用一个替代的任务执行器拥有这个监听器,这样在不同的线程去执行,比如在线程池里边。
看一下EventPublishingRunListener的starting方法:
1 | public void starting() { |
这里冒出来一个ApplicationStartingEvent。
ApplicationStartingEvent
org.springframework.boot.context.event public class ApplicationStartingEvent
extends SpringApplicationEvent
Event published as early as conceivably possible as soon as a SpringApplication has been started - before the Environment or ApplicationContext is available, but after the ApplicationListeners have been registered. The source of the event is the SpringApplication itself, but beware of using its internal state too much at this early stage since it might be modified later in the lifecycle.
当spring应用已经启动的时候尽早的把事件发布出去,并且在Environment或者ApplicationContext可用之前,但是在ApplicationListeners已经被注册之后,时间的源是SpringApplication本身,但是请注意使用它的内部状态太多的话,会对后续的生命周期有一些影响。
它的父类是SpringApplicationEvent,父类的实现类有如下事件:
ApplicationContextInitializedEvent
ApplicationEnvironmentPreparedEvent
ApplicationFailedEvent
ApplicationPreparedEvent
ApplicationReadyEvent
ApplicationStartedEvent
ApplicationStartingEvent
诠释了Application的生命周期。
小结
应用在启动的时候,会在某一些时间点发布一些事件,发对对象是所有注册的事件监听器,事件监听器自己决定是否感兴趣和处理这个事件,这是一种监听器设计模式的体现,监听器都有一个源,就是监听器的主题,源是Application本身,SpringApplicationRunListeners在不同的时间点发布不同的事件对象。
environment组件的重要作用详解
Environment doc解读
我们走到了【ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);】这行代码,首先理解下ConfigurableEnvironment,它的父类是Environment,看下Environment的doc:
org.springframework.core.env
public interface Environment extends PropertyResolver
Interface representing the environment in which the current application is running. Models two key aspects of the application environment: profiles and properties. Methods related to property access are exposed via the PropertyResolver superinterface.
A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the @Profile
annotation for syntax details. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.
Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Beans managed within an ApplicationContext may register to be EnvironmentAware or @Inject
the Environment in order to query profile state or resolve properties directly.
In most cases, however, application-level beans should not need to interact with the Environment directly but instead may have to have ${…} property values replaced by a property placeholder configurer such as PropertySourcesPlaceholderConfigurer, which itself is EnvironmentAware and as of Spring 3.1 is registered by default when using context:property-placeholder/.
Configuration of the environment object must be done through the ConfigurableEnvironment interface, returned from all AbstractApplicationContext subclass getEnvironment() methods. See ConfigurableEnvironment Javadoc for usage examples demonstrating manipulation of property sources prior to application context refresh().
代表了一种当前应用正在运行的环境,有两个很重要应用环境的方面:prifile和properties,访问属性的方法通过PropertyResolver父接口提供。
profile是被具名的,逻辑的bean的分组,当给定的profile是活动的状态时,这些bean定义会被被注册到容器当中,bean无论是xml定义或者annotation的形式,bean会被关联一个profile,请看 spring-beans 3.1的schema了解Profile注解的语法详情,环境的角色用来决定它关联的profiles,那些profile当前是活动的,并且这些profiles默认情况下是活动的。
在大多数的应用中,Properties扮演了一个重要的角色,它可能来自各种各样的来源,比如属性文件,jvm的系统属性,系统环境变量,jndi,servlet山下文参数,及时变更的属性对象,map,等等,关联属性的environment对象的这种角色,为用户提供了方便的属性配置属性解析的接口服务。
ApplicationContext里边注册的这些bean可以是EnvironmentAware或者是 @Inject
的,这些可以用来查询profile状态和属性解析。
在大多数的情况下,应用级别的bean不需要直接和Environment交互,但是可以用一种 ${…}这种形式的占位符配置器的方式去配置,比如PropertySourcesPlaceholderConfigurer,他自己本身就是环境组件,在Spring 3.1当中,通过使用context:property-placeholder/进行注册。
环境对象的配置必须通过ConfigurableEnvironment接口,从AbstractApplicationContext所有子类的getEnvironment方法返回。
ConfigurableEnvironment doc解读
接下来是ConfigurableEnvironment,也就是Environment的子类,看一下doc:
org.springframework.core.env public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver
Configuration interface to be implemented by most if not all Environment types. Provides facilities for setting active and default profiles and manipulating underlying property sources. Allows clients to set and validate required properties, customize the conversion service and more through the ConfigurablePropertyResolver superinterface.
Manipulating property sources
Property sources may be removed, reordered, or replaced; and additional property sources may be added using the MutablePropertySources instance returned from getPropertySources(). The following examples are against the StandardEnvironment implementation of ConfigurableEnvironment, but are generally applicable to any implementation, though particular default property sources may differ.
Example: adding a new property source with highest search priority
ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, String> myMap = new HashMap<>();
myMap.put(“xyz”, “myValue”);
propertySources.addFirst(new MapPropertySource(“MY_MAP”, myMap));
Example: removing the default system properties property source
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)
Example: mocking the system environment for testing purposes
MutablePropertySources propertySources = environment.getPropertySources();
MockPropertySource mockEnvVars = new MockPropertySource().withProperty(“xyz”, “myValue”);
propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);
When an Environment is being used by an ApplicationContext, it is important that any such PropertySource manipulations be performed before the context’s refresh() method is called. This ensures that all property sources are available during the container bootstrap process, including use by property placeholder configurers.
一个不是被所有也是被大多数Environment类型所实现的配置接口,提供了设置活动的默认的profiles,以及操作底层的属性元的基础设施,允许客户端设置和验证所要求的属性,定制转换服务,都是通过ConfigurablePropertyResolver这样的一个父接口进行的。
如何操纵属性源?
属性源可以被删除,重排序也可以被替换,并且额外的属性来源可以通过MutablePropertySources进行添加,通过getPropertySources方法进行获取,接下来的举例针对于ConfigurableEnvironment的标准环境的实现,不过和特定的默认的属性源有点不同。
例子:添加了一个高搜索优先级的属性源:
1 | ConfigurableEnvironment environment = new StandardEnvironment(); |
举例:删除默认系统属性源:
1 | MutablePropertySources propertySources = environment.getPropertySources(); |
举例:针对测试的目的模拟系统的环境:
1 | MutablePropertySources propertySources = environment.getPropertySources(); |
prepareEnvironment方法
它的逻辑如下:
1 | private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { |
getOrCreateEnvironment()方法
1 | private ConfigurableEnvironment getOrCreateEnvironment() { |
configureEnvironment()方法
1 | protected void configureEnvironment(ConfigurableEnvironment environment, |
ConversionService:一个用来进行类型转换的服务接口,装换系统的入口点,调用convert实现线程安全的转换。
ApplicationConversionService.getSharedInstance();用的单利模式,而且是线程安全的,这是经典的单利模式的使用,懒汉模式,注意配合volatile关键字。
1 | private static volatile ApplicationConversionService sharedInstance; |
【configurePropertySources(environment, args);】
添加,移除,重排序属性源
1 | protected void configurePropertySources(ConfigurableEnvironment environment, |
【configureProfiles(environment, args);】
针对于整个应用的环境,配置那些profile是活动的或者是默认的,其他额外的profile可以在配置文件处理过程中通过spring.profiles.active属性激活。
listeners.environmentPrepared(environment)
触发了一个新的事件——环境准备好的事件。
1 | public void environmentPrepared(ConfigurableEnvironment environment) { |
Banner
在进行往下进行之前,我们先对banner做一个实验,我们在resources目录下新建一个banner.txt文件,里边输入一段文字,然后启动应用,会看到打印了我们输入的信息,banner自定义生效:
Banner是一个函数式接口,里边有一个枚举,用于配置banner的模式:
1 | enum Mode { |
在主流程了printBanner()方法:
1 | private Banner printBanner(ConfigurableEnvironment environment) { |
SpringApplicationBannerPrinter
用来打印application的banner,其内部有一个成员变量【static final String DEFAULT_BANNER_LOCATION = “banner.txt”;】
即默认的banner文件。
构造器:
1 | SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) { |
看一下SpringApplicationBannerPrinter的print方法:
1 | public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) { |
getBanner根据环境得到banner。
1 | private Banner getBanner(Environment environment) { |
其中看一下getTextBanner():
1 | static final String DEFAULT_BANNER_LOCATION = "banner.txt"; |
然后我们看一下SpringBootBanner的printBanner()方法:
1 | private static final String[] BANNER = { "", |
可以看到之前我们没有配置banner.txt的时候打印就是这个”spring”输出,即输出了BANNER数组。
createApplicationContext()方法
一个用来创建上下文的策略方法。
1 | protected ConfigurableApplicationContext createApplicationContext() { |
prepareContext(context, environment, listeners, applicationArguments,printedBanner)方法
1 | private void prepareContext(ConfigurableApplicationContext context, |
refreshContext()刷新上下文
此方法按照规定的顺序执行, 这个方法做的事情非常多,不会过多介绍, 最终走到AbstractApplicationContext的refresh()方法:
1 | public void refresh() throws BeansException, IllegalStateException { |
afterRefresh(context, applicationArguments)方法
这个方法没有做任何事情,目的是为了留给子类继承的想象空间的。
stopWatch.stop()秒表计时结束
不做过多解释。
打印启动信息
listeners.started(context)
触发事件,上下文已经被刷新,应用已经启动,但是命令行运行器和应用运行器还没有被调用
callRunners()方法
看一下它的实现:
1 | private void callRunners(ApplicationContext context, ApplicationArguments args) { |
ApplicationRunner
Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple ApplicationRunner beans can be defined within the same application context and can be ordered using the Ordered interface or @Order annotation.
用于标示一个bean应该被运行,当它包含在一个SpringApplication当中,一个应用上下文可以有多个ApplicationRunner,可以使用Ordered接口或者@Order
直接进行排序。
1 | @FunctionalInterface |
CommandLineRunner
Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the Ordered interface or @Order annotation.
If you need access to ApplicationArguments instead of the raw String array consider using ApplicationRunner.
用于标示一个bean应该被运行,当它包含在一个SpringApplication当中,一个应用上下文可以有多个CommandLineRunner,可以使用Ordered接口或者@Order
直接进行排序。
如果你想访问ApplicationArguments而不是原生的字符串数组,请使用ApplicationRunner。
1 | @FunctionalInterface |
listeners.running(context)
run方法调用完毕之前立即会被触发和调用,应用上下文已经被刷新,CommandLineRunner和ApplicationRunner已经被调用。
结束
当listeners.running(context)调用完毕之后run也就结束了,run方法返回一个ConfigurableApplicationContext,会返回到我们的main方法,到此应用的启动过程分析完毕。
1 | @SpringBootApplication |