XmlBeanFactory解析xml源码分析(一)

spring的目的就是让我们的bean能成为一个纯粹的POJO,可以通过xml的配置形式解析并加载bean。可以通过XMLBeanFactory来加载

  XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));  
  MyTest myTest = (MyTest) xmlBeanFactory.getBean("myTest");

容器加载相关类

XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBcanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory 并实现了ConfigurableListableBeanFactory 以及BeanDefinitionRegistry接口。

DefaultListableBeanFactory结构图

简易执行过程

上述代码中XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"))执行的时序图如下:

xmlBeanFactory执行简易过程
  • new ClassPathResource是读取将指定的xml文件resource,通过classPathResource的方法getInputStream获取InputStream流
  • 实例化XmlBeanFactory,该类是解析xml的入口。并为属性reader赋值private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
  • 通过XmlBeanDefinitionReader的方法loadBeanDefinitions解析xml和实例化指定xml配置中的bean。

ClassPathResource.java

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

public interface InputStreamSource {
    //获取输入流
    InputStream getInputStream() throws IOException;
}

Resource抽象很多方法:是否存在、是否可读、是否打开、是否file、获取URL、获取URI、获取File等额外方法。

public interface Resource extends InputStreamSource {
    boolean exists();
    URL getURL();
    URI getURI()
    ...
}

对不同来源的资源文件都有相应的 Resource 实现 文件(FileSystemResource)、Classpath资源(ClassPathResource)、 URL 资源(UrlResource)、 InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。

ClassPathResource目的就是将在classPath下的spring-config.xml文件转化为对应的输入流InputStream。其ClassPathResource的构造函数实现比较简单。对自身的属性path和classLoader赋值。getInputStream方法获取InputStream就是通过classLoader来获取。

public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);//对path的处理并赋值自身属性path
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        this.path = pathToUse;  
        //classLoader传进来是为null。通过ClassUtils.getDefaultClassLoader()获取对应的类加载器。
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}

ClassUtils.getDefaultClassLoader()方法实现:首先通过线程上下文获取classLoader,如果失败,则通过ClassUtils.class获取classLoader;如果ClassUtils.class获取失败,则获取系统的classLoader

public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
      //通过上下文能获取classLoader
            cl = Thread.currentThread().getContextClassLoader();
        }
        catch (Throwable ex) {
            // Cannot access thread context ClassLoader - falling back...
        }
        if (cl == null) {
            //通过ClassUtils的class对象获取classLoader
            cl = ClassUtils.class.getClassLoader();
            if (cl == null) {
                // getClassLoader() returning null indicates the bootstrap ClassLoader
                try {
          //获取系统classLoader
                    cl = ClassLoader.getSystemClassLoader();
                }
                catch (Throwable ex) {
                    // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
                }
            }
        }
        return cl;
    }

对getInputStream()的实现有几个方式获取InputStream,优先级自上而下。通过属性的clazz的类加载来获取InputStream;通过属性classLoader来获取InputStream;如果clazz和classLoader为null,则通过系统类加载器获取InputStream。

public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            //通过自身属性clazz处理
            is = this.clazz.getResourceAsStream(this.path);
        }
        else if (this.classLoader != null) {
            //通过自身属性classLoader处理
            is = this.classLoader.getResourceAsStream(this.path);
        }
        else {
            //clazz和classLoader为null,通过系统类加载器获取
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
}

XmlBeanFactory.java

public class XmlBeanFactory extends DefaultListableBeanFactory {
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }
  //构造两数内部再次调用 内部构造函数
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }
}

this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现,是我们分析的重点之一,这里将xml解析document和注册实例bean操作。
从XmlBeanFactory的构造方法开始执行到XmlBeanDefinitionReader读取到Document以及注册实例bean的执行过程的时序图;这里没有展示如何读取Document以及如何通过document来注册和实例bean(下一章详细描述),实际读取xml获取document方式跟普通读取xml代码一样,spring并没有做何改变。

XmlBeanFactory.jpg
  • 1、2两步主要获取XmlBeanDefinitionReader实例,并通过XmlBeanDefinitionReader的loadBeanDefinitions方法来正真加载资源。
  • 3、组要将classPathResource封装EncodedResource,主要处理编码问题。在第4、5之间会处理,在时序图中并没有展示处理
  • 4、调用自身的方法loadBeanDefinitions(EncodedResource encodedResource)
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);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
    ...
}
  • 5、通过classPathResource的方法getInputStream获取InputStream流
  • 6、通过InputSource封装InputSteam,InputSource并不是spring提供的类,是在org.xml.sax包下
  • 7、上述几步都是做资源前置处理,也可以说是装备阶段。方法doLoadBeanDefinitions(inputSource, encodedResource.getResource())里面主要代码就是8、9展示的两步操作。获取xml的Document和通过Document注册实例化bean
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        ...
    try {
            //获取InputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                //java的org.xml.sax包下InputSource
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //读取xml为document和注册实例化bean
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
    ...
}

XmlBeanDefinitionReader.doLoadBeanDefinitions方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
          throws BeanDefinitionStoreException {

      try {
          //将inputStream解析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;
  }
  ...
}

讲到这里实际上都还是准备阶段。后续将讲述默认解析标签、自定义解析标签和bean的加载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354