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());

    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM