Spring(二),java 基础面试题应届生
下
所以,我们单纯使用 XmlBeanFactory 就可以进行读取配置文件、获取和创建 Bean 了
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("xxx.xml"));
bf.getBean("beanName");
可以看到,首先使用 ClassPathResource 去进行加载配置文件,然后使用 ClassPathResource 去实例化 XmlBeanFactory
配置文件封装
从上面的那一句代码我们就可以看到,配置文件的封装主要在下面这句代码里面!!!
new ClassPathResource("xxx.xml");
那么究竟这个 ClassPathResource 完成了什么功能
ClassPathResource 实现的就是加载配置文件的功能,并且 Spring 实现了自己的资源加载策略,不妨可以试想一下,如果没有自己的资源加载策略,对于各种形式的文件,比如 file、jar、xml 等形式,需要怎样进行解析加载呢?所以,Spring 干脆对其内部使用到的资源实现了自己的抽象结构
而这个资源加载策略、抽象结构的关键在于两个接口
InputStreamSource
Resource
InputStreamSource
可以看到这个接口十分简单,里面只定义了一个获取 InputStream 流的方法,用于获取文件的 InputStream
Resource
从源码上就可以看到 Resource 接口继承了 InputStreamSource 接口,所以具体的配置文件抽象结构就是 Resource 接口
相当于 Resource 接口抽象了所有 Spring 内部将会使用到的底层资源,一个 Resource 对应就是一个资源,是 File 类型的、还是 URL 类型的、是否存在、是否可读、最近的修改时间等,都可以从这个抽象接口中获取,从这个接口就可以获取配置文件的资源
所以,针对不同资源类型的(File、Url、ClassPath 等),对应都会有一个 Resource 实现类!
可以看到这个 Resource 家族是很大的!
下面列举几个常用的
FileSystemResource:获取 File 资源
ClassPathResource:获取 ClassPath 资源
UrlResource:获取 URL 资源
InputStreamResource:获取 InputStream 资源
ByteArrayResource:获取 Byte 数组资源
有了 Resource 接口,我们就可以对所有资源文件进行统一处理了,每个资源文件就是一个 Resource
Resource 相关实现类对配置文件进行封装了之后,后面 XmlBeanFactory 对文件的读取工作就全部交给了 XmlBeanDefinitionReader 来处理了
可以看到,构造方法就是将传进来的 Resource 交由 XmlBeanDefinitionReader 去进行处理,调用的是 loadBeanDefinitions 方法,也就是说,this.reader.loadBeanDefinitionReader(Resource)才是资源加载的真正实现!
所以,从配置文件的读取,要从这里开始
这里要注意的一点是,XmlBeanFactory 还调用了 super(ParentBeanFactory 方法),而 XmlBeanFactory 的父类是 DefaultListableBeanFactory
可以看到,DefaultListableBeanFactory 又直接 super,其父类是 AbstractAutowireCapableBeanFactory
可以看到,进来先调用 AbstractAutowireCapableBeanFactory 的无参构造方法
无参构造中值得注意的是 ignoreDependencyInterface 方法,该方法的主要作用是忽略给定接口的自动装配功能,也就是说实现了这些接口的类都不会再有自动注入功能,其底层实现是将忽略的接口存进名为 ignoreDependencyInterfaces 的 HashSet 集合中
举个栗子,假如一个实体 A 中的成员属性是实体 B,当实体 A 进行自动注入的时候,实体 B 也会被自动实例化,这也是 Spring 中提供的一个重要特性,但有些时候,我们不希望成员实体被自动注入那么我们可以让成员实体 B 去实现 BeanNameAware 接口、BeanFactoryAware 接口、BeanClassLoaderAware 接口,但前提是,实体 A 必须要有 B 的 setter 方法,也就是外部注入方法是不支持的!(这也可能是官方推荐 setter 方法注入原因之一)
对于 aware 这类接口,其实还有一个重要的作用是让 bean 对容器有意识,一般来说,bean 是根本不知道 Spring 的 IOC 容器的,所以 Bean 是无法获取 Spring 的 IOC 容器服务的,为了让 Bean 对 Spring 的 IOC 容器有意识,所以我们可以让实体去实现上面的那些 aware 接口,这样在容器里面的这些 Bean 会意识到 IOC 容器,并且可以获取相对应的 IOC 容器服务,比如实现 BeanNameAware 接口,可以让 Bean 获取自己在 IOC 容器里面的 Name
但这里还不能理解,为什么要给这些接口忽略外部注入功能???
这里我猜测,可能是想不让 Bean 被外部有意识的 Bean 污染(setter 注入),本来 Bean 就是一个无意识的东西,假如 Bean 还能注入外部有意识的 Bean,那么就会越来越多的 Bean 对 Spring 的 IOC 容器有意识,违反了初衷
加载 Bean
现在已经对配置文件进行封装了,那么下面就可以来加载 Bean 了
加载 Bean 的时候,其实是在读取配置文件里面完成的,也就是下面这句代码
这一整套流程是比较复杂的
EncodedResource
首先是封装资源文件后,对文件 Resource 进行 EncodedResource 封装
这个类是对文件的编码进行处理的
可以看到其构造器,其实起到装配作用而已,比如装配了文件 Resource,定义了编码 encoding 和字符集 charset,其主要作用体现在 getReader 方法上
不过先科普一下字符集和编码的关系,编码是依赖于字符集的,比如 UTF-8 编码就依赖于 Unicode 字符集,一般来说一个字符集对应一种编码
public Reader getReader() throws IOException {
//判断字符集是否为空,如果不为空,根据字符集处理文件并返回 InputStreamReader
//并且优先判断字符集
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
//判断编码是否为空,如果字符集不存在,那么就根据编码处理文件并返回 InputStreamReader
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
//如果即没有编码,也没有字符集,默认方式处理文件返回 InputStreamReader,默认为 UTF-8 编码
//从下面的源码中可以看出来
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
loadBeanDefinitions 方法
使用了 EncodedResource 对文件进行编码处理了之后,接下来就调用重载的 loadBeanDefinitions,**相当于将编码后的 Resource 交给重载的 loadBeanDefinitions 方法处理,具体根据配置
文件去加载 Bean 的操作都在这个方法里面**
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);
}
//从当前线程的 ThreadLocal 去获取正在加载的 Resource
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
//下面这个判断,是用来防止循环加载的!
//因为 currentResource 是一个 HashSet 集合,重复的不会进行添加
//所以,往 currentResources 添加失败,就表明当前文件已经在加载了
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
//获取文件的 InputStream
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
//又封装了一层,封装为了不是 Spring 下的 InputResource
InputSource inputSource = new InputSource(inputStream);
//同样进行编码操作
//所以可以估计,Spring 所说的自定义文件加载策略,其实就是基于 InputResource 的
if (encodedResource.getEncoding() != null) {
//如果文件自身携带编码规则,那就使用自带的编码规则
inputSource.setEncoding(encodedResource.getEncoding());
}
//进入了加载 Bean 的核心部分
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
//最终都会执行的操作
finally {
//当文件加载完后,无论失败与否,都要清楚在 currentResources 的存在
currentResources.remove(encodedResource);
//如果 currentResources 为空了,证明没有配置文件加载了
//当前线程的 ThreadLocal 直接清空这个集合
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
评论