写点什么

Spring(二),java 基础面试题应届生

作者:Java高工P7
  • 2021 年 11 月 10 日
  • 本文字数:3318 字

    阅读完需:约 11 分钟


所以,我们单纯使用 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 方法处理,具体根据配置


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


文件去加载 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();


}


}


}

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
Spring(二),java基础面试题应届生