使用System.setOut(java.io.PrintStream)和System.setErr(java.io.PrintStream)可以将控制台的输出流重定向到其它的单个输出流比如文件输出流中。
但若要将控制台的输出流重定向到多个输出流(包括控制台),这时,可以通过代理的方式来实现,在Java里一般有两种方式来实现代理,一是继承组合,一是动态代理
1.继承原先的PrintStream,并维持一个多个输出流的集合,当调用该类的任意方法时,调用输出流集合里输出流的方法,伪代码如下:
public ManyPrintStream extends PrintStream {
private List<PrintStream> streamList = new ArrayList<PrintStream>();
................
public void method(....) {
for (int i = 0; i < streamList.size(); i++) {
PrintStream ps = streamList.get(i);
ps.method(....);
}
}
}
这种方法可行,但由于PrintStream的方法较多,实现比较麻烦。
2.一种更为简便的方法是动态代理,这种代理的原理在生成代理动态拦截对被代理类的方法的调用,我们可以使用java.lang.reflect.Proxy 类来生成动态代理(JDK动态代理),但是该代理只能基于接口(Interface)生成代理,而java.io.PrintStream是没有继承口的具体类,所以我们无法使用JDK动态代理来实现。但我们可以采用第三方提供的基于类生成代理的方法,CGLIB就是一种基于类(class)生成代理的开源库,用CGLIB生成代理要求被代理的类具有默认的无参构造函数(Constructor),而PrintStrem不符合些要求,但我们可能通过继承来达到些目的:
使用CGLIB需要的jar包:cglib-2.1_3.jar,asm-1.5.3.jar,asm-attrs-1.5.3.jar
或cglib-nodep-2.1_3.jar
二者选其一即可,两者都选会导致jar包冲突
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
//被代理的类
public class PseudoPrintStream extends PrintStream {
public PseudoPrintStream() {
super(new OutputStream() {//匿名OutputStream
public void write(int b) throws IOException {
//不用实现,只是为了比父类增加一个无参构造函数
}
});
}
}
下面是代理的实现代码:
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//处理代理被调用的类
public class StreamHandler implements MethodInterceptor{
//输出流链表
private List<PrintStream> streamList = new ArrayList<PrintStream>();
//代理处理类生效,不允许再添加输出流标志
private boolean validate = false;
/**添加输出流*/
public synchronized void addStream(PrintStream ps) {
if (validate) {
throw new IllegalStateException();
}
streamList.add(ps);
}
/**代理生效*/
public synchronized void validate() {
validate = true;
}
/**查询代理是否生效*/
public synchronized boolean getValidate() {
return validate;
}
/**拦截被代理的方法并处理*/
public Object intercept(Object object,
Method method,
Object[] args,
MethodProxy proxy)
throws Throwable {
//如果是PseudoPrintStream 的close方法被调用,则调用PseudoPrintStream 的close
//方法关闭其匿名输出流
if ("close".equals(method.getName())) {
proxy.invokeSuper(object, args);
}
Object object;
for (int i = 0; i < streamList.size(); i++) {
PrintStream ps = streamList.get(i);
object = method.invoke(ps, args);
}
return obejct;//返回链表最后一个输出流调用的结果
}
/**生成代理的方法*/
public static PrintStream proxyFor(StreamHandler handler) {
if (!handler.getValidate()) throw new RuntimeException("Handler not validate");
Enhancer enhancer = new Enhancer();
//被代理的ClassLoader
enhancer.setClassLoader(PseudoPrintStream.class.getClassLoader());
//被代理的Class
enhancer.setSuperclass(PseudoPrintStream.class);
//代理的方法被调用时,处理该调用的MethodInterceptor接口实现
enhancer.setCallback(handler);
//生成代理
PrintStream proxy= (PseudoPrintStream) enhancer.create();
//返回生成的代理
return proxy;
}
}
测试类
import java.io.FileOutputStream;
import java.io.PrintStream;
public class TestManyStream {
public static void main(String[] args) throws Exception{
//文件输出流1
FileOutputStream fo1 = new FileOutputStream("E:\\file1.txt", true);
//文件输出流2
FileOutputStream fo2 = new FileOutputStream("E:\\file2.txt", true);
//PrintStream1
PrintStream ps1 = new PrintStream(fo1);
//PrintStream1
PrintStream ps2 = new PrintStream(fo2);
//代理被调用时的处理类
StreamHandler sHandler = new StreamHandler();
sHandler.addStream(ps1);
sHandler.addStream(ps2);
sHandler.addStream(System.out);
sHandler.validate();
PrintStream streamProxy = StreamHandler.proxyFor(sHandler);
//系统输出流重定向到代理
System.setOut(streamProxy);
System.setErr(streamProxy);
System.out.println("All stream print this out!");
try {
throw new Exception("An Exception Occured!");
} catch (Exception e) {
e.printStackTrace();
}
streamProxy.close();
}
}
运行该测试类会发现控制台,E:\file1.txt,E:\file2.txt输出同样的内容
3.还可以通过继承OutputStream来实现,参照FilterOutputStream。
示例代码:不考虑一致性,错误处理策略等
public class MultiStream extends OutputStream {
private List<OutputStream> streamList = new ArrayList<OutputStream>();
//其他构造方法省略
public MultiStream(OutputStream ...outputStreams) {
streamList.addAll(Arrays.asList(outputStreams));
}
public void close() throws IOException {
IOException e = null;
//依次调用多个输出流的close方法
for (OutputStream o:streamList) {
try {
o.close();
} catch (IOException ioE) {
e = ioE;
}
}
if (e != null) {
throw e;
}
}
public void flush() throws IOException {
...
}
public void write(byte[] b, int off, int len) throws IOException {
IOException e = null;
for (OutputStream o:streamList) {
try {
o.write(b, off, len);
} catch (IOException ioE) {
e = ioE;
}
}
if (e != null) {
throw e;
}
}
public void write(byte[] b) throws IOException {
...
}
public void write(int b) throws IOException {
...
}
}
测试类
import java.io.FileOutputStream;
import java.io.PrintStream;
public class TestMultiStream {
public static void main(String[] args) throws Exception {
//文件输出流1
FileOutputStream fo1 = new FileOutputStream("E:\\file1.txt", true);
//文件输出流2
FileOutputStream fo2 = new FileOutputStream("E:\\file2.txt", true);
//PrintStream
MultiStream mStream = new MultiStream(fo1, fo2);
//MultiStream mStream = new MultiStream(fo1, fo2,System.out);
PrintStream ps = new PrintStream(mStream);
//系统输出流重定向
System.setOut(ps);
System.out.println("All stream print this out!");
ps.close();
}
}
运行该测试类会发现控制台,E:\file1.txt,E:\file2.txt输出同样的内容