java_命令执行

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方法的区别如下:

  1. 第一个重载表示在单独的进程中执行指定的字符串命令
  2. 第二个重载表示在指定环境的单独的进程中执行指定的字符串命令
  3. 第三个重载表示在指定环境和工作目录的单独的进程中执行指定的字符串命令
  4. 第一个重载表示在单独的进程中执行指定的字符串命令和变量
  5. 第二个重载表示在指定环境的单独的进程中执行指定的字符串命令和变量
  6. 第三个重载表示在指定环境和工作目录的单独的进程中执行指定的字符串命令和变量

调用链分析

跟进exec方法,发现调用其重载的exec方法
image-20211128203521826
继续跟进,这个方法又再调用重载的exec方法
image-20211128203554157
继续跟进,到下面的这个方法exec调用链就结束了,你会发现,无论一开始调用的是哪个重载exec方法,最终都会调用到下面这个exec方法
image-20211128203752880
这个方法调用ProcessBuilder::start方法,跟进发现其调用ProcessImpl::start方法
image-20211128203926794
到这里基本就结束了,调用链为Runtime::exec->ProcessBuilder::exec->ProcessImpl::exec

根据调用链的三种执行命令代码

根据上面的调用链分析,可以有三种命令执行的方式,分别为Runtime、ProcessBuilder、ProcessImpl

Runtime执行命令

Runtime又可以有如下三个

  1. 第一个就是上面的demo
  2. 第二个是通过反射执行,通过getRuntime方法获取Runtime类实例,为什么要通过getRuntime方法获取呢?因为Runtime的构造函数是私有的,无法在外部进行实例化,通过一个静态方法返回Runtime对象,这样就保证了只有一个Runtime对象实例!这其实就是单例模式
    image-20211128205243342
    为什么要这样设计呢?借用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方法执行命令,那么直接反射执行这个方法即可
image-20211128212419955
注意的地方是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访问语言检查。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值