Java命令执行

前言

自己的学习记录,如有不足或疑问,欢迎指出或提问,谢谢。

​ 文章介绍java的命令执行 以及 通过反射调用命令执行的扫盲知识。

​ 原生JDK提供了三个类来执行系统命令:java.lang.Runtime、java.lang.ProcessBuilder、java.lang.ProcessImpl。

​ 本文都是基于springboot实现web,不乏会有些注解。

java.lang.Runtime

​ java.lang包下的一个类,默认被导入,也就是说我们无需手动import,即可调用此类里面的方法

​ Runtime是java.lang的一个类,主要是与操作系统交互执行命令,其中最常用的 就是exec()

image-20240310141721486

​ jdk8中,exec()有六种重载形式,可以传入不同的参数类型来执行命令,这里演示exec(String command)exec(String[] cmdarray)

exec(String command)

​ 在单独的进程中执行指定的字符串命令。

​ 简单来说就是直接执行字符串命令。

java实现
1
2
3
//示例
String command = "whoami";
Runtime.getRuntime().exec(command)
1
2
3
4
5
6
7
8
9
10
11
12
//实现 
private static void cmdstring(String command) throws IOException {
String line = null;
Process process = Runtime.getRuntime().exec(command);
//使用BufferedReader设置编码解决执行命令响应中文乱码问题
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}

1
2
3
4
5
//在主方法中直接调用
public static void main(String[] args) throws Exception {
String command1 = "cmd /c whoami";
cmdstring(command1);
}

img

javaweb实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
@ResponseBody
public class ExecController {

@RequestMapping("/execRuntimeString")
public void execRuntimeString(String command, HttpServletResponse response) throws IOException {
String line = null;
Process process = Runtime.getRuntime().exec(command);
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
PrintWriter out = response.getWriter();
while ((line = bufferedReader.readLine()) != null) {
//逐行读取
System.out.println(line);
out.print(line);
}
bufferedReader.close();
}

此处简单说一下注解,这三个都是spring框架中的注解,后续文章中再次出现,笔者就不在赘述了。

@Controller:声明此类为控制器,也就是MVC中的”C”:controller层代码的功能

@ResponseBody:当方法被调用时,返回的对象将被转换为响应体,并发送给客户端

@RequestMapping:映射请求路径和方法,类似于Servlet中的@WebServlet

1
url:http://localhost:8080/execRuntimeString?command=whoami

image-20240310151224849

exec(String[] cmdarray)

在单独的进程中执行指定的命令和参数。

简单来说就是以数组的形式接收多个字符串然后执行。

java实现
1
2
3
//示例
String[] command = {"powershell","/c","ping www.baidu.com"}
Runtime.getRuntime().exec(command);
1
2
3
4
5
6
7
8
9
10
private static void cmdarray(String[] command) throws IOException {
String line = null;
Process process = Runtime.getRuntime().exec(command);
//使用BufferedReader设置编码解决执行命令响应中文乱码问题
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
1
2
3
4
5
//主方法直接调用 
public static void main(String[] args) throws Exception {
String[] command2 = {"powershell","/c","ping www.baidu.com"};
cmdarray(command2);
}

image-20240310152142609

javaweb中实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping("/execRuntimeArray")
public void execRuntimeArray(String command, HttpServletResponse response) throws IOException {
String line = null;
String[] commandarray ={"cmd","/c",command};
Process process = Runtime.getRuntime().exec(commandarray);
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
PrintWriter out = response.getWriter();
while ((line = bufferedReader.readLine()) != null) {
//逐行读取
System.out.println(line);
out.print(line);
}
bufferedReader.close();
}

image-20240310152443364

java.lang.ProcessBuilder

​ ProcessBuilder同样也是lang包中的一个类,这个类主要用于创建操作系统进程,此类两个重要且常用的方法command()start()

command()+start()

​ command()有四种重载形式,也都和exec大同小异,一种是可变的字符串,简单说就是可以传入普通字符串,或者字符串数组,另一种是字符串列表。有exec的案例,就不演示command()的重载了

image-20240310153557809

​ start()用来执行通过command()设置的命令

使用此方法执行命令流程:command()设置命令 ==》start()启动进程

java实现
1
2
3
4
//示例
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("calc"); //设置启动计算器的命令
processBuilder.start(); //执行命令

image-20240310160038351

javaweb实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
@ResponseBody
public class ProcessBuilderController {
@RequestMapping("/processBuilder")
public void processBuilder(String command, HttpServletResponse response) throws IOException {
String line = null;
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(command);
Process start = processBuilder.start();
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(start.getInputStream(), Charset.*forName*("GBK")));
PrintWriter out = response.getWriter();
while ((line = bufferedReader.readLine()) != null){
out.print(line);
}
bufferedReader.close();
}
}
1
url:http://localhost:8080/processBuilder?command=calc

image-20240310160418616

java.lang.ProcessImpl

​ 这里就不赘述反射相关的知识了,不懂java反射的同学可以先去学下反射再来看

​ 在JDK9之前,它是两个类:UNIXProcessProcessImpl,JDK9之后就把它们俩合并到了一个类中了

image-20240310161408091

​ ProcessImpl相对于其它两种就比较特殊了,ProcessImpl 是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类,对于ProcessImpl,我们不能和其它两个类一样直接调用方法,java.lang.ProcessImpl 都被 private 封装起来了,并没有设置公共的 API 接口,只能通过反射调用

java实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Map;

public class ExecProcessImpl {
public static void main(String[] args) throws Exception {
String[] cmds = new String[]{"whoami"};
Class clazz = Class.*forName*("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod(
"start",
String[].class,
Map.class,
String.class,
ProcessBuilder.Redirect[].class,
boolean.class
);
method.setAccessible(true);
Process process = (Process) method.invoke(null, cmds, null, ".", null, true);
String line = null;
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.*forName*("GBK")));
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
}

image-20240310162820846

javaweb实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

@Controller
@ResponseBody
public class ProcessImplController {
@RequestMapping("/processImpl")
public void processImpl(String command, HttpServletResponse response) throws Exception {
String[] cmds = new String[]{command};
Class clazz = Class.*forName*("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod(
"start",
String[].class,
Map.class,
String.class,
ProcessBuilder.Redirect[].class,
boolean.class
);
method.setAccessible(true);
Process process = (Process)method.invoke(null,cmds,null,".",null,true);
String line = null;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.*forName*("GBK")));
PrintWriter writer = response.getWriter();
while ((line = bufferedReader.readLine())!= null){
writer.print(line);
}
bufferedReader.close();
}
}

image-20240310163342400

​ 到这里,java命令执行的基础就完毕了

进阶-反射调用命令执行

​ 通过反射来调用命令执行。

​ 因为三个命令执行类中,ProcessImpl只能通过反射调用 而且上述也已经演示,这里只讲述Runtime和ProcessBuilder。

Runtime类

​ Runtime类中 常用且简单粗暴的两种调用方法,getMethod()getDeclaredConstructor()

getMethod()

​ 获取一个类的一个public方法

​ 如下,通过反射调用Runtime类中的exec方法执行命令。

1
2
3
4
5
6
7
public static void main(String[] args) throws Exception{
Class<?> clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime, "calc.exe");
}

​ 大家可能会发现,这里多了一个名为getRuntime方法,这是为什么?跟进到Runtime类可以得知

​ 橙色的锁在idea中表示private权限,也就是说Runtime类的构造器是私有的,因此不能直接使用newInstance() 创建一个实例

​ 这个getRuntime又是什么,为什么需要用到这个方法来执行命令?

​ 跟进类源码可以看到

image-20240311174705517

​ 因为Runtime类是私有的,jdk提供了一个公开的静态getRuntime()方法,这个方法返回了一个私有的静态实例变量currentRuntime,并将其初始化为new Runtime(),也就是一个新的Runtime对象,然后再调用其exec()方法。这涉及到了”单例模式“这个概念,大概就是将类的构造函数设为私有,再通过静态方法来获取,以此来减少资源的消耗和启动时间。

image-20240311180837386

getDeclaredConstructor()

​ 获得一个任意权限的构造器

1
2
3
4
5
6
7
public static void main(String[] args) throws Exception{
Class<?> clazz = Class.*forName*("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
Method c1 = clazz.getMethod("exec", String.class);
c1.invoke(m.newInstance(), "calc.exe");
}

image-20240311183403259

​ 构造器就一个,且没有传递构造参数。

image-20240311191043898

ProcessBuilder类

​ 相对于Runtime类,ProcessBuilder类携带了两个public的构造器:ProcessBuilder(List)ProcessBuilder(String)

​ 直接通过newInstance()访问构造器,创建一个实例

1
2
3
4
5
public static void main(String[] args) throws Exception{
Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
Object object = clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"));
clazz.getMethod("start").invoke(object,null);
}

​ 类中可以看到,给command属性传递了命令,然后再调用start()方法去执行命令

image-20240311192102761

end