记录apache-commons-exec使用错误
Healthy Mind Lv3

​ 在我们需要启动一个进程的时候可以使用 java 原始的 ProccessBuilder 或使用apache-commons-exec执行命令的时候,其实commons-exec是对ProcessBuidler的一个封装

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static class ShellExecutor{
private DefaultExecutor executor ;

public ShellExecutor() {
this.executor = new DefaultExecutor();
ExecuteWatchdog watchdog = new ExecuteWatchdog(COMMAND_EXEC_TIME_OUT);
this.executor.setWatchdog(watchdog);

}

public BufferedReader exec(CommandLine commandLine) throws IOException {
PipedInputStream pis = new PipedInputStream();
//设置输出流
PipedOutputStream pos = new PipedOutputStream(pis);
//设置标准输出和错误输出
PumpStreamHandler ps = new PumpStreamHandler(pos,pos);
//设置handler 表示异步执行
DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
executor.setStreamHandler(ps);
executor.execute(commandLine, handler);
//返回执行的输出流
return new BufferedReader(new InputStreamReader(pis, Charset.defaultCharset()));
}

}

这主要发现的错误是在 CommandLine 的构建上

在 JAVA的 ProcessBudiler中 命令参数是分别放在不同的数组中的,在构建的CommandLine 应该是命令和参数分开放置,不然会报一些奇怪的错误,而且commons-exec没有log,很难排查错误

1
2
3
4
5
6
7
8
9
10
11
private CommandLine createBackupCommand(CommandConfig.BackupRestoreParam param){
Objects.requireNonNull(param.host,"数据库地址不能为空!");
String command = getShellDir()+"/pg_dump";
String commandParam = "--dbname=postgres://%s:%s@%s:%s/%s -v -f %s ";
commandParam = String.format(commandParam, param.userName, param.password, param.host, param.port, param.dbName, param.backFilePath);
//命令
CommandLine commandLine = new CommandLine(command);
//参数
commandLine.addArguments(commandParam);
return commandLine;
}

commons-exec 执行命令源码如下

org.apache.commons.exec.launcher.Java13CommandLauncher 源码

1
2
3
4
5
6
7
8
9
@Override
public Process exec(final CommandLine cmd, final Map<String, String> env,
final File workingDir) throws IOException {

final String[] envVars = EnvironmentUtils.toStrings(env);
//这里调用 CommandLine的 toStrings()方法
return Runtime.getRuntime().exec(cmd.toStrings(),
envVars, workingDir);
}

在看看 org.apache.commons.exec.CommandLine toStrings方法

1
2
3
4
5
6
public String[] toStrings() {
final String[] result = new String[arguments.size() + 1];
result[0] = this.getExecutable();
System.arraycopy(getArguments(), 0, result, 1, result.length-1);
return result;
}

这里返回的是数组

还有一个奇怪的问题,我使用单点的Java进程执行,其实是可以解析命令和参数一起放置的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.trs.exec;

import org.apache.commons.exec.*;

import java.io.*;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

/**
* @Description
* @DATE 2021.5.15 17:12
**/

public class TrsExce {

public static void main(String[] args) throws IOException {

BufferedReader reader = exeCommand(null);
if (args.length > 1){
reader = exeCommand(args[0]);
}

String line = null;
while ((line = reader.readLine()) != null){
Logger.getGlobal().info(line);
}

}

public static BufferedReader exeCommand(String cmd) throws IOException {
if(cmd == null){
cmd = "/opt/apache-tomcat-7.0.81-8085-kg3/webapps/tckg/WEB-INF/classes/shell/pg_dump --dbname=postgres://postgres:postgres@192.168.1.53:5432/trskg3_tc -v -f /home/postgres/trskg3_tc_20210518092525.dump ";

}
CommandLine cmdLine = CommandLine.parse(cmd);

DefaultExecutor executor = new DefaultExecutor();
PipedOutputStream outputStream = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(outputStream);
PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(outputStream,outputStream);
//设置输出流
executor.setStreamHandler(pumpStreamHandler);
//设置 进程最大执行时间
ExecuteWatchdog watchdog = new ExecuteWatchdog(1000 * 60 * 30);
executor.setWatchdog(watchdog);
executor.execute(cmdLine,new DefaultExecuteResultHandler()); //设置异步
BufferedReader reader = new BufferedReader(new InputStreamReader(pis, Charset.forName("GBK")));
return reader;
}

}

在单独的java进程中以上是可以执行的,但是在tomcat7中就不行,应该是类加载器加载了不同版本的commons-exec.还没找到具体原因,这里记录一下。