一、引子:
我们先来看看下面这行代码的具体实现
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
通过时序图,我们看到BeanFactoryTest中首先调用ClassPathResource的构造函数来构造Resource。这里就有个问题,因为资源有多种表现形式,文件、URL、Byte数组等,那么Resource资源是怎么封装的呢?
二、Resource类图:
先来看下Resource的类图
首先,InputStreamSource接口定义了任何可以返回InputStream的类,也就是所有可以转化为输入流的源,都可以通过这个接口去获取。像文件File、资源文件和字节数组等。
其次,在将资源转化为输入流之前,我们需要判断要转化的资源是否存在exist(),资源是否可读isReadable(),资源的名字getFilename()等等。而这些操作被封装在Resource接口中。
有同学可能会问,InputStreamSource接口只定义了一个getInputStream()方法,而Resource接口又继承自InputStreamSource接口,那为什么不直接在Resource接口中增加getInputStream()方法,这样不更省事儿么。这涉及到接口隔离原则:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。简单的讲就是接口精简最小,比如一个类实现一个接口,接口有10个方法,但实际只需要实现其中的2个方法,另外的8个方法根本不需要。但按照语法还得增加这8个方法的实现,这就很不合适。遇到这种情况,可以讲接口拆分成更小的功能模块。聊聊设计模式讲了设计模式的六大原则,有兴趣的同学可以参考。
AbstractResource提供了Resource接口的部分方法的默认实现,因为只提供了部分方法的实现,不是全部,所以它是一个抽象类。这里插一句,抽象类除了很适合做工具类之外(Spring源码阅读2:AliasRegistry&SimpleAliasRegistry中讲到了工具类Assert&StringUtils),还很适合做模板。子类中共用的方法在父类中定义,需要子类单独实现的,在父类中定义为抽象方法,这样父类本身也就变成了一个模板类。
FileSystemResource和ByteArrayResource代表两种不同的输入源,两个类都实现了Resource中定义的所有方法。讲解源码的时候,我们是按从上及下的方式,现先讲最高层次的抽象,再讲到不同输入源的具体实现,但在实际开发设计中,我们应该是按从下及上的去抽象来设计架构的。
AbstractFileResolvingResource相对于AbstractResource,覆盖了部分AbstractResource的默认实现,提供了相对于ClassPathResource和UrlResource的共用方法实现,预留了部分需要ClassPathResource和UrlResource特定实现的方法,所以它还是一个抽象类。
ClassPathResource和UrlResource是相对于FileSystemResource和ByteArrayResource的另外两种输入源的具体实现。
三、使用实例
举个例子:
Resource resource = new ClassPathResource("test.xml");
InputStream inputStream = resource.getInputStream();
发现了吧,调用Resource接口的getInputStream()方法就可以获取输入流了。之后你就可以解析数据流去做一些操作,当然Spring已经为我们做了。