java_命令执行
Runtime类分析
先看一个命令执行的demo,如下
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class RuntimeTest {
public static void main(String[] args) throws IOException {
Runtime runtime = Runtime.getRuntime();
Process result = runtime.exec("whoami");
InputStream in = result.getInputStream();
byte[] bytes = new byte[1024];
int readsize = 0;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((readsize = in.read(bytes)) != -1){
out.write(bytes,0,readsize);
}
System.out.println(out.toString());
}
}
很明显,是通过Runtime::exec
方法进行系统命令执行的,跟进Runtime类,发现有如下六个exec重载方法
public Process exec(String command)
public Process exec(String command, String[] envp)
public Process exec(String command, String[] envp, File dir)
public Process exec(String cmdarray[])
public Process exec(String[] cmdarray, String[] envp)
public Process exec(String[] cmdarray, String[] envp, File dir)
这六个exec方法的区别如下:
- 第一个重载表示在单独的进程中执行指定的字符串命令
- 第二个重载表示在指定环境的单独的进程中执行指定的字符串命令
- 第三个重载表示在指定环境和工作目录的单独的进程中执行指定的字符串命令
- 第一个重载表示在单独的进程中执行指定的字符串命令和变量
- 第二个重载表示在指定环境的单独的进程中执行指定的字符串命令和变量
- 第三个重载表示在指定环境和工作目录的单独的进程中执行指定的字符串命令和变量
调用链分析
跟进exec方法,发现调用其重载的exec方法
继续跟进,这个方法又再调用重载的exec方法
继续跟进,到下面的这个方法exec调用链就结束了,你会发现,无论一开始调用的是哪个重载exec方法,最终都会调用到下面这个exec方法
这个方法调用ProcessBuilder::start
方法,跟进发现其调用ProcessImpl::start
方法
到这里基本就结束了,调用链为Runtime::exec->ProcessBuilder::exec->ProcessImpl::exec
根据调用链的三种执行命令代码
根据上面的调用链分析,可以有三种命令执行的方式,分别为Runtime、ProcessBuilder、ProcessImpl
Runtime执行命令
Runtime又可以有如下三个
- 第一个就是上面的demo
- 第二个是通过反射执行,通过getRuntime方法获取Runtime类实例,为什么要通过getRuntime方法获取呢?因为Runtime的构造函数是私有的,无法在外部进行实例化,通过一个静态方法返回Runtime对象,这样就保证了只有一个Runtime对象实例!这其实就是单例模式
为什么要这样设计呢?借用P神一段话有同学就比较好奇,为什么会有类的构造方法是私有的,难道他不想让用户使用这个类吗?这其实涉及 到很常见的设计模式:“单例模式”。(有时候工厂模式也会写成类似)
比如,对于Web应用来说,数据库连接只需要建立一次,而不是每次用到数据库的时候再新建立一个连 接,此时作为开发者你就可以将数据库连接使用的类的构造函数设置为私有,然后编写一个静态方法来 获取
执行命令代码如下
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
public class Demo02 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.Runtime");
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Method execMethod = clazz.getMethod("exec", String.class);
Object runtimeObject = getRuntimeMethod.invoke(Runtime.class);
Process process = (Process)execMethod.invoke(runtimeObject, "whoami");
InputStream inputStream = process.getInputStream();
byte[] bytes = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int readsize = 0;
while ((readsize = inputStream.read(bytes)) != -1){
byteArrayOutputStream.write(bytes,0,readsize);
}
System.out.println(byteArrayOutputStream.toString());
}
}
这里可能会有一个疑问,就是getRuntimeMethod方法的执行为什么传入一个class对象,不应该传入执行方法 的对象吗?其实这里可以对比一下java的静态方法调用,是这样的类名.静态方法名,那么对比到反射里就不难 理解了!
3. 第三个就是通过构造方法获取Runtime对象,但是Runtime的构造方法是私有的,可以通过getDeclaredConstructor方法获取私有的构造方法
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Demo03 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.Runtime");
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object runtimeObject = declaredConstructor.newInstance();
Method execMethod = clazz.getMethod("exec", String.class);
Process process = (Process)execMethod.invoke(runtimeObject, "whoami");
InputStream inputStream = process.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int readsize = 0;
while ((readsize = inputStream.read(bytes)) != -1){
byteArrayOutputStream.write(bytes,0,readsize);
}
System.out.println(byteArrayOutputStream.toString());
}
}
这里需要注意,获取到的构造方法是私有的,所以需要通过setAccessible方法关闭java访问语言检查,不然无法进行实例化
ProcessBuilder
查看Runtime最后调用的exec方法,其调用ProcessBuilder::start
方法执行命令,那么直接反射执行这个方法即可
注意的地方是newInstance方法本来接收的就是可边长参数(也就是数组),传入的也是一个数组,那么就叠加为一个二维数组
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Demo04 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = clazz.getConstructor(String[].class);
Method startMethod = clazz.getMethod("start");
Process process = (Process)startMethod.invoke(constructor.newInstance(new String[][]{{"whoami"}}));
InputStream inputStream = process.getInputStream();
byte[] bytes = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int readsize = 0;
while ((readsize = inputStream.read(bytes)) != -1){
byteArrayOutputStream.write(bytes,0,readsize);
}
System.out.println(byteArrayOutputStream.toString());
}
}
ProcessImpl
跟进ProcessBuilder::start
方法,其调用ProcessImpl.start
方法进行命令执行,那么直接反射执行这个方法即可
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;
public class Demo05 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.ProcessImpl");
Method startMethod = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
startMethod.setAccessible(true);
Process process = (Process)startMethod.invoke(clazz, new String[]{"whoami"}, null, null, null, false);
InputStream inputStream = process.getInputStream();
byte[] bytes = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int readsize = 0;
while ((readsize = inputStream.read(bytes)) != -1){
byteArrayOutputStream.write(bytes,0,readsize);
}
System.out.println(byteArrayOutputStream.toString());
}
}
这里注意的是ProcessImpl类和其start方法都没有访问修饰符,那么就是默认,在java里面默认就是同一包下可以访问,也就是在java.lang包下,所以这里需要通过setAccessible关闭java访问语言检查。