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

xiaoxiao2021-02-28  89

概述

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

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

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

Resource的定位

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

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

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

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

其他: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); } } }
转载请注明原文地址: https://www.6miu.com/read-50374.html

最新回复(0)