Spring的启动过程,就是其IoC容器的启动过程,本质就是创建和初始化bean的工厂(BeanFactory),BeanFactory其实就是整个SpringIoc的核心,Spring 使用 BeanFactory 来实例化、配置和管理 Bean。
对于web程序,IoC容器启动过程即是建立上下文的过程,在web应用中,web容器会提供一个全局的ServletContext上下文环境,ServletContext上下文为Spring IoC提供了一个宿主环境。
Spring应用在Web容器中启动的整个过程如下:
先介绍下源码中出现的各位小伙伴:
- Resource : 是spring对于资源的一种抽象,因为资源的来源可能很丰富,利于File,Class Path Resource,Url Resource等,进行统一封装,暴露出getInputStream进行统一读取解析
- Document : 这个没啥好讲的,XML文档对象
- EncodedResource :封装了Resource,指定Resource的编码
- ReaderContext :Bean Definition解析过程中的上下文对象,封装了Resource、ProblemReporter、EventListener、SourceExtractor等
- Element :XML中的元素节点对象
- BeanDefinition :这个接口及其实现类非常重要,他描述了XML中一个bean节点及其子节点的所有属性,将xml中的描述转变成内部的field信息,举例:scope,lazyinit,ConstructorArgumentValues(描述构造器),PropertyValues(描述属性值)等,是一个保罗万象的接口,其子类实现包括GenericBeanDefinition、RootBeanDefinition、ChildBeanDefinition等
- BeanDefinitionHolder : 顾名思义包含了一个BeanDefinition,同时其包含了beanName和aliases,更好的封装了一次
- BeanDefinitionReader : 定义了读取BeanDefinition的接口,主要作用是从Resource中读取Bean定义,XmlBeanDefinitionReader是其具体的实现类
- BeanDefinitionDocumentReader :定义了从Document对象中解析BeanDefinition并且注册到Registry中设计到的接口,其默认实现类是DefaultBeanDefinitionDocumentReader,主要是被XmlBeanDefinitionReader委派去处理Document对象
- BeanDefinitionParserDelegate:是用于最终处理XML bean定义的人,它做的可都是脏活累活,import/alias/bean等element以及element的子节点以及属性都是它解析并且填充到BeanDefinition中然后使用ReaderContext中的Registry(实际就是DefaultListableBeanFactory)来将该BeanDefinition注册
在Web项目使用Spring,是通过在web.xml里面配置:
org.springframework.web.context.ContextLoaderListener
来初始化IOC容器的。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
确切的说,ContextLoaderListener这个监听对象,监听的是ServletContext这个,当web容器初始化,ServletContext发生变化的时候,会触发相应的事件。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
ContextLoaderListener继承了ContextLoader,并实现了ServletContextListener接口,在web容器初始化的时候,会触发ServletContextListener接口中的contextInitialized()方法,同理,在容器关闭的时候,会触发对应的contextDestroyed()方法。
其中,initWebApplicationContext()方法为父类ContextLoader中的方法:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
由于initWebApplicationContext()方法源码较长,我们进行拆分阅读。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
//日志记录
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
//启动startTime,记录启动耗时
long startTime = System.currentTimeMillis();
首先判断是否创建了WebApplicationContext,正常情况下创建了一个WebApplicationContext后,会把context set到ServletContext中,setAttribute的本质其实是往LinkedHashMap中set值。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
如果从servletContext中根据key获得了WebApplicationContext对象,则表示之前已经创建过了根上下文WebApplicationContext,此时抛出异常,提示检查web.xml中的配置,避免重复创建root上下文,保证只有一个Spring容器。
再看下面的代码:
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
代码片段较长,一步一步来,try中有俩关键的地方:
createWebApplicationContext()和configureAndRefreshWebApplicationContext()
先逐步往下看
首先是判断 this.context == null,通过createWebApplicationContext方法创建一个
WebApplicationContext,具体代码如下:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
determineContextClass,字面意思为检测Context的class类型,会读取servletContext的初始化参数contextClass,大部分情况下我们不会配置此参数,在未配置的情况下,Spring会去org.springframework.web.context包中的ContextLoader.properties配置文件读取默认配置:
通过Spring提供的ClassUtil进行反射,反射出XmlWebApplicationContext类,再通过BeanUtils进行反射,调用无参构造器,instance出一个WebApplicationContext并返回ConfigurableWebApplicationContext。
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
获得WebApplicationContext后,下一步判断获得的context是否为
ConfigurableWebApplicationContext的实例,默认的XmlWebApplicationContext满足判断条件。
判断父上下文是否为active状态,如果active为false下,需要判断父上下文是否为null,如果parent上下文为null的情况,则执行loadParentContext()。loadParentContext()方法,是一个默认的是模板实现方法,用于加载/获取ApplicationContext,此context为根WebApplicationContext的父上下文。
下一步,进入configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
将上面创建的XmlWebApplicationContext进行初始化操作。
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
主要创建一个默认id,org.springframework.web.context.WebApplicationContext
:+项目的ContextPath
//设置sc到wac中,便于Spring获得ServletContext
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
创建好id后,把servletContext放入创建好的XmlWebApplicationContext中,便于spring后续获得servletContext上下文。
紧接着会去读取web.xml中contextConfigLocation的配置
如果在web.xml中未配置,则会去读取WEB-INF下面的applicationContext.xml配置文件,
即XmlWebApplicationContext中的属性 DEFAULT_CONFIG_LOCATION的默认值
/** Default config location for the root context */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
读取到contextConfigLocation相关文件配置路径后,设置到XmlWebApplicationContext中,用于加载指定路径的配置文件。
下一步 customizeContext(sc, wac);
主要用于自定义context相关配置,比如定义bean是否可以循环引用,bean定义是否可以被覆盖等,通常情况下不做任何操作。
最后一步:
wac.refresh();
整个容器启动的最核心方法,在这个refresh()方法中,会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作。
先看下refresh()方法的说明。
refresh()方法是ConfigurableApplicationContext接口提供的方法,refresh()方法的具体实现是由
AbstractApplicationContext这个抽象类实现的。
AbstractApplicationContext这个抽象类继承了DefaultResourceLoader,并且实现了接口
ConfigurableApplicationContext
废话不多,上源码
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
显而易见的,refresh()中是一段同步代码块,this.startupShutdownMonitor对象是一个Object对象,主要作用其实就是 用于“刷新”和“销毁”的同步监视器,换句话说,就是在refresh()和close()两个方法中,都会同步此对象,用来保证统时间只有一个线程执行刷新或者关闭操作。
1. prepareRefresh()
主要就是一些准备操作,记录context的startup时间,设置closed为false,active为true,其中,设置active为true这一步,对应在ContextLoader中的initWebApplicationContext方法,用于判断当前context对象是否被刷新过。
回到refresh方法中,接下来是obtainFreshBeanFactory()方法。
2.obtainFreshBeanFactory()
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
obtainFreshBeanFactory()方法是整个refresh()方法的核心,主要做完成BeanFactory的创建,bean配置文件的加载,解析和注册。
来看obtainFreshBeanFactory的实现
第一步refreshBeanFactory
顾名思义,刷新beanFactory,在刷新的时候,判断是否已经有beanFactory了,如果有相关beanFactory,则destory所有单利的bean,并且关闭beanFactory,在关闭了原来旧的beanFactory后,再创建一个新的beanFactory,创建了新的beanFactory后,完成加载bean的配置文件,解析bean等一系列相关操作。
先进入hasBeanFactory()
又是一段同步代码块,有没有很熟悉,又是同步了this.beanFactoryMonitor,用于判断当前是否有已经持有了一个bean factory,并且至少已经被刷新了一次,未被关闭的。
如果返回有bean factory,则需要destory所有单例的bean,因为bean factory只能管理单例的bean,非单例的bean不归bean factory管,所以只destory了所有单利的bean,并且关闭了bean factory。
在关闭了bean factory后,再次创建的factory是 DefaultListableBeanFactory,创建好factory后,设置序列号,个性化设置。
重点来了,
loadBeanDefinitions(factory);
这个方法,主要做了加载bean到factory中
通过XmlBeanDefinitionReader进行加载bean的定义,进入loadBeanDefinitions方法,里面调用的层数很多,大部分都是@Override的方法,主要就是用来加载Spring的配置文件,大部分情况下是多个,加载好配置文件后用于后面的解析。
最后真正执行加载配置文件xml的地方,是在XmlBeanDefinitionReader里的loadBeanDefinitions()方法中
先通过InputStream读取当前已经被加载的配置文件,读取好后再通过
doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法进行加载,进入doLoadBeanDefinitions方法
可以看出,在doLoadBeanDefinitions方法中,try代码块中第一行方法主要做的就是将配置文件加载成Document类型,具体怎么处理的这里先不细说。真正在执行bean加载的,就是第二行的方法。
进入registerBeanDefinitions(doc, resource)方法
显而易见的,方法中的第三行代码,进行了bean的加载和注册
继续进入doRegisterBeanDefinitions(root)
方法里面先是创建了一个BeanDefinitionParserDelegate,具体怎么解析xml的,这里先跳过,我们的目标是知道怎么加载bean的。
看parseBeanDefinitions(root, this.delegate)这个方法
方法上的注释说的很明白:解析document中root level(import,alias,bean标签)的element。
先判断是否为defaultNameSpace,通过判断element的父类Node中的NamespaceURI,是否与创建的
BeanDefinitionParserDelegate中的属性BEANS_NAMESPACE_URI中的值是否相等。
先贴上配置文件和路径,结合配置文件说会比较清楚。
resources下面两个目录,一个config,一个spring
在启动的时候,debug到上面的parseBeanDefinitions方法中,我们先看看下delegate中读取的是哪个配置文件:
很明显,先读取了spring文件夹下面的servlet-context.xml配置文件。
再看servlet-context.xml配置文件中的内容。
首先配置的是配置文件注入的bean,注意上面的注释,在解析的时候也会被读取,但是由于不是Element的实例,所以不会进行处理。
再下一步继续debug
进入parseDefaultElement(ele,delegate)方法,
判断delegate中node的name,是否为root level的name标签(import,alias,beans,bean)
由于是ele是bean标签,所以会进入第二个elseif delegate.nodeNameEquals(ele, BEAN_ELEMENT)判断
再走下一步看:
方法中的第一行,就是把ele标签中的配置,解析成一个BeanDefinitionHolder
解析完成后,我们可以看到,整个BeanDefinitionHolder对象中,属性beanName已经有值了,对应的就是上面servlet-context.xml中bean标签的id里面的值,beanDefinition中就是bean的所有定义,包括bean的class,是否singleton,是否lazy-init等属性配置。
再往下走,进入try中的registerBeanDefinition这个方法,主要就是注册当前bean到当前的beanfactory中。
继续深入registerBeanDefinition(String beanName, BeanDefinition beanDefinition),代码较长==
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
看最后几行代码,
往beanDefinitionMap这个map中放值,key是beanName,value是beanDefinition
往beanDefinitionNames这个arrayList中add beanName,看一下这个beanDefinitionNames的注释:
根据注册顺序,往list中add name。
add完bean的name和definition后,就返回了,回到parseBeanDefinitions继续解析下一个doc。
继续看配置文件:
再解析完bean标签后,跳过注释,下一个标签是context:component-scan这个指定扫描包的标签,此标签不是defaultNamespace,所以会跳转到else中的parseCustomElement(ele)方法中。
进入parseCustomElement(ele)中,
首先根据当前element元素的namespaceURI,获得对应的handler,其实就是根据不同的xml标签,找出合适的BeanDefinitionParser。
由于我们在xml配置文件中配置了context:component-scan,可以看到,component-scan对应的handler
其实是 org.springframework.context.annotation.ComponentScanBeanDefinitionParser
进入ComponentScanBeanDefinitionParser的parse()方法
basePackage的值其实就是xml中base-package后面我们填写的包名。
再往下,创建了一个ClassPathBeanDefinitionScanner,注释说的很清楚,扫描bean的定义然后注册他们,注意:这里是类文件而不是类,因为现在这些类还没有被加载,只是ClassLoader能找到这些class的路径而已
感觉离真相越来越近了。。。继续
进入scanner.doScan()看看,是怎么扫描的
如果basePackages为空,提示至少需要指定一个base-package
进入for循环,
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
这一句,意思是根据basePackage找到所有的包中的类,当做候选人。
再进去look look里面干了什么
又是一大坨代码,是不是有点晕啊
一句一句来分析,先看这一句
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
好像不太明白啥意思,首先假设我们在xml中的base-package写的是"com.abc.efg",那么,
packageSearchPath=classpath*:com.abc.efg//.class,意思就是com.abc.efg包下面的所有class,包括子包中的class。如果base-package写的是"",那就是classpath*😗//*.class,就是全部的class了。
获得到packageSearchPath后,再去获得Resources
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
resourcePatternResolver是一个interface,里面的getResources(String locationPattern)方法,这里使用的实现类是PathMatchingResourcePatternResolver,Spring3的版本中可以看到直接声明:
Spring4.3.6中移除了后面的new实例化。resolver中本质还是通过AntPathMatcher进行匹配。
进入getResources(packageSearchPath);
首先会判断locationPattern是否以"classpatch:"开头,上面spring已经替我们加了这个前缀, 所以这里符合条件
下一步进入findPathMatchingResources(),
假设我们的locationPattern是:classpath:com/abc/def/**/*.class
determineRootDir(locationPattern)方法做的就是获得根文件夹目录,去掉/**/*.class
则rootDirPath=classpath*:com/abc/def/
subPattern=**/*.class
继续下一步,回到getResources(rootDirPath)方法,此时依旧符合以classpath*开头,但是不满足if判断条件,进入else逻辑:
继续下一步:
zzz,进入find方法后,location=“com/abc/def”,
下面的ClassLoader cl = getClassLoader()
说明了Spring也是通过用的ClassLoader加载的class文件。
到此为止,就获得了bean的class文件了,最终spring会将class封装成beanDefinition。
回到doScan中,
最终,又回到了registerBeanDefinition(definitionHolder, this.registry);就是本文上面说把beanName放入DefaultListableBeanFactory中的beanDefinitionMap里面。
到这一步,spring已经获得了bean的class文件了,具体spring是怎么实例化bean的呢
这里继续跳转到另一篇专门的博客中。