在使用 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 来对其进行表达的,类图结构如下:
在对 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 的加载链路给捋清楚了,真正解析逻辑以及注册留在下一篇博文。最后,用一张时序图来总结下整个链路。