web.xml中有这么一段声明

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener类是启动的开始点,观察
ContextLoaderListener类的实现,如下:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
} public ContextLoaderListener(WebApplicationContext context) {
super(context);
} public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
} public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}

利用ServletContextListener接口监听到启动事件,调用 ContextLoader.initWebApplicationContext方法完成启动

过程在 ContextLoader.initWebApplicationContext事件中,如下:

try {
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
} if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext); //跟踪出来此块为空,即父容器默认空
cwac.setParent(parent);
} this.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;
} catch (RuntimeException var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
} catch (Error var9) {
logger.error("Context initialization failed", var9);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
throw var9;
}

重点方法有两个

1:createWebApplicationContext

通过反射,构造一个ConfigurableWebApplicationContext类的实例,即WebApplicationContext类实例

2:configureAndRefreshWebApplicationContext

源码如下:


protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        wac.setServletContext(sc);
configLocationParam = sc.getInitParameter("contextConfigLocation");
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
} ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
} this.customizeContext(sc, wac);
wac.refresh();

将第一步创建的ConfigurableWebApplicationContext,以及环境上下文ServletContext传入进来

获取xml的配置文件,即ioc的application.xml配置文件,以及初始化容器属性参数

重点在ConfigurableWebApplicationContext的refresh()方法

实现如下:


代码在AbstractApplicationContext类中,
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh(); //准备初始化

ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); //该方法调用了beanFactory的创建,DefaultListableBeanFactory beanFactory = this.createBeanFactory(),
即实例化的是DefaultListableBeanFactory类,该方法源码在后面
以及定义了XmlBeanDefinitionReader,该XmlBeanDefinitionReader获取(application.xml)即spring web的配置文件,
this.prepareBeanFactory(beanFactory);//通过上下文初始化beanFactory try {
this.postProcessBeanFactory(beanFactory); //
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
} this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
} }
}

次方法是同步的,避免重复刷新,每个步骤都放在单独的方法内,流程清晰,是值得学习的地方。这里面有个重要的方法是finishBeanFactoryInitialization(beanFactory);,里面的内容是Spring如何实例化bean,并注入依赖的,这个内容下一节讲,本节只说明Spring是如何加载class文件的。

首先就是prepareRefresh()方法。

protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis(); synchronized (this.activeMonitor) {
this.active = true;
} if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
} // Initialize any placeholder property sources in the context environment
initPropertySources(); // Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
}

此方法做一些准备工作,如记录开始时间,输出日志,initPropertySources();getEnvironment().validateRequiredProperties();一般没干什么事。

接下来就是初始化BeanFactory,是整个refresh()方法的核心,其中完成了配置文件的加载、解析、注册

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  

看看它里面都做了些什么?

  1. protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory();
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
    logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
    }

首先refreshBeanFactory()

我们看到会创建一个DefaultListableBeanFactory实例

  1. DefaultListableBeanFactory beanFactory = createBeanFactory();

再设置一个ID

  1. beanFactory.setSerializationId(getId());

然后设置一些自定义参数:

  1. customizeBeanFactory(beanFactory);

这里面最重要的就是loadBeanDefinitions(beanFactory);方法了。

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

此方法会通过XmlBeanDefinitionReader加载bean定义。具体的实现方法是在org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions方法中定义的。这里设计了层层调用,有好多重载方法,主要就是加载Spring所有的配置文件(可能会有多个),以备后面解析,注册之用。我一路追踪到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)

protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}

这里创建了一个BeanDefinitionParserDelegate示例,解析XML的过程就是委托它完成的,我们不关心它是怎样解析XML的,我们只关心是怎么加载类的,所以就要看parseBeanDefinitions(root, this.delegate)方法了。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

我们看到最终解析XML元素的是delegate.parseCustomElement(ele)方法,最终会走到一下方法.

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

这里会根据不同的XML节点,会委托NamespaceHandlerSupport找出合适的BeanDefinitionParser,如果我们配置了

那么对应BeanDefinitionParser就是org.springframework.context.annotation.ComponentScanBeanDefinitionParser,来看看它的parse方法。

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null;
}

不难看出这里定义了一个ClassPathBeanDefinitionScanner,通过它去扫描包中的类文件,注意:这里是类文件而不是类,因为现在这些类还没有被加载,只是ClassLoader能找到这些class的路径而已。到目前为止,感觉真想距离我们越来越近了。顺着继续往下摸。进入doSacn方法里,映入眼帘的又是一大坨代码,但是我们只关心观点的部分。

一眼就能看出是通过

Set<BeanDefinition> candidates = findCandidateComponents(basePackage);  

有时候不得不佩服这些外国人起名字的功力,把扫描出来的类叫做candidates(候选人);真是不服不行啊,这种名字真的很容易理解有不有?哈哈,貌似扯远了。继续往下看。这里只列出方法的主题部分。

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);

先看这两句:

String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + this.resourcePattern;  

假设我们配置的需要扫描的包名为com.geeekr.service,那么packageSearchPath的值就是classpath*:com.geeekr.service/**/*.class,意思就是com.geeekr.service包(包括子包)下所有class文件;如果配置的是*,那么packageSearchPath的值就是classpath*:*/**/*.class。这里的表达式是Spring自己定义的。Spring会根据这种表达式找出相关的class文件。

Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);  

这一句就把相关class文件加载出来了,那我们就要看看,Spring究竟是如何把class文件找到的了。首先看看resourcePatternResolver的定义:

private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();  
 

进入getResources方法

@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}

这里会先判断表达式是否以classpath*:开头。前面我们看到Spring已经给我们添加了这个头,这里当然符合条件了。接着会进入findPathMatchingResources方法。在这里又把**/*.class去掉了,然后在调用getResources方法,然后在进入findAllClassPathResources方法。这里的参数只剩下包名了例如com/geeekr/service/

真相大白了,Spring也是用的ClassLoader加载的class文件。一路追踪,原始的ClassLoader是Thread.currentThread().getContextClassLoader();。到此为止,就拿到class文件了。
Spring会将class信息封装成BeanDefinition,然后再放进DefaultListableBeanFactorybeanDefinitionMap中。

拿到了class文件后,就要看看Spring是如何装配bean的了,下一节,继续看。

最新文章

  1. Windows 2012 R2图标以及字体颜色发生变化更改成默认设置
  2. [QoS]cisco3560限速配置案例-收集于网工泡泡
  3. 解决 MVC 用户上线下线状态问题
  4. ERwin 连接 mysql
  5. android通讯录导航栏源码(一)
  6. highcharts:根据Y的数值范围,动态改变图形的填充颜色
  7. EF Dal通用类
  8. 寻找两个已序数组中的第k大元素
  9. MySQL入门笔记(一)
  10. linux下的Shell编程(5)循环
  11. java.io.FileNotFoundException关于使用Intellij Idea时系统找不到指定文件的解决方案
  12. (Bash博弈)51nod1067 Bash游戏 V2
  13. Linux_CentOS-服务器搭建 &lt;一&gt;
  14. 工程设计文档服务EngineerCMS
  15. 1-scala基础
  16. Grafana elasticsearch 应用
  17. div 内table 居中实现代码
  18. activity生命周期实例(不同启动模式)
  19. java 表单验证
  20. Android Intent 总结

热门文章

  1. C# RSA的加解密与签名验证
  2. Mycat实战之离散分片
  3. hadoop文件写入
  4. 微信小程序中出现Invoking Page() in async task.问题
  5. 【Dubbo学习】
  6. 下载并保存图片Python2.7
  7. java Web servlet简介及其生命周期
  8. Linux服务器防火墙白名单设置
  9. selector在color和drawable目录下的区别
  10. Chrome OS上可运行Linux