如在过滤页面中所解释的,FiltersManager.applyRelevantFilters
方法通过循环遍历所有注册的DataFilter
实例,并调用它们的filter
方法来处理当前的DataSource
。如果filter
方法返回的是传递给它的完全相同的实例,则表示该过滤器不起作用。在这种情况下,FiltersManager.applyRelevantFilters
方法会继续循环并检查下一个过滤器。如果filter
方法返回的是与传递给它的不同的DataSource
实例,则表示该过滤器确实对数据流起作用。在这种情况下,FiltersManager.applyRelevantFilters
方法将当前的DataSource
设置为返回的值,并从头重新开始循环。
该算法允许同一个过滤器在需要时被多次应用,并且它还允许过滤器以任意顺序应用,而不受它们在FiltersManager
中注册的顺序的影响。
用户可以利用这个通用功能来添加自己的过滤器。一个例子是,如果敏感数据应该以加密形式存储,并且在加载数据时需要解密,则可以使用解密算法作为过滤器。
根据applyRelevantFilters
方法的工作方式,filter
方法必须以这样的方式实现:它应该检查传递给它的DataSource
,如果它认为不应该对其进行过滤,则返回其参数,如果它认为应该对其进行过滤,则返回一个新的DataSource
。
在实现自定义过滤器时,有一个重要的注意事项:过滤器绝不能自己打开DataSource
,无论它们在调用其filter
方法时返回原始实例还是过滤后的实例。原因是上层将决定是否打开返回的值,而且DataSource
只能打开一次;这是DataSource
提供的延迟打开的核心原则。
这个注意事项的一个结果是,过滤器不能查看由DataSource
引用的数据流的几个字节,例如尝试在头部查找魔数。这就是为什么例如GzipFilter
在名称中查找.gz
后缀而不是在文件开头查找0x1f8B
魔数的原因。
由于applyRelevantFilters
在每次向堆栈添加过滤器时都会从头开始重新启动循环,因此必须小心避免在同一个过滤器的无限数量实例之上堆叠。这意味着在过滤后返回的过滤DataSource
应该被识别为已经过滤,并且不再被同一个过滤器匹配。如果检查是基于文件名扩展名的(例如对于gzip压缩文件的.gz
),那么如果原始DataSource
的名称形式为base.ext.gz
,那么过滤后的文件应该具有base.ext
的名称形式。另一个要点是,如果过滤器不对DataSource
进行操作,则它必须返回传递给它的相同实例,它不能简单地创建一个透明的过滤器,只是传递名称和数据流而不做任何更改,否则它将被视为有效的过滤器,并且会一次又一次地添加,直到发生堆栈溢出或内存耗尽异常。
过滤部分本身是通过打开来自底层原始DataSource
的数据流,从中读取原始数据,对这些数据进行处理(解压缩、解密等),并将它们作为另一个流返回来实现的。
以下示例展示了如何为基于简单异或的虚拟解密算法实现过滤器(这只是一个玩具示例,不打算提供任何安全性)。
public class XorFilter implements DataFilter {
/** XOR加密文件的后缀。 */
private static final String SUFFIX = ".xor";
/** 高度保密的密钥。 */
private static final int key = 0x3b;
/** {@inheritDoc} */
@Override
public DataSource filter(final DataSource original) {
final String oName = original.getName();
final DataSource.Opener oOpener = original.getOpener();
if (oName.endsWith(SUFFIX)) {
final String fName = oName.substring(0, oName.length() - SUFFIX.length());
final DataSource.StreamOpener fOpener = () -> new XORInputStream(oName, oOpener.openStreamOnce());
return new DataSource(fName, fOpener);
} else {
return original;
}
}
/** XOR加密流的过滤器。 */
private static class XORInputStream extends InputStream {
/** 文件名。 */
private final String name;
/** 底层压缩流。 */
private final InputStream input;
/** 输入结束指示器。 */
private boolean endOfInput;
/** 简单构造函数。
* @param name 文件名
* @param input 底层压缩流
*/
XORInputStream(final String name, final InputStream input) {
this.name = name;
this.input = input;
this.endOfInput = false;
}
/** {@inheritDoc} */
@Override
public int read() throws IOException {
if (endOfInput) {
// 已经到达数据末尾
return -1;
}
final int raw = input.read();
if (raw < 0) {
endOfInput = true;
return -1;
} else {
return raw ^ key;
}
}
}
}