spring容器实现之资源加载

上节在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());
    }
}
  1. 上述方法主要是构造了一个有编码的InputStreamReader.
  2. 当创建了一个EncodedResource对象后,来到了我们的核心方法
  3. loadBeanDefinitions((new EncodedResource(resource))
  4. 即所有之前的操作都是为了给此方法做铺垫,也就是说我们前方高能,请看:
/***
 * 该方法还是返回解析后的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);
    }
}
  1. 上述代码看似很长,实际就做了获取了一个Document实例,然后根据我们获取的Document实例向注册器中注册bean的信息.
  2. 我们看到在获取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)的验证模式.

  1. 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例.

根据我们的Decument实例去注册bean的信息,调用了xmlBeanDefinitionReader#registerBeanDefinitions()方法,关于上面这些过程在下面的文章会继续分析.最后分享自己画的一张时序图:

3711017-867408af4d3da779.png
xmlBeanFactory的初始化时序图.png

转载于:https://www.jianshu.com/p/908a89cb5f21

猜你喜欢

转载自blog.csdn.net/weixin_34088583/article/details/91252280