Spring IOC容器的初始化过程(1)

概述

IOC 容器的初始化过程主要分为3个步骤:

  1. Resource的定位:这个指的是利用各种方式来找到BeanDefinition的位置,BeanDefinition是对Bean定义的一个抽象,Resource是对BeanDefinition的所处位置的抽象(想想你使用spring的时候写的xml文件)。
  2. BeanDefinition的载入和解析:通过第一步,我们能够找到Resource的位置,并获得它的实例,在这一步,我们会从Resource中读取到BeanDefinition。
  3. 向IOC容器注册这些BeanDefinition:将第二步所获得的BeanDefinition注册到IOC容器中,这样我们就能够通过IOC容器获取定义的bean的实例对象了。

本篇博客,我们来分别分析Resource的定位过程

Resource的定位

首先我们来看一看Resource这个接口,Resource定义的方法如下:

  1. exists():返回类型为boolean,用于判断对应的资源是否真的存在。
  2. isReadable():返回类型为boolean,用于判断对应资源的内容是否可读。需要注意的是当其结果为true的时候,其内容未必真的可读,但如果返回false,则其内容必定不可读。
  3. isOpen():返回值为boolean,用于判断当前资源是否代表一个已打开的输入流,如果结果为true,则表示当前资源的输入流不可多次读取(可以理解为这个资源不在你的硬盘上面,是一个你一关机就没了的资源),而且在读取以后需要对它进行关闭,以防止内存泄露。该方法主要针对于InputStreamResource,实现类中只有它的返回结果为true,其他都为false。
  4. getURL():返回值URL,返回当前资源对应的URL。如果当前资源不能解析为一个URL则会抛出异常。如ByteArrayResource就不能解析为一个URL。
  5. getFile():返回值File,返回当前资源对应的File。如果当前资源不能以绝对路径解析为一个File则会抛出异常。
  6. getInputStream():返回值InputStream,获取当前资源代表的输入流。

总之,Resource其实就是对一个只读资源(文件)的一种抽象,Resource又分为很多种,常见的有:

  1. ClassPathResource:这个资源代表你的项目中写的资源,处于你的类路径下面,我们常常把一些配置文件放在项目里面,如果要读取那些文件,我们就需要ClassPathResource。当然,文件路径写相对路径
  2. FileSystemResource:这个资源代表你的计算机上的资源,从名称来看,这个资源的定位范围为整个文件系统。使用的时候文件路径写绝对路径。
  3. UrlResource:这个资源的范围更广泛一些,我们可以通过传入URL地址来获取到其他计算机资源文件。
  4. 其他:ByteArrayResource,ServletContextResource,InputStreamResource等,不再一一描述。

    现在我们明白了Recourse是什么一回事情,单单获取一个资源也很简单,以ClassPathResource为例,我们只需要以下代码即可获得:

Resource resource = new ClassPathResource("beans.xml");

然而为了保证IOC容器的封装性和可扩展性,这样简单粗暴的获取Resource显然是不明智的,那么spring是如何做到优雅的获取Resource的呢?我们来看一看一个抽象类AbstractRefreshableApplicationContext。

从名称上来看,这个类首先是一个ApplicationContext,我们知道ApplicationContext是实现了BeanFactory接口的类,说了这么多实际上我只想表明AbstractRefreshableApplicationContext是一个IOC容器。其次这个IOC容器是Refreshable(可刷新的)的类,那么可刷新是什么意思呢?意思就是说,如果我想改变这个IOC容器的BeanDifinition,但是我又不想重新创建一个IOC容器,此时这个IOC容器必然有个功能叫做可刷新。

做了这么多铺垫以后,我们终于可以进入正题了,废话不多说,直接上代码:

//定位Resource的核心方法
public int loadBeanDefinitions(String location, Set actualResources) throws BeanDefinitionStoreException{
    ResourceLoader resourceLoader = getResourceLoader();
    if(resourceLoader == null){
        throw new BeanDefinitionStoreException(
        "哎呀呀,资源加载器找不着了");
    }
    /*
    上面代码的作用是获得一个资源加载器ResourceLoader,有了资源加载器,我们就可以通过我们传入的location(XML的文件路径)来获取一个Resource
    */
    if(resourceLoader instanceof ResourcePatternResolver){
        /*
            ResourcePatternResolver,从名字上可以看出带有pattern的功能,比如我传入的路径是一个正则表达式,那么这条路径就有可能对应多个Resource,resourceLoader是可以用户自己定义的。
        */
        try{
            Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
            int loadCount =loadBeanDefinitions(resources);
            /*
            注意,此处的loadBeanDefinitions方法并不是递归调用,而是调用了它的一个重载方法,这里的resourceLoader
            的作用是将location所表示的文件封装成一个个的Resource,本身不负责解析,而此处loadBeanDefinitions方
            法会负责解析这些Resource,并返回成功解析的资源个数,这个loadBeanDifinition方法会在后面提到。
            */
            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 {  
            // 这里的resourceLoader就比较简单了,它只能解析具体的一条路径  
            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的定位实际上在下面所述的代码中获取

Resource resource = resourceLoader.getResource(location);

在继续说下去之前,我们有必要理一理ResourceLoader和AbstractRefreshableApplicationContext之间的关系
这里写图片描述

我们可以看到AbstractRefreshableApplicationContext实际上是实现了ResourceLoader这个接口的,但是,问题也来了,我在上面的方法了为什么还要在get一个ResourceLoader,然后再调用ResourceLoader的getResource方法呢,直接用自己的难道不行吗?其实这个问题很好解答,这其实是一个装饰者模式。AbstractRefreshableApplicationContext拥有一个成员变量ResourceLoader,通常它默认是DefaultResourceLoader(图里有),AbstractRefreshableApplicationContext可以通过包装它的一些方法,来完成它不能完成的一些事情,像DefaultResourceLoader的子类FileSystemResourceLoader也可以被包装,这样resourceLoader.getResource就变得多种多样了。

现在我们来看看DefaultResourceLoader的getResource方法,一且就真相大白了。

public Resource getResource(String location){
    Assert.notNull(location, "Location must not be null");
    if(location.startsWith(CLASSPATH_URL_PREFIX)){
        return new ClassPathResource(
            location.subString(CLASSPATH_URL_PREFIX.length()), 
            getClassLoader());
        /*
            带CLASSPATH_URL_PREFIX前缀的路径会被理解成相对路径,这里干的事情是去掉这个前缀,然后使用该路径来创建一个ClassPathResource,并返回
        */
    }else{
        try{
            URL url = new URL(location);
            return new UrlResource(url);
            /*
                这里尝试创建一个UrlResource,如果成功了,那么这个路径其实就是一条url路径,如果失败了,使用catch语句中的代码来补救
            */
        }catch(MalformedURLException ex){
            /*
                这个getResourceByPath可以被子类重写,通过子类,我们就形成了多样化的Resource的生成模式
            */
            return getResourceByPath(location);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值