JAVA 本地命令执行类

这里自己记录了下Runtime类以及ProcessBuilder,ProcessImpl之间的关系,还有通过反射来实现Runtime,ProcessBuilder, ProcessImpl

RunTime类进行exec的流程

其实它的本质是一个: 运行时环境,听起来好像不太好理解,这个"运行时环境其实也就是"java虚拟机的运行环境"!

首先先看源码:Runtime类

public class Runtime,它是一个公有类

构造函数:private Runtime() {},构造函数为私有,说明不能直接实例化该对象

private static Runtime currentRuntime = new Runtime();,这句话可以看出来currentRuntime这个属性本身是一个Runtime的类

继续看,那么应该就知道了,这个Runtime类是遵循单例模式的,每次运行的对象就只有一个,所以在java程序中不同线程通过调用Runtime.getRuntime()获得的是同一个对象实例,也就是说一个java进程中只有一个Runtime实例

public static Runtime getRuntime() {
    return currentRuntime;
}

Runtime有个exec的方法,并且对应的重载方法有多个,并且执行完之后返回的是Process类的实例,是一个进程类的实例对象

public Process exec(String command) throwsIOException
public Process exec(String command,String[] envp)
public Process exec(String command,String[] envp, File dir)
public Process exec(String cmdarray[])throws IOException

这里得细说exec这个方法,该exec返回的是一个Process类的实例,那么这里就可以知道了它实则是创建了一个进程,让进程来执行我们传入的command命令

这里得跟下exec方法的大概流程,这里执行的是一条命令执行的代码,如下:
InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();

首先跟进exec的方法,发现还是一个exec方法

它继续跟进exec方法中,继续跟

第三个exec方法中,如下图所示,可以看到又会继续生成一个ProcessBuilder的实例对象,并且执行start方法

又会来到如下的方法start中:

start方法中又会继续执行ProcessImpl类中的start静态方法,该ProcessImpl类是Process的子类

开始执行ProcessImpl的start方法如下:

它最终会返回一个ProcessImpl的实例化的对象,如下

在ProcessImpl构造函数中,它会创建一个句柄,自己也不太清楚是啥,最后就将这个ProcessImpl实例化的对象作为返回值,大概的流程就是上面这样

总流程的调用链如下:

第一次exec -> 第二次exec -> 第三次exec -> new ProcessBuilder(...).start() -> ProcessImpl.start(...) -> return new ProcessImpl(...) ,最后拿到的就是ProcessImpl对象!!!

关于ProcessBuilder的文章: https://www.cnblogs.com/mistor/p/6129682.html

顺便来一个流程图:

Runtime反射实现exec的执行

最简单的一条命令,如下:

InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();

如果通过反射实现的话,这里来反射来实现调用exec的代码如下:

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        String className = "java.lang.Runtime";
        Class  runtimeClass1 = Class.forName(className);

        Constructor runtimeConstructor =  runtimeClass1.getDeclaredConstructor();
        runtimeConstructor.setAccessible(true);
        Runtime runtime = (Runtime) runtimeConstructor.newInstance();
        Method ExecMethod = runtimeClass1.getMethod("exec", String.class);
        String cmd = "whoami";
        Process p = (Process) ExecMethod.invoke(runtime, cmd);

        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        System.out.println(baos.toString());
    }
}

结果图如下:

继续看,因为上面我们发现调用链不止是直接通过Runtime就执行,其中调用的类比如还有ProcessBuilder、ProcessImpl这两个类那么这两个类是不是也可以通过反射获取该对象来进行命令执行呢?

通过反射ProcessBuilder类来实现命令执行

先看下Runtime什么时候调用的ProcessBuilder类,如下图所示

那么可以知道了调用的是ProcessBuilder的start方法

这里挑选只有一个参数的构造方法来进行实例化我们的对象

    public ProcessBuilder(List<String> command) {
        if (command == null)
            throw new NullPointerException();
        this.command = command;
    }

实现代码如下:

public class TwoCmd {
    public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        Class pClass = ProcessBuilder.class;
        List<String> list = new ArrayList<>();
        list.add("whoami");

        Constructor constructor = pClass.getDeclaredConstructor(new Class[]{List.class});

        Object obj = (ProcessBuilder)constructor.newInstance(list);
        Method startMethod = pClass.getMethod("start");
        Process p = (Process)startMethod.invoke(obj);

        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        System.out.println(baos.toString());
        
    }
}

结果图如下:

通过反射ProcessImpl类来实现命令执行

还是需要看这个ProcessImpl是在哪个流程中进行调用的,如下图所示:

实现的代码如下:

public class ThreeCmd {
    public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
        Class pClass = Class.forName("java.lang.ProcessImpl");
        //Constructor constructor = pClass.getDeclaredConstructor(); //这里是空的构造参数
        //constructor.setAccessible(true);
        //Object obj = constructor.newInstance();
        String[] cmdarray = new String[]{"whoami"};
        Method startMethod = pClass.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        startMethod.setAccessible(true);
        Process p = (Process)startMethod.invoke(null,cmdarray, null, null, null, false); //这里需要的五个参数,第一个参数为null,因为调用的方法是这个类的静态方法

        // 下面的操作就是正常的读取数据的操作
        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        System.out.println(baos.toString());

    }
}

posted @ 2020-08-12 15:57  zpchcbd  阅读(1749)  评论(0编辑  收藏  举报