Maven引用坐标:
<dependency> <groupId>org.tinygroup</groupId> <artifactId>vfs</artifactId> <version>0.0.12</version> </dependency>
于是Apache VFS就进入了我的框架,功能也完全良好。但是在压力测试的时候,忽然发现有内存泄露问题,dump一下内存,进行分析之后,发现原来是Apache VFS 2.0惹得祸,看一看apache VFS已经好久没有升级了,跟踪了一下源码,发现有些地方,比较诡异,就是有时候进有时候不进,查之良久而不得,只好下决定把Apache VFS从里面拿掉,而拿掉之前,就需要有同样功能的东东支撑,不得已,花费2天时间写了一个VFS,功能比Apache的VFS少一些,但是够用了。然后花了一天时间,把原来Apache VFS的代码迁移到新的VFS之上,做此项迁移工作的小弟手指都木了,迁移完之后,再行测试,内存泄露问题不再存在。
好了,上面讲了前因,下面就介绍一下VFS的构成及实现:
模式提供者接口用于扩展各种文件来源,比如:本地文件,HTTP文件,FTP文件,Jar包文件,Zip文件等等,其接口声明如下:
/** * 模式提供者接口 */ public interface SchemaProvider { /** * 是否匹配 * * @param resource * @return 如果返回true,表示此提供者可以处理,返回false表示不能处理 */ boolean isMatch(String resource); /** * 返回处理的模式 * * @return */ String getSchema(); /** * 解析资源,并返回文件对象 * * @param resource * @return */ FileObject resolver(String resource); }
文件对象FileObject,是对文件的访问的统一接口,它的定义如下:
public interface FileObject { String getFileName();// 返回文件名 String getPath();// 返回路径 String getAbsolutePath();// 返回绝对路径 String getExtName();// 返回扩展名 boolean isExist();// 是否存在 long getSize();// 返回文件大小 InputStream getInputStream();//返回输入流 boolean isFolder();// 返回是否是目录,如果是目录,则getInputStream无效。 FileObject getParent();// 返回上级文件 void setParent(FileObject fileObject);// 设置上级文件 List<FileObject> getChildren();// 返回下级文件列表 FileObject getChild(String fileName);//获取参数名称指定的fileobject long getLastModifiedTime();// 返回修改时间 SchemaProvider getSchemaProvider();//返回模式提供者 boolean isInPackage();//是否是包文件 URL getURL();//返回url OutputStream getOutputStream();//返回输出流 }关键的接口就是上面两个,当然还有一个重要的类就是虚拟文件系统类,内容如下:
/** * 虚拟文件系统 */ public class VFS { static Map<String, FileObject> fileObjectCacheMap = new ConcurrentHashMap<String, FileObject>(); static Map<String, Long> fileModifyTimeMap = new ConcurrentHashMap<String, Long>(); static final Map<String, SchemaProvider> schemaProviderMap = new HashMap<String, SchemaProvider>(); private static String defaultSchema = "file:"; static { addSchemaProvider(new JarSchemaProvider()); addSchemaProvider(new ZipSchemaProvider()); addSchemaProvider(new FileSchemaProvider()); addSchemaProvider(new HttpSchemaProvider()); addSchemaProvider(new HttpsSchemaProvider()); addSchemaProvider(new FtpSchemaProvider()); addSchemaProvider(new JBossVfsSchemaProvider()); } /** * 构建函数私有化 */ private VFS() { } /** * 清空Cache */ public static final void clearCache() { fileObjectCacheMap.clear(); } /** * 添加新的模式提供者 * * @param schemaProvider */ public static final void addSchemaProvider(SchemaProvider schemaProvider) { schemaProviderMap.put(schemaProvider.getSchema(), schemaProvider); } /** * 设置默认模式提供者 * * @param schema */ public static final void setDefaultSchemaProvider(String schema) { defaultSchema = schema; } /** * 返回指定的模式提供者 * * @param schema * @return */ public static final SchemaProvider getSchemaProvider(String schema) { return schemaProviderMap.get(schema); } /** * 解析文件 * * @param resource * @return */ public static FileObject resolveFile(String resource) { FileObject fileObject = fileObjectCacheMap.get(resource); if (fileObject != null) { long oldTime = fileModifyTimeMap.get(resource); long newTime = fileObject.getLastModifiedTime(); if (oldTime == newTime) { return fileObject; } } try { resource = URLDecoder.decode(resource, "UTF-8"); } catch (UnsupportedEncodingException e) { // 如果出错也不用管,忽略之 } SchemaProvider schemaProvider = schemaProviderMap.get(defaultSchema); for (SchemaProvider provider : schemaProviderMap.values()) { if (provider.isMatch(resource)) { schemaProvider = provider; break; } } fileObject = schemaProvider.resolver(resource); fileObjectCacheMap.put(resource, fileObject); fileModifyTimeMap.put(resource, fileObject.getLastModifiedTime()); return fileObject; } /** * 解析URL * * @param url * @return */ public static FileObject resolveURL(URL url) { return resolveFile(url.getPath()); } }
可以看到,他实际上是一个工具类,提供了若干个静态方法,以供使用,因此,为了便于扩展,此类也没有加final,考虑到添加模式提供者一般是加载时单线程处理的,因此,为了提高效率,也没有增加线程安全相关的处理。
主体就是这三个类,接下来就是FileObject和SchemaProvider的扩展了,从下面的代码可以看到,默认已经添加了Jar文件支持,Zip文件支持,本地文件支持,Http资源支持,Ftp支持,JBossVfsSchema支持。
static { addSchemaProvider(new JarSchemaProvider()); addSchemaProvider(new ZipSchemaProvider()); addSchemaProvider(new FileSchemaProvider()); addSchemaProvider(new HttpSchemaProvider()); addSchemaProvider(new HttpsSchemaProvider()); addSchemaProvider(new FtpSchemaProvider()); addSchemaProvider(new JBossVfsSchemaProvider()); }当然,你也可以根据进行进行扩展,只要调用VFS类的addSchemaProvider方法就可以了。限于篇幅,SchemaProvider及FileObject的实现类就不再一个一个讲解了,需要的话,可以看源码。接下来看看如何使用?
编写下面两行程序:
FileObject fileObject= VFS.resolveFile("src/main/java/org/tinygroup/vfs"); FileUtils.printFileObject(fileObject);输出结果如下:
absolutePath:E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs extName:null fileName:vfs inputsteam:null path: provider:org.tinygroup.vfs.impl.FileSchemaProvider@296672d6 size:0 children:[E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs\FileObject.java, E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs\impl, E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs\SchemaProvider.java, E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs\util, E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs\VFS.java] parent:null exist:true folder:true url:file:E:/SVN/tinyorg-code/trunk/Sources/framework/base/vfs/src/main/java/org/tinygroup/vfs ------------------ absolutePath:E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs\FileObject.java extName:java fileName:FileObject.java inputsteam:java.io.BufferedInputStream@13ccb029 path:/FileObject.java provider:org.tinygroup.vfs.impl.FileSchemaProvider@296672d6 size:2077 children:null parent:E:\SVN\tinyorg-code\trunk\Sources\framework\base\vfs\src\main\java\org\tinygroup\vfs exist:true folder:false url:file:E:/SVN/tinyorg-code/trunk/Sources/framework/base/vfs/src/main/java/org/tinygroup/vfs/FileObject.java ------------------ 下面还有许多文件......省略了
可以看到确实是已经成功解析本地文件。
解析Web页面也是很方便的,如下:
FileObject fileObject=VFS.resolveFile("http://my.oschina.net/tinyframework"); assertTrue(fileObject instanceof HttpFileObject); FileUtils.printFileObject(fileObject);测试用例也通过,说明Web页面也是一样样的。
由于所有的文件对象都实现了FileObject接口,所以,我们成功的对所有的文件进行了封装,构建了我们自己的VFS。