| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -78,15 +78,18 @@ private static DefaultExecutor getExecutor() | |||
| 78 | 78 | ||
| 79 | 79 | // 设置超时时间,超时后,ExecuteWatchdog.timeoutOccured(Watchdog w)调用Process.destroy()杀死命令行进程,类似于kill命令,但是此时命令行进程的返回值是不可信的,就是说无法确认是程序正常执行完毕,返回了一个值,还是因为超时返回了一个值。为了区分是否超时,可以配合使用DefaultExecuteResultHandler.waitFor(long timeout)进行区分,具体操作是:假设外界要求的超时时间是60秒,那么watchdog设置成60+10秒,waitFor参数设置成60秒,这样就能做到可以区分是否超时,并在超时后杀死命令行进程。 | |
| 80 | 80 | // 通过测试发现: | |
| 81 | - // (1)在window上:命令行进程被Process.destroy()杀死,返回值为1;在任务管理器被结束进程杀死,返回值为1。 | ||
| 81 | + // (1)在windows上:命令行进程被Process.destroy()杀死,返回值为1;在任务管理器被结束进程杀死,返回值为1。 | ||
| 82 | 82 | // (2)在linux(suse)上:命令行进程被Process.destroy()杀死,返回值为143;在任务管理器被结束进程杀死,返回值为137。 | |
| 83 | 83 | ExecuteWatchdog watchdog = new ExecuteWatchdog(20 * 1000L); | |
| 84 | 84 | executor.setWatchdog(watchdog); | |
| 85 | 85 | ||
| 86 | 86 | // 设置期望返回值,不符合抛异常ExecuteException。这里置为空,禁用此功能,由业务自己处理返回值,无需业务捕获异常。 | |
| 87 | 87 | executor.setExitValues(null); | |
| 88 | 88 | ||
| 89 | - // 设置关闭钩子,当jvm退出时,杀死正在执行的命令行进程 | ||
| 89 | + // 设置关闭钩子,当jvm退出时,杀死正在执行的命令行进程。 | ||
| 90 | + // 在不设置关闭钩子的情况下,通过测试发现: | ||
| 91 | + // (1)windows进程退出特点(使用procexp.exe观察):父进程退出后,子进程还是在运行的,并且子进程的父进程id无变化。(感觉有些不合理,父进程id已经不存在了,不知道windows为什么这么设计) | ||
| 92 | + // (2)linux进程退出特点(使用ps观察):父进程退出后,子进程还是在运行的,但是子进程的父进程id会变成1,也就是1号进程。 | ||
| 90 | 93 | executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); | |
| 91 | 94 | ||
| 92 | 95 | return executor; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,124 @@ | |||
| 1 | + package com.xdc.basic.api.apache.commons.exec.framwaork; | ||
| 2 | + | ||
| 3 | + import java.io.File; | ||
| 4 | + import java.util.Map; | ||
| 5 | + | ||
| 6 | + import org.apache.commons.lang3.builder.ToStringBuilder; | ||
| 7 | + import org.apache.commons.lang3.builder.ToStringStyle; | ||
| 8 | + | ||
| 9 | + public class ExecCommand | ||
| 10 | + { | ||
| 11 | + /** | ||
| 12 | + * 执行的命令,支持管道和重定向。 | ||
| 13 | + */ | ||
| 14 | + private String command; | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 命令行调用的标准输入流。比如删除文件要确认,测试命令:touch xdc.log && rm -i xdc.log | ||
| 18 | + */ | ||
| 19 | + private String stdIn = null; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 设置执行目录。一般是不需要的,有些场景需要切换到其他目录执行目录。 | ||
| 23 | + */ | ||
| 24 | + private File workingDirectory = new File("."); | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 可以定制环境变量。留空的话,默认继承java程序的环境变量。 | ||
| 28 | + */ | ||
| 29 | + private Map<Object, Object> environment = null; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 超时时间,单位毫秒。超时后,再等待10秒钟,杀死命令行进程,否则会残留,浪费资源。等10秒的原因是为了区分正常返回和执行超时。 | ||
| 33 | + * 因为杀死进程后,进程也是有返回值的,看起来与执行完成没有区别。虽然执行成功(惯例是0)、失败、超时被杀死的返回码不同,也是不能根据返回值判断是否超时的,那样代码就跟操作系统的耦合了。 | ||
| 34 | + */ | ||
| 35 | + private long timeout = 60 * 1000L; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * 期待的返回值列表,只有返回值包含在此列表中,才认为此次调用成功。根据惯例,值0表示正常终止。此处提供定制能力,以适应一些设计古怪的可执行命令。 | ||
| 39 | + */ | ||
| 40 | + private int[] expectedExitValues = new int[] { 0 }; | ||
| 41 | + | ||
| 42 | + public ExecCommand(String command) | ||
| 43 | + { | ||
| 44 | + super(); | ||
| 45 | + this.command = command; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public String getCommand() | ||
| 49 | + { | ||
| 50 | + return command; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public void setCommand(String command) | ||
| 54 | + { | ||
| 55 | + this.command = command; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + public String getStdIn() | ||
| 59 | + { | ||
| 60 | + return stdIn; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + public void setStdIn(String stdIn) | ||
| 64 | + { | ||
| 65 | + this.stdIn = stdIn; | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + public File getWorkingDirectory() | ||
| 69 | + { | ||
| 70 | + return workingDirectory; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + public void setWorkingDirectory(File workingDirectory) | ||
| 74 | + { | ||
| 75 | + this.workingDirectory = workingDirectory; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + public Map<Object, Object> getEnvironment() | ||
| 79 | + { | ||
| 80 | + return environment; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + public void setEnvironment(Map<Object, Object> environment) | ||
| 84 | + { | ||
| 85 | + this.environment = environment; | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + public int[] getExpectedExitValues() | ||
| 89 | + { | ||
| 90 | + return expectedExitValues; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + public void setExpectedExitValues(int[] expectedExitValues) | ||
| 94 | + { | ||
| 95 | + this.expectedExitValues = (expectedExitValues == null ? null : (int[]) expectedExitValues.clone()); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + public void setExpectedExitValue(int expectedExitValue) | ||
| 99 | + { | ||
| 100 | + this.setExpectedExitValues(new int[] { expectedExitValue }); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + public long getTimeout() | ||
| 104 | + { | ||
| 105 | + return timeout; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + public void setTimeout(long timeout) | ||
| 109 | + { | ||
| 110 | + if (timeout < 10 * 1000) | ||
| 111 | + { | ||
| 112 | + throw new IllegalArgumentException( | ||
| 113 | + String.format("Timeout[%s] is not vaild. Timeout must be greater than 10000.", timeout)); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + this.timeout = timeout; | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + @Override | ||
| 120 | + public String toString() | ||
| 121 | + { | ||
| 122 | + return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | ||
| 123 | + } | ||
| 124 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,140 @@ | |||
| 1 | + package com.xdc.basic.api.apache.commons.exec.framwaork; | ||
| 2 | + | ||
| 3 | + import org.apache.commons.lang3.builder.ToStringBuilder; | ||
| 4 | + import org.apache.commons.lang3.builder.ToStringStyle; | ||
| 5 | + | ||
| 6 | + public class ExecResult | ||
| 7 | + { | ||
| 8 | + /** | ||
| 9 | + * 命令行调用框架异常,出现此返回值,需要考虑修改框架缺陷。 | ||
| 10 | + */ | ||
| 11 | + public static final int EXEC_FAILURE = -1; | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * 命令行调用超时,出现此返回值,需要排查脚本和可执行命令中是否有死循环等错误。 | ||
| 15 | + */ | ||
| 16 | + public static final int EXEC_TIMEOUT = -2; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 期待的返回值列表,只有返回值包含在此列表中,才认为此次调用成功。根据惯例,值0表示正常终止。此处提供定制能力,以适应一些设计古怪的可执行命令。 | ||
| 20 | + */ | ||
| 21 | + private int[] expectedExitValues = new int[] { 0 }; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 命令行调用的返回值 | ||
| 25 | + */ | ||
| 26 | + private int exitValue; | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 命令行调用的标准输出流 | ||
| 30 | + */ | ||
| 31 | + private String stdOut; | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * 命令行调用的标准错误流 | ||
| 35 | + */ | ||
| 36 | + private String stdErr; | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * 命令行调用框架异常。 | ||
| 40 | + */ | ||
| 41 | + private Throwable throwable; | ||
| 42 | + | ||
| 43 | + public ExecResult(int[] expectedExitValues, int exitValue, String stdOut, String stdErr) | ||
| 44 | + { | ||
| 45 | + super(); | ||
| 46 | + this.expectedExitValues = expectedExitValues; | ||
| 47 | + this.exitValue = exitValue; | ||
| 48 | + this.stdOut = stdOut; | ||
| 49 | + this.stdErr = stdErr; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public int[] getExpectedExitValues() | ||
| 53 | + { | ||
| 54 | + return expectedExitValues; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public void setExpectedExitValues(int[] expectedExitValues) | ||
| 58 | + { | ||
| 59 | + this.expectedExitValues = expectedExitValues; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + public int getExitValue() | ||
| 63 | + { | ||
| 64 | + return exitValue; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public void setExitValue(int exitValue) | ||
| 68 | + { | ||
| 69 | + this.exitValue = exitValue; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + public String getStdOut() | ||
| 73 | + { | ||
| 74 | + return stdOut; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + public void setStdOut(String stdOut) | ||
| 78 | + { | ||
| 79 | + this.stdOut = stdOut; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + public String getStdErr() | ||
| 83 | + { | ||
| 84 | + return stdErr; | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + public void setStdErr(String stdErr) | ||
| 88 | + { | ||
| 89 | + this.stdErr = stdErr; | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + public Throwable getThrowable() | ||
| 93 | + { | ||
| 94 | + return throwable; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + public void setThrowable(Throwable throwable) | ||
| 98 | + { | ||
| 99 | + this.throwable = throwable; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + @Override | ||
| 103 | + public String toString() | ||
| 104 | + { | ||
| 105 | + return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + public boolean isSuccess() | ||
| 109 | + { | ||
| 110 | + if (exitValue == EXEC_FAILURE || exitValue == EXEC_TIMEOUT) | ||
| 111 | + { | ||
| 112 | + return false; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + if (throwable != null) | ||
| 116 | + { | ||
| 117 | + return false; | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + if (expectedExitValues == null || expectedExitValues.length == 0) | ||
| 121 | + { | ||
| 122 | + return true; | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + for (int i = 0; i < expectedExitValues.length; i++) | ||
| 126 | + { | ||
| 127 | + if (expectedExitValues[i] == exitValue) | ||
| 128 | + { | ||
| 129 | + return true; | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + return false; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + public boolean isFailure(final int exitValue) | ||
| 137 | + { | ||
| 138 | + return !isSuccess(); | ||
| 139 | + } | ||
| 140 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,112 @@ | |||
| 1 | + package com.xdc.basic.api.apache.commons.exec.framwaork; | ||
| 2 | + | ||
| 3 | + import java.io.ByteArrayInputStream; | ||
| 4 | + import java.io.ByteArrayOutputStream; | ||
| 5 | + | ||
| 6 | + import org.apache.commons.exec.CommandLine; | ||
| 7 | + import org.apache.commons.exec.DefaultExecuteResultHandler; | ||
| 8 | + import org.apache.commons.exec.DefaultExecutor; | ||
| 9 | + import org.apache.commons.exec.ExecuteWatchdog; | ||
| 10 | + import org.apache.commons.exec.PumpStreamHandler; | ||
| 11 | + import org.apache.commons.exec.ShutdownHookProcessDestroyer; | ||
| 12 | + import org.apache.commons.lang3.StringUtils; | ||
| 13 | + import org.apache.commons.lang3.SystemUtils; | ||
| 14 | + | ||
| 15 | + import com.xdc.basic.commons.codec.BytesUtil; | ||
| 16 | + | ||
| 17 | + public class ExecTool | ||
| 18 | + { | ||
| 19 | + // 获取命令行的编码: | ||
| 20 | + // (1)windows下获取cmd.exe的编码,打开命令行后能在属性里看到的。 | ||
| 21 | + // (2)linux下获取当前shell(如bash)的编码,执行locale可以查看。 | ||
| 22 | + private static final String CMD_ENCODING = System.getProperty("sun.jnu.encoding"); | ||
| 23 | + | ||
| 24 | + public static ExecResult exec(ExecCommand execCommand) | ||
| 25 | + { | ||
| 26 | + // 创建执行命令行 | ||
| 27 | + CommandLine commandLine = null; | ||
| 28 | + if (SystemUtils.IS_OS_UNIX) | ||
| 29 | + { | ||
| 30 | + commandLine = CommandLine.parse("sh"); | ||
| 31 | + commandLine.addArguments(new String[] { "-c", execCommand.getCommand() }, false); | ||
| 32 | + } | ||
| 33 | + else if (SystemUtils.IS_OS_WINDOWS) | ||
| 34 | + { | ||
| 35 | + commandLine = CommandLine.parse("cmd"); | ||
| 36 | + commandLine.addArguments(new String[] { "/c", execCommand.getCommand() }, false); | ||
| 37 | + } | ||
| 38 | + else | ||
| 39 | + { | ||
| 40 | + throw new RuntimeException("Unsupport OS type. Suppurt OS type:[UNIX-like, Windows]."); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + // 创建执执行器 | ||
| 44 | + DefaultExecutor executor = new DefaultExecutor(); | ||
| 45 | + | ||
| 46 | + // 设置标准输出流,标准错误留,标准输入流。值得注意的是ByteArrayInputStream和ByteArrayOutputStream是不用关闭的,因为不是它们不是真正的IO流,未与设备交互,不过是Java内部缓冲用的。其实查看他的的close方法,是空实现的。 | ||
| 47 | + ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); | ||
| 48 | + ByteArrayOutputStream stdErr = new ByteArrayOutputStream(); | ||
| 49 | + ByteArrayInputStream stdIn = null; | ||
| 50 | + if (StringUtils.isNotEmpty(execCommand.getStdIn())) | ||
| 51 | + { | ||
| 52 | + stdIn = new ByteArrayInputStream(BytesUtil.string2Bytes(execCommand.getStdIn(), CMD_ENCODING)); | ||
| 53 | + } | ||
| 54 | + executor.setStreamHandler(new PumpStreamHandler(stdOut, stdErr, stdIn)); | ||
| 55 | + | ||
| 56 | + // 设置看门狗,超时后,再等待10秒钟,杀死命令行进程,否则会残留,浪费资源。等10秒的原因是为了区分正常返回和执行超时。 | ||
| 57 | + // 因为杀死进程后,进程也是有返回值的,看起来与执行完成没有区别。虽然执行成功(惯例是0)、失败、超时被杀死的返回码不同,也是不能根据返回值判断是否超时的,那样代码就跟操作系统的耦合了。 | ||
| 58 | + ExecuteWatchdog watchdog = new ExecuteWatchdog(execCommand.getTimeout() + 10 * 1000L); | ||
| 59 | + executor.setWatchdog(watchdog); | ||
| 60 | + | ||
| 61 | + // 设置执行目录 | ||
| 62 | + executor.setWorkingDirectory(execCommand.getWorkingDirectory()); | ||
| 63 | + | ||
| 64 | + // 设置期望返回值,不符合抛异常ExecuteException。这里置为空,禁用此功能,由业务自己处理返回值,无需业务捕获异常。 | ||
| 65 | + executor.setExitValues(null); | ||
| 66 | + | ||
| 67 | + // 设置关闭钩子,当jvm退出时,杀死正在执行的命令行进程。 | ||
| 68 | + executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); | ||
| 69 | + | ||
| 70 | + // 创建执行结果处理器 | ||
| 71 | + DefaultExecuteResultHandler defaultExecuteResultHandler = new DefaultExecuteResultHandler(); | ||
| 72 | + | ||
| 73 | + try | ||
| 74 | + { | ||
| 75 | + // 执行命令 | ||
| 76 | + executor.execute(commandLine, execCommand.getEnvironment(), defaultExecuteResultHandler); | ||
| 77 | + | ||
| 78 | + // 同步等待命令行进程完成 | ||
| 79 | + defaultExecuteResultHandler.waitFor(execCommand.getTimeout()); | ||
| 80 | + } | ||
| 81 | + catch (Throwable e) | ||
| 82 | + { | ||
| 83 | + // 执行失败,获取异常 | ||
| 84 | + ExecResult execResult = new ExecResult(execCommand.getExpectedExitValues(), ExecResult.EXEC_FAILURE, | ||
| 85 | + BytesUtil.bytes2String(stdOut.toByteArray(), CMD_ENCODING), | ||
| 86 | + BytesUtil.bytes2String(stdErr.toByteArray(), CMD_ENCODING)); | ||
| 87 | + execResult.setThrowable(e); | ||
| 88 | + | ||
| 89 | + return execResult; | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + if (defaultExecuteResultHandler.hasResult()) | ||
| 93 | + { | ||
| 94 | + // 执行完成,获取返回值 | ||
| 95 | + ExecResult execResult = new ExecResult(execCommand.getExpectedExitValues(), | ||
| 96 | + defaultExecuteResultHandler.getExitValue(), | ||
| 97 | + BytesUtil.bytes2String(stdOut.toByteArray(), CMD_ENCODING), | ||
| 98 | + BytesUtil.bytes2String(stdErr.toByteArray(), CMD_ENCODING)); | ||
| 99 | + | ||
| 100 | + execResult.setThrowable(defaultExecuteResultHandler.getException()); | ||
| 101 | + | ||
| 102 | + return execResult; | ||
| 103 | + } | ||
| 104 | + else | ||
| 105 | + { | ||
| 106 | + // 执行超时,返回值设置为空 | ||
| 107 | + return new ExecResult(execCommand.getExpectedExitValues(), ExecResult.EXEC_TIMEOUT, | ||
| 108 | + BytesUtil.bytes2String(stdOut.toByteArray(), CMD_ENCODING), | ||
| 109 | + BytesUtil.bytes2String(stdErr.toByteArray(), CMD_ENCODING)); | ||
| 110 | + } | ||
| 111 | + } | ||
| 112 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments