Java调用系统命令
我尝试过两种系统命令调用方式:一种是JNI,可以对系统接口进行对接,有点像Java→C++→Windows API,更底层,但这里不赘述;一种是通过Runtime调用系统命令。
先看一下Runtime的初始化源码
public class Runtime {
private static Runtime currentRuntime = new Runtime();
…………
public static Runtime getRuntime() {
return currentRuntime;
}
}
通过上述源码可以发现Runtime是很典型的饿汉单例,通过getRuntime()获取唯一的Runtime实例:Runtime runtime = Runtime.getRuntime()
,通过runtime.exec()
执行相关命令。
runtime.exec()本身可能抛出IOException异常,而且参数很杂:
一般的,第一个参数是命令,第二个参数是变量或命令参数,第三个变量是要执行命令的目录。
文档:https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html
举例:
执行一个简单的命令,比如zip压缩:
runtime.exec("zip -r compressed.zip /www/picture");
执行带有参数的命令,比如备份数据库到文件
String[] paramArr = {" > /www/backup/myblog.sql"};
runtime.exec("/usr/bin/mysqldump -u root -p pwd dbname", paramArr);
在某个目录执行带有参数的命令
String[] paramArr = {" > /www/backup/myblog.sql"};
runtime.exec("mysqldump -u root -p pwd dbname", paramArr, new File("/usr/bin/"));
runtime.exec()会返回Process对象,用于对该命令所产生进程的控制。
文档:https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html
示例备份数据库和文件夹
mysql和mysqldump在5.6后更新了有关密码的命令行策略,凡是含有明文密码的命令全部给出警告。通过runtime.exec()的第二个参数可以设置后续输入的非明文密码,但如果密码中存在像”#”这类的特殊字符,可能会提示错误密码错误。所以建议将mysql的密码写在my.cnf中。
通过mysqldump --help
命令可知cnf文件的优先级分别为:
/etc/my.cnf、/etc/mysql/my.cnf、/usr/etc/my.cnf、~/.my.cnf
所以在/etc/my.cnf的最后添加以下字段:
[mysqldump]
user=root
password='pwd'
主要代码如下,省略了部分属性:
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class BackupService {
private Thread mysqlBackup;
private Thread pictureBackup;
//内部类对Runnable接口进行继承,并重写run(),方便以后扩展
private class MysqlRunnable implements Runnable {
@Override
public void run() {
Runtime runtime = Runtime.getRuntime();
//备份数据库语句,由于在my.cnf中设置了用户名和密码,所以在命令中无需输入
String command = "mysqldump dbname --result-file=/www/backup/db.sql";
try {
Process process = runtime.exec(command);
/*Process的waitFor的作用是阻塞当前进程,直到process的进程运行完毕,可能会抛出InterruptedException异常。
waitFor有两种方法,一种是不带超时设置的,一种是像下面这样带超时设置的。
第一个参数是超时时间,第二个参数是TimeUnit的时间单位。
超时后,waitFor会停止阻塞并返回false */
process.waitFor(Long.parseLong(maxTimeStr), TimeUnit.SECONDS);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
private class PictureRunnable implements Runnable {
@Override
public void run() {
Runtime runtime = Runtime.getRuntime();
//使用zip压缩图片文件夹
String command = "zip -r compressed.zip /www/picture";
try {
Process process = runtime.exec(command);
process.waitFor(Long.parseLong(maxTimeStr), TimeUnit.SECONDS);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
//加了锁,配合thread.isAlive(),防止线程多开
public synchronized Boolean backupMysql() {
if (this.mysqlBackup == null || !this.mysqlBackup.isAlive()) {
//备份属于小业务,多余的线程就让GC去处理吧
this.mysqlBackup = new Thread(new MysqlRunnable());
//判断并创建备份文件的路径上的未创建的文件夹
File directory = new File(backupPath + mysqlBackupFileName).getParentFile();
if (directory.exists() || directory.mkdirs()) {
this.mysqlBackup.start();
}
}
//返回当前线程状态。除非父子线程的优先级差距很大,否则一般都是true
return this.mysqlBackup.isAlive();
}
public synchronized Boolean backupPicture() {
if (this.pictureBackup == null || !this.pictureBackup.isAlive()) {
this.pictureBackup = new Thread(new PictureRunnable());
File directory = new File(backupPath + pictureBackupFileName).getParentFile();
if (directory.exists() || directory.mkdirs()) {
this.pictureBackup.start();
}
}
return this.pictureBackup.isAlive();
}
}