寫這篇的主要目的是爲了整理和記錄,歸檔以便之後查閱。segmentfault
我以前在SF上提問了一個問題:如何正確使用PipedInputStream和PipedOutputStreamapp
問題中提到的Apache Commons Execs這個庫,相比咱們原來使用原生的Runtime和Process有很多優勢。
對比我以前寫過的代碼,總結一下:異步
cmd /c
,好比把test.bat
腳本拼接成cmd /c
才向Runtime.exec方法傳入這個腳本做爲第一個參數在個人項目需求中,規定要得到腳本的退出碼,標準輸出、錯誤輸出。另外,還有可能要從標註輸出中解析獲得一個描述成功或失敗的結果,大概就是過濾腳本的標準輸出,捕獲感興趣的某一行,最後要預留超時設置的接口。還有,須要支持字符編碼設置,在Windows下對象調試程序頗有幫助,所以,咱們能夠列表表示整個需求。ide
序號 | 需求 | 是否必須 |
---|---|---|
1 | 退出碼、標準輸出、錯誤輸出 | 是 |
2 | 得到腳本提供的結果描述 | 是 |
3 | 設置超時 | 否 |
4 | 設置字符編碼 | 否 |
public abstract class AbstractCommonExecs { private String bin; //腳本 private List<String> arguments; //參數 //Constructor method //封裝返回結果 public ExecResult exec() throws IOException { try{ Executor executor = getExecutor(); //執行線程 CommandLine cmdLine = getCommandLine(); //腳本命令參數等 if(supportWatchdog()) { //是否支持監視 用於設置超時等 executor.setWatchdog(getWatchdog()); } executor.setStreamHandler(streamHandler); //設置處理標註輸出和錯誤輸出的Handler int ret = executor.execute(cmdLine); //得到退出碼 }catch(ExecuteException e) { int ret = e.getExitValue(); //若是出現異常還能得到退出碼 關於這個仔細想一想 } } }
1.1 抽象類接收腳本和參數,類型和形式還能夠是別的形式測試
1.2 對外提供的exec方法返回的是退出碼、標準輸出、錯誤輸出和腳本提供的結果描述ui
1.3 經過getXXX方法的形式能夠將具體的實現交給具體實現類來完成this
爲了從Executor
中得到標準輸出和錯誤輸出,是須要向Executor
傳入一個streamHandler
的是,這是一個基於字節流式的Handler,爲了支持字符編碼的設計,
最終處理時咱們還須要將它轉成字符流並設置目標字符編碼,好比在Windows開發環境下設置爲GBK
。編碼
executor.setStreamHandler(streamHandler); //設置處理標註輸出和錯誤輸出的Handler
這裏先提兩種很是有效的作法,一種是基於ByteArrayOutStream的,一種是官方封裝的LogOutputStream。第一種,線程
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream,errorStream); executor.setStreamHandler(streamHandler); exec.execute(cmdline); String out = outputStream.toString("gbk"); //設置編碼 String error = errorStream.toString("gbk"); //設置編碼
第二種,參考這個答案。設計
第二種是沒法設置字符編碼的,而第一種是得到了整個標準輸出和錯誤輸出後再設置字符編碼的。
若是採用這種方式,爲了知足從標準輸出解析某個特殊結果是須要對這個標準輸出作切分,再循環判斷的。
最後我採用的是PipedInputStream
和PipedOutStream
的方式,這也是爲何會有這個問題如何正確使用PipedInputStream和PipedOutputStream
。爲了讓處理標註輸出、錯誤輸出和結果描述看起來比較統一,我使用了回調
的方式。
private void readInputStream(PipedInputStream pis, OutputCallback ...cbs) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(pis, getEncoding())); String line = null; while((line = br.readLine()) != null) { for(OutputCallback cb : cbs) { cb.parse(line); //這裏能夠得到結果描述 } } }
總體思路上的抽象已經作到了,可是還不夠完全,抽象類exec方法體內業務邏輯仍是過於耦合的。
ExecResult
代碼,
public class ExecResult { private int exitCode; private String stdout; private String stderr; private String codeInfo; //getter and setter }
OutputCallback
接口代碼,
public interface OutputCallback { public void parse(String line); }
AbstractCommonExecs
代碼,
public abstract class AbstractCommonExecs { private Logger log = LoggerFactory.getLogger(AbstractCommonExecs.class); private static final String DEFAULT_ENCODING = "UTF-8"; private String encoding = DEFAULT_ENCODING; private String bin; private List<String> arguments; public AbstractCommonExecs(String bin, List<String> arguments) { this.bin = bin; this.arguments = arguments; } public ExecResult exec() throws IOException{ ExecResult er = new ExecResult(); //ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PipedOutputStream outputStream = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(outputStream); ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); CodeInfoCallback codeInfoCb = new CodeInfoCallback(); StdOutputCallback stdoutCb = new StdOutputCallback(); ErrorOutputCallback stderrCb = new ErrorOutputCallback(); String stdout = null; String stderr = null; try { Executor executor = getExecutor(); CommandLine cmdLine = getCommandLine(); log.info("Executing script {}",cmdLine.toString()); if(supportWatchdog()) { executor.setWatchdog(getWatchdog()); } PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream,errorStream); executor.setStreamHandler(streamHandler); int ret = executor.execute(cmdLine); readInputStream(pis, stdoutCb, codeInfoCb); pis.close(); readErrorStream(errorStream, stderrCb); stdout = join(stdoutCb.getLines()); stderr = stderrCb.getErrors(); log.info("output from script {} is {}", this.bin, stdout); log.info("error output from script {} is {}", this.bin, stderr); log.info("exit code from script {} is {}", this.bin, ret); er.setStdout(stdout); er.setStderr(stderr); er.setCodeInfo(codeInfoCb.getCodeInfo()); er.setExitCode(ret); return er; } catch (ExecuteException e) { if(pis != null) { readInputStream(pis, stdoutCb, codeInfoCb); pis.close(); } if(errorStream != null) { readErrorStream(errorStream, stderrCb); } stdout = join(stdoutCb.getLines()); stderr = stderrCb.getErrors(); int ret = e.getExitValue(); log.info("output from script {} is {}", this.bin, stdout); log.info("error output from script {} is {}", this.bin, stderr); log.info("exit code from script {} is {}", this.bin, ret); er.setStdout(stdout); er.setStderr(stderr); er.setCodeInfo(codeInfoCb.getCodeInfo()); er.setExitCode(ret); return er; } } /** * 接口回調的方式解析腳本的錯誤輸出 * @param baos * @param cbs * @throws IOException */ private void readErrorStream(ByteArrayOutputStream baos, OutputCallback ...cbs) throws IOException { String err = baos.toString(getEncoding()); for(OutputCallback cb : cbs) { cb.parse(err); } } /** * 接口回調的方式解析腳本的標準輸出 * @param pis * @param cbs * @throws IOException */ private void readInputStream(PipedInputStream pis, OutputCallback ...cbs) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(pis, getEncoding())); String line = null; while((line = br.readLine()) != null) { for(OutputCallback cb : cbs) { cb.parse(line); } } } public Executor getExecutor() { Executor executor = new DefaultExecutor(); executor.setWorkingDirectory(new File(this.bin).getParentFile()); return executor; } public CommandLine getCommandLine() { String fullCommand = bin + join(arguments); return CommandLine.parse(fullCommand); } protected String join(List<String> arguments) { if(arguments == null || arguments.isEmpty()) { return ""; } StringBuilder sb = new StringBuilder(); for(String arg : arguments) { sb.append(" ").append(arg); } return sb.toString(); } /** * @return the encoding */ protected String getEncoding() { return encoding; } /** * @param encoding the encoding to set */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * @return the bin */ protected String getBin() { return bin; } /** * @param bin the bin to set */ public void setBin(String bin) { this.bin = bin; } /** * @return the arguments */ protected List<String> getArguments() { return arguments; } /** * @param arguments the arguments to set */ public void setArguments(List<String> arguments) { this.arguments = arguments; } public abstract boolean supportWatchdog(); public abstract ExecuteWatchdog getWatchdog(); }
public class GbkCommonExecs extends AbstractCommonExecs{ /** * @param bin * @param arguments */ public GbkCommonExecs(String bin, List<String> arguments) { super(bin, arguments); } /* (non-Javadoc) * @see com.bingosoft.proxy.helper.AbstractCommonExecs#supportWatchdog() */ @Override public boolean supportWatchdog() { // TODO implement AbstractCommonExecs.supportWatchdog return false; } /* (non-Javadoc) * @see com.bingosoft.proxy.helper.AbstractCommonExecs#getWatchdog() */ @Override public ExecuteWatchdog getWatchdog() { // TODO implement AbstractCommonExecs.getWatchdog return null; } //提供這個編碼便可 public String getEncoding() { return "GBK"; } public static void main(String[] args) throws IOException { String bin = "ping"; String arg1 = "127.0.0.1"; List<String> arguments = new ArrayList<String>(); arguments.add(arg1); AbstractCommonExecs executable = new GbkCommonExecs(bin, arguments); ExecResult er = executable.exec(); System.out.println(er.getExitCode()); System.out.println(er.getStdout()); System.out.println(er.getStderr()); } }
設置監視狗就能設置超時
public class TimeoutCommonExecs extends AbstractCommonExecs{ private Logger log = LoggerFactory.getLogger(TimeoutCommonExecs.class); private long timeout = 10 * 1000; // 10 seconds public TimeoutCommonExecs(String bin, List<String> arguments) { super(bin, arguments); } public TimeoutCommonExecs(String bin, List<String> arguments, long timeout) { super(bin, arguments); this.timeout = timeout; } public boolean supportWatchdog() { return true; // 使用監視狗 監視腳本執行超時的狀況 } public ExecuteWatchdog getWatchdog() { ExecuteWatchdog watchdog = new ExecuteWatchdog(this.timeout); return watchdog; } /** * @return the timeout */ public long getTimeout() { return timeout; } /** * @param timeout the timeout to set */ public void setTimeout(long timeout) { this.timeout = timeout; } }
爲了方便在Windows下測試
public class TimeoutGbkCommonExecs extends TimeoutCommonExecs{ public TimeoutGbkCommonExecs(String bin, List<String> arguments, long timeout) { super(bin, arguments, timeout); } //字符編碼設置 public String getEncoding() { return "GBK"; } public static void main(String[] args) throws IOException { String bin = "ping"; String arg1 = "-t"; //不斷ping String arg2 = "127.0.0.1"; List<String> arguments = new ArrayList<String>(); arguments.add(arg1); arguments.add(arg2); AbstractCommonExecs executable = new TimeoutGbkCommonExecs(bin, arguments, 5 * 1000); ExecResult er = executable.exec(); System.out.println(er.getExitCode()); System.out.println(er.getStdout()); System.out.println(er.getStderr()); } }