上节在spring容器实现详解一我们已经做好了加载资源前的一些准备工作,如:将我们的xml配置转成spring认可 的Resource,还有需要的加载器等.接下来我们来看看是如何加载的,来看代码:
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
这段代码我们完成了一半的操作,当new XmlBeanFactory时的条件,接下来看XmlBeanFactory做了什么操作,代码入下:
public class XmlBeanFactory extends DefaultListableBeanFactory {
//很重要的一个属性,实质上所有的加载读取操作都在XmlBeanDefinitionReader中完成
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* Create a new XmlBeanFactory with the given resource,
* which must be parsable using DOM.
* @param resource the XML resource to load bean definitions from
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
* @param resource the XML resource to load bean definitions from
* @param parentBeanFactory parent bean factory
* @throws BeansException in case of loading or parsing errors
*/
/**parentBeanFactory为父BeanFactory,一般用于factory的合并,所以可以为null*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
//该方法是加载资源的真正入口
this.reader.loadBeanDefinitions(resource);
}
}
在上述的代码中,两个方法用来初始化操作的,只不过第一个调用了内部的,所需参数不一样,其他的都是扯淡,this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现,来看它之前我们先来了解下它调用父类的方法到底干了什么,代码如下:
/**
* Create a new DefaultListableBeanFactory with the given parent.
* @param parentBeanFactory the parent BeanFactory
*/
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
super(parentBeanFactory);
}
Dbug来到他的爸爸DefaultListableBeanFactory,本来XmlBeanFactory就是DefaultListableBeanFactory的一个拓展bean工厂,针对于xml配置的bean的实现,扯远了,这里我们还是没看到想要的,接着往下走,前方高能:
/**
* Create a new AbstractAutowireCapableBeanFactory with the given parent.
* @param parentBeanFactory parent bean factory, or {@code null} if none
*/
public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
this();
setParentBeanFactory(parentBeanFactory);
}
我们来到了AbstractAutowireCapableBeanFactory的初始化方法,看着都是一个吊玩意,这里调用了内部无参的构造方法# this(),我们来看:
/**
* Create a new AbstractAutowireCapableBeanFactory.
*/
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
- ignoreDependencyInterface的主要作用是可以忽略给定接口的自动装配功能.
简单的举个案例:
当A中拥有一个属性B,如果Spring在获取A时,如果属性B还没有初始化,此刻spring会自动初始化B,这个特性也是spring中很重要的一个特性.当然有利也有b
存在一种可能性,属性B是不会初始化,B实现了BeanNameAware接口时.
上面的那个构造方法中还调用了父类的,可能大家很好奇,算了,满足一下好奇心,其实我也很好奇的,直接看代码:
/**
* Create a new AbstractBeanFactory.
*/
public AbstractBeanFactory() {
}
没错这是顶级父类AbstractBeanFactory的实现方法,就是这个鸟玩意.咦,我们在上一个方法还看到了一个AbstractAutowireCapableBeanFactory#setParentBeanFactory()方法,需要一个factory作为参数,这个方法用来干嘛的来看:
public void setParentBeanFactory(@Nullable BeanFactory parentBeanFactory) {
if (this.parentBeanFactory != null && this.parentBeanFactory != parentBeanFactory) {
throw new IllegalStateException("Already associated with parent BeanFactory: " + this.parentBeanFactory);
}
this.parentBeanFactory = parentBeanFactory;
}
该方法是AbstractBeanFactory#setParentBeanFactory()设置值的一个属性方法,因为在他的内部有这样一个属性:
/** Parent bean factory, for bean inheritance support. */
@Nullable
private BeanFactory parentBeanFactory;
我们终于可以回归正规了,看this.reader.loadBeanDefinitions(resource)该方法到底干了什么,直接看代码:
/**
* 该方法主要是从xml中加载bean的定义
* @param resource 对我们xml文件的封装和描述
* @return 返回 bean definition的个数
* @throws BeanDefinitionStoreException
*/
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
- 该方法是XmlBeanDefinitionReader#loadBeanDefinitions,主要的作用是加载bean定义的数量.
- 其次该方法还调用了内部的#loadBeanDefinitions()的方法.
- 更重要的是该方法需要EncodedResource类或者是它的实现作为方法的参数
大致我们可以从代码中推断出EncodedResource类是对我们resource进行编码方面的封装,这里只是推测,具体如何接着来看:
public EncodedResource(Resource resource, @Nullable String encoding) {
this(resource, encoding, null);
}
上述方法是EncodedResource类中,这里调用了一个内部的初始化方法,来看:
* Create a new {@code EncodedResource} for the given {@code Resource},
* using the specified {@code encoding}.
* @param resource the {@code Resource} to hold (never {@code null})
* @param encoding the encoding to use for reading from the resource
*/
public EncodedResource(Resource resource, @Nullable String encoding) {
this(resource, encoding, null);
}
方法l简单,一看一目了然,只是这里有一次的调了三个参数的初始化方法,来看:
/**
* Create a new {@code EncodedResource} for the given {@code Resource},
* using the specified {@code Charset}.
* @param resource the {@code Resource} to hold (never {@code null})
* @param charset the {@code Charset} to use for reading from the resource
*/
public EncodedResource(Resource resource, @Nullable Charset charset) {
this(resource, null, charset);
}
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
这里才是真正的初始化操作,进行相关的赋值操作,顺便看一下EncodedResource的属性和重要的方法:
属性
public class EncodedResource implements InputStreamSource {
private final Resource resource; //资源(封装我们自己的xml之后的抽象类)
@Nullable
private final String encoding;//resource中的编码
@Nullable
private final Charset charset;//也是从resource读取到的字符集如:utf-8等一般是是他
这就是EncodedResource的属性,我们看到它也实现了InputStreamSource该接口,还记得该接口中是有一个很重要的方法InputStream #getInputStream() ,这个方法主要是读取我们封装资源中的内容
方法
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
- 上述方法主要是构造了一个有编码的InputStreamReader.
- 当创建了一个EncodedResource对象后,来到了我们的核心方法
- loadBeanDefinitions((new EncodedResource(resource))
- 即所有之前的操作都是为了给此方法做铺垫,也就是说我们前方高能,请看:
/***
* 该方法还是返回解析后的bean definition的个数,不同的是用指定的编码去解析
* @param encodedResource
* @return
* @throws BeanDefinitionStoreException
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
//获取已经加载过的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
//将当前资源加载进去,如果当前资源在currentResources存在,就抛出如下异常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//这里分了二步走:
//1.首先调用EncodedResource#getResource去获取封装的资源resource,这里拿到的是classPathResource对象
//1.1. 然后在调用Resource#getInputStream获取一个inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//1.通过获取到的inputStream构建一个InputSource对象
//2.其中inputStream为BufferedInputStream 为字节流
//3.主要调用InputSource#setByteStream(byteStream);进行保存
InputSource inputSource = new InputSource(inputStream);
//保存对应的编码
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//这里才是加载资源的真正的核心逻辑处理操作
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
//关闭输入流
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
//从currentResources移除encodedResource
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
上述方法是XmlBeanDefinitionReader#loadBeanDefinitions(),简单的总结一下该方法的主要作用:
- 首先是对我们的xml文件的封装成resource资源,然后对其编码的设置
- 来到该方法中,resourcesCurrentlyBeingLoaded中获取所有的资源,其主要的目的是跟我们当前的资源对比.
- 如果我们当前的资源存在于currentResources中就抛异常,反之保存
- 然后去获取一个字节流inputStream,详细过程上述代码中已说明
- 设置对应的编码
- 然后调用真正的和核心加载资源的方法doLoadBeanDefinitions().
- 于此关闭输入流
- 最后从currentResources移除encodedResource
接下来我们看真正的核心加载资源的过程
/**
* Actually load bean definitions from the specified XML file.
* @param inputSource the SAX InputSource to read from
* @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
* @see #doLoadDocument
* @see #registerBeanDefinitions
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//获取一个Document实例对象
Document doc = doLoadDocument(inputSource, resource);
//根据我们获取到的Document实例注册bean信息
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
- 上述代码看似很长,实际就做了获取了一个Document实例,然后根据我们获取的Document实例向注册器中注册bean的信息.
- 我们看到在获取Document实例时,调用了xmlBeanDefinitionReader#doLoadDocument方法.我们来看一下这个方法:
/***
* 该方法的主要作用是使用默认的documentLoader去加载文档
* @param inputSource 是一个SAX 格式的xml InputSource从这里读取
* @param resource 我们设置了的有编码的资源文件
* @return Document实例
* @throws Exception
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
上述是xmlBeanDefinitionReader#doLoadDocument方法,我们看到该方法中:
1.调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式.
- 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例.
根据我们的Decument实例去注册bean的信息,调用了xmlBeanDefinitionReader#registerBeanDefinitions()方法,关于上面这些过程在下面的文章会继续分析.最后分享自己画的一张时序图:
转载于:https://www.jianshu.com/p/908a89cb5f21