在使用 Spring 的过程中,不知道大家有时候是否像我有一样的疑问,都说 Spring 主要提供两大机制:IoC 容器AOP 编程,而 IoC 容器是根本,提供控制反转的功能,我们在使用的过程中只管声明 bean 或使用注解的方式,IoC 容器就为我们管理这些对象,并且帮我注入对象依赖,那么这一切都是怎么做到的呢?既然有这样的疑问,那就得去弄明白,而想明白 IoC 容器的原理,首先就得需明白 Spring 是怎么加载我们声明的 bean,所以通过这篇文章来捋捋 Spring 加载 bean 的原理。
一个使用 Spring 较为简单的方式就是通过 ClassPathXmlApplicationContext 来启动 Spring。

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
}

接下来就探索下这样简单的一条语句背后下到底做了什么事情。

/**
 * Create a new ClassPathXmlApplicationContext, loading the definitions
 * from the given XML file and automatically refreshing the context.
 */
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
	this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {
	// 调用父类 AbstractApplicationContext 的构造函数,设置父容器以及
	// 初始化路劲解析策略: PathMatchingResourcePatternResolver
	super(parent);
	// 设置 configLocations,因为有可能我们传的路径存在占位符,需要解析,因此此时
	// 会创建 Environment 对象,具体为 StandardEvironment
	setConfigLocations(configLocations);
	// refresh 默认为 true
	if (refresh) {
		// 刷新容器
		refresh();
	}
}

**PS:**可以得知默认父容器是为 null
上面方法最核心的就是 refresh() 这行代码了,所以看看你 AbstractApplicationContext 类的 refresh 具体做了哪些事情。

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// 准备阶段:初始化 PropertySource;验证必须要的属性是否存在
		prepareRefresh();

		// 委托子类刷新 BeanFactory
		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();
		}
	}
}

在只要加载 bean 定义的原理时,对 refresh() 方法内部对其他方法的调用可以先不深入了解,目前只需要对 AbstractRefreshableApplicationConttext 类的obtainFreshBeanFactory() 深入了解下,因为这个方法内部会有一行代码去做我们现在想要知道的事情。

/**
 * This implementation performs an actual refresh of this context's underlying
 * bean factory, shutting down the previous bean factory (if any) and
 * initializing a fresh bean factory for the next phase of the context's lifecycle.
 */
protected final void refreshBeanFactory() throws BeansException {
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());
		// 对 BeanFactory 进行自定义配置,如是否可以重写 bean 定义,是否允许循环引用
		customizeBeanFactory(beanFactory);
		// 加载 bean definition
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

可以看到,此方法除了创建 BeanFactory 外,还有一行代码值得我们关注,那就是 loadBeanDefinitions(beanFactory),这行代码做的事情就是去加载 bean 的定义。不知道大家有没有想过 Spring 内部是怎么表达我们在 xml 文件中声明的 bean 的信息。没错,Spring 就是使用了 BeanDefinition 来对其进行表达的,类图结构如下:

spring bean加载规则 spring bean加载原理_spring


在对 BeanDefinition 类图有个简单了解下之后,我们看看 AbstractXmlApplicationContext 类的 loadBeanDefinitions() 方法。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// 为指定的 BeanFactory 创建 XMLBeanDefinitionReader
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	// 配置 BeanDefinitionReader
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
 
 	// 钩子方法,让子类有机会对 BeanDefinitionReader 进行定制化
	initBeanDefinitionReader(beanDefinitionReader);
	// 加载 BeanDefinition
	loadBeanDefinitions(beanDefinitionReader);
}

可以看到其实此方法并没有去加载 BeanDefinition,而是对 BeanDefinitionReader 进行设置和定制化,如果此方法名为 initReader() 或者其他的可能更合适。那我们就在看看接下来的 loadBeanDefinitions() 又做了啥。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
	// 默认为 null,子类可以重写
	Resource[] configResources = getConfigResources();
	if (configResources != null) {
		reader.loadBeanDefinitions(configResources);
	}
	
	// 获取传递给 ClassPathXmlApplicationContext 构造函数的参数,即配置文件
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		// 读取文件
		reader.loadBeanDefinitions(configLocations);
	}
}

此处还是没有真正看到加载 BeanDefinition 的逻辑,还是在做准备阶段,此时做的是获取配置信息源,然后根据不同的来源调用不同的重载方法,我们就已其中最常见的一种形式说明。

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
	Assert.notNull(locations, "Location array must not be null");
	int counter = 0;
	for (String location : locations) {
		// 加载 BeanDefinition
		counter += loadBeanDefinitions(location);
	}
	return counter;
}

因为用户可能为了更好的组织信息,对不同的配置信息放在不同配置文件中,因此需要循环的去加载每个文件中的 BeanDefinition。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
	// 默认为使用的容器,此时是 ClassPathXmlApplicationnContext
	ResourceLoader resourceLoader = getResourceLoader();
	if (resourceLoader == null) {
		throw new BeanDefinitionStoreException(
				"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
	}

	if (resourceLoader instanceof ResourcePatternResolver) {
		// Resource pattern matching available.
		try {
			// 解析配置文件路径为 Resource
			Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
			// 加载 BeanDefinition
			int loadCount = loadBeanDefinitions(resources);
			if (actualResources != null) {
				for (Resource resource : resources) {
					actualResources.add(resource);
				}
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
			}
			return loadCount;
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Could not resolve bean definition resource pattern [" + location + "]", ex);
		}
	}
	else {
		// Can only load single resources by absolute URL.
		Resource resource = resourceLoader.getResource(location);
		int loadCount = loadBeanDefinitions(resource);
		if (actualResources != null) {
			actualResources.add(resource);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
		}
		return loadCount;
	}
}

其实可以看到还是调用了参数为 Resource 的 loadBeanDefinitions() 方法。那么 Resource 到底是什么呢?

Spring 的配置文件读取是通过 ClassPathResource 进行封装的,如 new ClassPathResource(“spring.xml”),那么 ClassPathResource 完成了什么功能呢?

在 java 中,将不同来源的资源抽象成 URI,通过注册不同的 handler(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般 handler 的类型使用不同前缀(协议,Protocol)来识别,如 “file:”、“http:” 等,然而 URL 没有默认定义相对 Classpath 或 ServletContext 等资源的 handler,虽然可以注册自己的 URLStreamHandler 来解析特定的 URL 前缀(协议),然后这需要了解 URL 的实现机制,而且 URL 也没有提供一些基本的方法,因而 Spring 对其内部使用到的资源实现了自己的抽象结构:Resource 接口来封装底层资源。

public interface InputStreamSource { InputStream getInputStream() throws IOException; }public interface Resource extends InputStreamSource { boolean exists(); boolean isReadable(); boolean isOpen(); URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String var1) throws IOException; String getFilename(); String getDescription(); }

有了 Resource 接口便可以对所有资源文件进行统一处理。
接下来看看 XMLBeanDefinitionReader 的 loadBeanDefinitions() 方法。

/**
 * Load bean definitions from the specified XML file.
 * @param resource the resource descriptor for the XML file
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 */
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}

这里 EncodeResource 实现了 InputStreamSource,它是对 Resource 进行封装,设置文件编码。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	// 获取已经解析的资源
	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
	// 没有就进行初始化
	if (currentResources == null) {
		currentResources = new HashSet<EncodedResource>(4);
		this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
	// 如果当前资源已经被解析,就抛异常
	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}
	try {
		// 跟 xml 文件对应的输入流
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			// 把流封装为可以解析 xml 文件的 sax 输入源
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			
			// 真正加载 BeanDefinition 的代码入口
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		finally {
			inputStream.close();
		}
	}
	catch (IOException ex) {
		throw new BeanDefinitionStoreException(
				"IOException parsing XML document from " + encodedResource.getResource(), ex);
	}
	finally {
		currentResources.remove(encodedResource);
		if (currentResources.isEmpty()) {
			this.resourcesCurrentlyBeingLoaded.remove();
		}
	}
}

敲黑板啦,终于看到了真正解析 BeanDefinition 的代码入口了。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
	try {
		// 获取代表 xml 文件的 Document 对象(dom 解析 xml 文件)
		Document doc = doLoadDocument(inputSource, resource);
		// 解析 xml 文件并注册 BeanDefinition
		return registerBeanDefinitions(doc, resource);
	} catch (Exception ignored) {
		// 省略对各种异常的捕获
	}
}

到了这个地方才可以说是把 Spring 对 BeanDefinition 的加载链路给捋清楚了,真正解析逻辑以及注册留在下一篇博文。最后,用一张时序图来总结下整个链路。

spring bean加载规则 spring bean加载原理_spring bean加载规则_02