通用源码阅读引导mybatis源码详解:io包
io包io包即输入/输出包,负责完成 MyBatis中与输入/输出相关的操作。
说到输入/输出,首先想到的就是对磁盘文件的读写。在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的读操作。因此,io包中提供对磁盘文件读操作的支持。
除了读取磁盘文件的功能外,io包还提供对内存中类文件(class文件)的操作。
配景知识
单例模式
单例模式(Singleton Pattern)是一种非常简朴的设计模式。使用了单例模式的类提供一个方法得到该类的对象,而且总保证这个对象是唯一的。
例如,代码9-1就实现了一个单例模式的类。在该类中,构造方法被 private声明,因此无法通过外部获得 Singleton 对象。获取 Singleton 对象的唯一方式就是调用 getInstance方法,而每次访问 getInstance方法得到的对象都来自 INSTANCE属性。INSTANCE属性是一个静态属性,全局唯一。通过以上的机制保证了全局只有一个 Singleton对象。
【代码9-1】
https://p1.pstatp.com/large/pgc-image/014d959ef0a04b5abfb76928d1df6fae
从多线程安全、懒加载等角度思量,单例模式还有很多种写法。我们这里不再展开,感兴趣的读者可以自己学习。
代理模式
代理模式(Proxy Pattern)是指建立某一个对象的代理对象,而且由代理对象控制对原对象的引用。
例如,我们不能直接访问对象 A,则可以建立对象 A的代理对象 AProxy。这样,就可以通过访问 A Proxy来间接地使用对象 A的功能。AProxy就像 A的对外联络人一般。图9-1给出了代理模式类图。
https://p1.pstatp.com/large/pgc-image/ad00432117f14690a0ef45d077790d99图9-1 代理模式类图
代理模式能够实现很多功能:· 隔离功能:通过建立一个目标对象的代理对象,可以防止外部对目标对象的直接访问,这样就使得目标对象与外部隔离。我们可以在代理对象中增加身份验证、权限验证等功能,从而实现对目标对象的安全防护。
· 扩展功能:对一个目标对象建立代理对象后,可以在代理对象中增加更多的扩展功能。例如,可以在代理对象中增加日志记录功能,这样对目标对象的访问都会被代理对象计入日志。
· 直接替换:对一个目标对象建立代理对象后,可以直接使用代理对象完全替换目标对象,由代理对象来实现全部的功能。例如,MyBatis 中数据库操作只是一个抽象方法,但实际运行中会建立代理对象来完成数据库的读写操作。
静态代理
静态代理就是代理模式最简朴的实现。所谓“静态”,是指被代理对象和代理对象在程序中是确定的,不会在程序运行过程中发生变革。
例如,我们为用户类设置一个如代码9-2所示的接口类 UserInterface,然后在其中增加一个打招呼的抽象方法 sayHello。
【代码9-2】
https://p1.pstatp.com/large/pgc-image/626e33a1d8554c069c628e31990426fb
实现 UserInterface接口的被代理类代码如代码9-3所示。
【代码9-3】
https://p1.pstatp.com/large/pgc-image/0495d6ef5a4c42929d5d3819bf3f4699
下面为被代理类增加一个代理类。代理类中调用了被代理类的 sayHello方法,并在此方法的底子上增加了新的功能:在打招呼前后增加开场语句和结束语句。代码9-4 展示了该代理类。
【代码9-4】
https://p1.pstatp.com/large/pgc-image/35210f646443457fa56430df42bbe695
https://p1.pstatp.com/large/pgc-image/b5df0465fa074064a07c912915ea1011
下面通过代码9-5调用代理对象中的方法。
【代码9-5】
https://p9.pstatp.com/large/pgc-image/2e59b91ca46d4cb6beeb1ec1b1741071
程序运行结果如图9-2 所示。可以看到,通过代理对象执行了被代理对象的方法,而且被代理对象的方法前后新增加了一些功能。在整个过程中不需要被代理对象自身做出任何的更改。
https://p1.pstatp.com/large/pgc-image/3de8b5f87d60429cbe8dbf0ca77c8920图9-2 程序运行结果
示例项目类图如图9-3所示。
https://p1.pstatp.com/large/pgc-image/c40542f7f1e84b7b80a4a39a1abf2ae3图9-3 示例项目类图
但是静态代理也有一些局限性,最显着的就是代理对象和被代理对象是在程序中写死的,显然不够灵活。动态代理则没有此毛病,我们会在 10.1.3节进行介绍。
VFS
磁盘文件系统分为很多种,如 FAT、VFAT、NFS、NTFS等。不同文件系统的读写操作各不相同。VFS(Virtual File System)作为一个假造的文件系统将各个磁盘文件系统的差异屏蔽了起来,提供了统一的操作接口。这使得上层的软件能够用单一的方式来跟底层不同的文件系统沟通,如图9-4所示。
https://p3.pstatp.com/large/pgc-image/abee2680e09343eeb9ff51ab7cfa4cdb图9-4 VFS的作用
在操作磁盘文件时,软件程序不需要和实体的文件系统打交道,只需要和 VFS沟通即可。这使得软件系统的磁盘操作变得更为简朴。
VFS实现类
MyBatis的 io包中 VFS的作用是从应用服务器中找寻和读取资源文件,这些资源文件可能是配置文件、类文件等。io包中 VFS相关类主要有三个,图9-5展示了它们的类图。
https://p9.pstatp.com/large/pgc-image/affe11687a9d43e1a3a627a5fb7cab42图9-5 VFS类图
DefaultVFS类和 JBoss6VFS类是 VFS类的两个实现类。在确定了详细的实现类之后,外部只需通过 VFS中的方法即可完成外部文件的读取。
VFS类中的主要属性如代码9-6所示,两个属性中生存了内置和用户自定义的 VFS实现类。
【代码9-6】
https://p1.pstatp.com/large/pgc-image/ce0d8d4aa6704a6b8afbb50f8e71cb1d
https://p1.pstatp.com/large/pgc-image/57e6eb3760ef4cf7929fe921079518bd
VFS 类中含有一个内部类 VFSHolder,该类使用了单例模式。其中的 createVFS 方法能够对外给出唯一的 VFS实现类。VFSHolder类带注释的源码如代码9-7所示。
【代码9-7】
https://p3.pstatp.com/large/pgc-image/5df19280dec44a86a240c7f237a2eee4
在 VFSHolder类的 createVFS方法中,先组建一个 VFS实现类的列表,然后依次对列表中的实现类进行校验。第一个通过校验的实现类即被选中。在组建列表时,用户自定义的实现类放在了列表的前部,这保证了用户自定义的实现类具有更高的优先级。
内部类 VFSHolder中终极确定的 VFS实现类会被放入 INSTANCE变量中。这样,当外部调用 VFS类的 getInstance方法时就可以拿到该 VFS实现类的对象,如代码9-8所示。
【代码9-8】
https://p3.pstatp.com/large/pgc-image/22e46bcd05e24a95a8781d230b0f08a4
MyBatis中为 VFS提供了两个内部实现类,分别是 DefaultVFS类和
JBoss6VFS类,下面分别进行介绍。
DefaultVFS类
DefaultVFS 作为默认的 VFS 实现类,其 isValid 函数恒返回 true。因此,只要加载DefaultVFS类,它肯定能通过 VFS类中 VFSHolder单例中的校验,而且在进行实现类的校验时 DefaultVFS排在整个校验列表的最后。因此,DefaultVFS成了所有 VFS实现类的保底方案,即最后一个验证,但只要验证肯定能通过。
除了 isValid方法外,DefaultVFS中还有以下几个方法。
· list(URL,String):列出指定 url下符合条件的资源名称;
· listResources(JarInputStream,String):列出给定 jar包中符合条件的资源名称;
· findJarForResource(URL):找出指定路径上的 jar包,返回jar包的准确路径;· getPackagePath(String):将 jar包名称转为路径;
· isJar:判断指定路径上是否是 jar包。
以上方法均接纳直接读取文件的方式来实现,结构并不复杂,我们不再展开介绍。
JBoss6VFS类
JBoss是一个基于 J2EE的开放源代码的应用服务器,JBoss6是 JBoss中的一个版本。JBoss6VFS即为借鉴 JBoss6设计的一套 VFS实现类。
在 JBoss6VFS中主要存在两个内部类。
· VirtualFile:仿照 JBoss中的 VirtualFile类设计的一个功能子集;
· VFS:仿照 JBoss中的 VFS类设计的一个功能子集。
阅读 VirtualFile和 VFS中的方法便可以发现,这些方法中都没有实现详细的操作,而是调用 JBoss中的相关方法。
以代码9-9展示的 VirtualFile中的 getPathNameRelativeTo方法为例,方法中直接使用invoke语句,将操作转给了 org.jboss.vfs.VirtualFile类中的 getPathNameRelativeTo方法。因此,这里使用了代理模式,此处的 VirtualFile内部类是 JBoss中 VirtualFile的静态代理类。
【代码9-9】
https://p9.pstatp.com/large/pgc-image/87069389b8374ae7b401e763f91faf7c
https://p3.pstatp.com/large/pgc-image/605d81ae014b4965a77794fd3b9d2100
同理,VFS内部类是 JBoss中 VFS的静态代理类。
在 JBoss6VFS类中,两个内部类 VirtualFile和 VFS都是代理类,只负责完成将相关操作转给被代理类的工作。那么,要想使 JBoss6VFS类正常工作,必须确保被代理类存在。
确定被代理类是否存在的过程在 JBoss6VFS类的 initialize方法中完成。该方法由静态代码块触发,因此会在类的加载阶段执行,如代码9-10所示。
【代码9-10】
https://p3.pstatp.com/large/pgc-image/2f08d141a1db4af7b3f38ca94fe57661
https://p1.pstatp.com/large/pgc-image/133f346f4e9040408cc3032b4fdecd9c
在初始化方法中,会尝试从 JBoss 的包中加载和校验所需要的类和方法。最后,还通过返回值对加载的方法进行了进一步的校验。而在以上的各个过程中,只要发现加载的类、方法不存在或者返回值发生了变革,则以为 JBoss 中的类不可用。在这种情况下,checkNotNull方法和 checkReturnType方法中会调用 setInvalid 方法将 JBoss6VFS的valid字段设置为 false,表现 JBoss6VFS类不可用。
类文件的加载
除了从磁盘中读取普通文件外,从磁盘中获取类文件(Class文件)并加载成一个类也是一种常用的功能。
要把类文件加载成类,需要类加载器的支持。ClassLoaderWrapper类中封装了五种类加载器,而 Resources 类又对 ClassLoaderWrapper类进行了一些封装。下面我们重点关注ClassLoaderWrapper类。
ClassLoaderWrapper的五种类加载器由 getClassLoaders方法给出,如代码9-11所示。
【代码9-11】
https://p3.pstatp.com/large/pgc-image/1c046ab88b5a430f8d8101367c699ddb
这五种类加载器依次是:
· 作为参数传入的类加载器,可能为 null;· 系统默认的类加载器,如未设置则为 null;
· 当前线程的线程上下文中的类加载器;
· 当前对象的类加载器;
· 系统类加载器,在 ClassLoaderWrapper的构造方法中设置。
以上五种类加载器的优先级由高到低。在读取类文件时,依次到上述五种类加载器中进行寻找,只要某一次寻找成功即返回结果。
classForName 方法是一个根据类名找出指定类的方法,下面以该方法为例,查看五种类加载器是如何轮番上阵发挥作用的。整个过程如代码9-12所示。
【代码9-12】
https://p1.pstatp.com/large/pgc-image/69045f93b58346ae855bbc0d09ea2f21
ResolverUtil类
ResolverUtil是一个工具类,主要功能是完成类的筛选。这些筛选条件可以是:
· 类是否是某个接口或类的子类;
· 类是否具有某个注解。为了能够基于这些条件进行筛选,ResolverUtil中设置有一个内部接口 Test。Test是一个筛选器,内部类中有一个抽象方法 matches来判断指定类是否满足筛选条件。图9-6给出了 Test接口及其实现类的类图。
https://p1.pstatp.com/large/pgc-image/78365093142a4a40be26f8c7804451a1图9-6 Test接口及其实现类的类图
如上所示,Test内部类有两个实现类,都重写了 matches方法。
· IsA类中的 matches方法可以判断目标类是否实现了某个接口或者继承了某各类;
· AnnotatedWith类中的 matches方法可以判断目标类是否具有某个注解。
终极通过校验的类会放到 ResolverUtil类的 matches属性中。
这样一来,在读取某个路径上的类文件时,还可以借助 ResolverUtil对类文件进行一些筛选。ResolverUtil中的 find方法即支持筛选出指定路径下的符合指定条件的类文件,代码9-13是该方法带注释的源码。
【代码9-13】
https://p1.pstatp.com/large/pgc-image/02775abe6f4f42ab84c480bdb12104a4
上述方法中传入的 Test参数可以是 IsA对象,也可以是 AnnotatedWith对象,这样就可以将 packageName 路径下所有符合条件的类文件找出来。真正触发测试的是addIfMatching子方法,该方法带注释的源码如代码9-14所示。
【代码9-14】
https://p1.pstatp.com/large/pgc-image/3796ca0e60814e418a95c68788b66a71
https://p1.pstatp.com/large/pgc-image/7f257e282bc94649b7067261a08351c3
本文给大家解说的内容是通用源码阅读引导mybatis源码详解: io包
[*]下篇文章给大家解说的是通用源码阅读引导mybatis源码详解: logging包;
[*]以为文章不错的朋侪可以转发此文关注小编;
[*]感谢大家的支持!
页:
[1]