12

我正在使用一个任性的库,不幸的是,它会将信息打印到System.out(或偶尔System.err)。防止这种情况的最简单方法是什么?

我一直在考虑创建一个到内存的输出流,在每次调用其中一个麻烦的方法之前替换System.outerr之前,稍后恢复它们,然后忽略创建的流的缓冲区。有没有更简单、更优雅的方法?

编辑:我不想重定向所有输出 - 这很容易完成。我只想忽略某些库调用可能生成的输出。

4

4 回答 4

16

我最终做了类似的事情:

PrintStream out = System.out;
System.setOut(new PrintStream(new OutputStream() {
    @Override public void write(int b) throws IOException {}
}));
try {
    <library call>
} finally {
    System.setOut(out);
}

感谢 AlexR 和 stacker 将我重定向到这个简短的解决方案。

于 2010-11-28T10:53:44.853 回答
4

永久摆脱那些不需要的打印的一种方法是使用字节码操作从麻烦的库中删除打印语句。例如,这可以使用ASM(或其他更高级别且更易于使用的AOP框架之一)来完成。

您可以在运行时执行此操作,也可以将其作为重写库类文件的一次性操作。请参阅 ASM 的文档以了解如何操作。这是一个概念证明。它所做的是将所有对System.out的引用替换为对不执行任何操作的 a 的引用PrintStream

首先是测试。他们使用我项目中的一些实用程序类来帮助测试字节码转换(测试它需要创建一个自定义类加载器并将字节码转换应用于正确的类,而不是任何其他类)。

package net.orfjackal.dimdwarf.aop;

import net.orfjackal.dimdwarf.aop.conf.*;
import org.junit.*;
import org.objectweb.asm.*;
import org.objectweb.asm.util.CheckClassAdapter;

import java.io.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.*;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

public class RemoveCallsToSystemOutTest {

    private PrintStream originalOut;
    private ByteArrayOutputStream collectedOut;

    @Before
    public void collectSystemOut() {
        originalOut = System.out;
        collectedOut = new ByteArrayOutputStream();
        System.setOut(new PrintStream(collectedOut));
    }

    @After
    public void restoreOriginalSystemOut() {
        System.setOut(originalOut);
    }

    @Test
    public void the_target_class_prints_when_not_manipulated() throws Exception {
        String safetyCheck = callPrintSomething(TroublesomePrinter.class);

        assertThat(safetyCheck, is("it did execute"));
        assertThat(collectedOut.size(), is(greaterThan(0)));
    }

    @Test
    public void the_target_class_does_not_print_when_it_has_been_manipulated() throws Exception {
        String safetyCheck = callPrintSomething(instrumentClass(TroublesomePrinter.class));

        assertThat(safetyCheck, is("it did execute"));
        assertThat(collectedOut.size(), is(0));
    }

    private static String callPrintSomething(Class<?> clazz) throws Exception {
        Method m = clazz.getMethod("printSomething");
        m.setAccessible(true);
        return (String) m.invoke(null);
    }

    private static Class<?> instrumentClass(Class<?> cls) throws ClassNotFoundException {
        ClassFileTransformer transformer = new AbstractTransformationChain() {
            protected ClassVisitor getAdapters(ClassVisitor cv) {
                cv = new CheckClassAdapter(cv);
                cv = new RemoveCallsToSystemOut(cv);
                return cv;
            }
        };
        ClassLoader loader = new TransformationTestClassLoader(cls.getPackage().getName() + ".*", transformer);
        return loader.loadClass(cls.getName());
    }
}

class TroublesomePrinter {
    public static String printSomething() {
        System.out.println("something");
        return "it did execute";
    }
}

然后执行。请注意,您不应该在没有先了解它的情况下使用此代码。不要巧合地编程

class SilentSystem {
    public static final PrintStream out = new PrintStream(new OutputStream() {
        public void write(int b) {
        }
    });
}

class RemoveCallsToSystemOut extends ClassAdapter {

    public RemoveCallsToSystemOut(ClassVisitor cv) {
        super(cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        return new MyMethodAdapter(super.visitMethod(access, name, desc, signature, exceptions));
    }

    private static class MyMethodAdapter extends MethodAdapter {
        public MyMethodAdapter(MethodVisitor mv) {
            super(mv);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
            if (opcode == Opcodes.GETSTATIC
                    && owner.equals("java/lang/System")
                    && name.equals("out")
                    && desc.equals("Ljava/io/PrintStream;")) {
                super.visitFieldInsn(opcode, "net/orfjackal/dimdwarf/aop/SilentSystem", name, desc);
            } else {
                super.visitFieldInsn(opcode, owner, name, desc);
            }
        }
    }
}
于 2010-11-25T23:16:53.613 回答
1
File file  = new File(filename);
PrintStream printStream = new PrintStream(new FileOutputStream(file));
System.setOut(printStream);
于 2010-11-25T22:06:09.380 回答
1

有2种方式。1.最简单的方法是运行你的程序并将所有输出重定向到/dev/null(如果你在unix上)或将被删除的特殊文件(对于windows)这种方法很简单,但这意味着你的所有输出都是一事无成。如果您的程序将 STDOUT 用于好事情,则您不能使用这种方式。

  1. 您可以使用 java API 设置 STDOUT。

    System.setOut(out) System.setErr(out)

现在您可以实现自己的 OutputStream:

import java.io.IOException;
import java.io.OutputStream;
import java.util.regex.Pattern;


public class FilterOutputStream extends OutputStream {
    private OutputStream out;
    private Pattern filterOut;
    public FilterOutputStream(OutputStream out, Pattern filterOut) {
        this.out = out;
        this.filterOut = filterOut;
    }


    @Override
    public void write(int b) throws IOException {
        String callerClassName = new Throwable().getStackTrace()[1].getClassName();
        if (filterOut.matcher(callerClassName).find()) {
            return;
        }
        out.write(b);
    }

}

这更好,因为您可以过滤掉不相关的输出并打印好的信息。

于 2010-11-25T22:13:50.697 回答