Java命令执行
前言
自己的学习记录,如有不足或疑问,欢迎指出或提问,谢谢。
文章介绍java的命令执行 以及 通过反射调用命令执行的扫盲知识。
原生JDK提供了三个类来执行系统命令:java.lang.Runtime、java.lang.ProcessBuilder、java.lang.ProcessImpl。
本文都是基于springboot实现web,不乏会有些注解。
java.lang.Runtime
java.lang包下的一个类,默认被导入,也就是说我们无需手动import,即可调用此类里面的方法
Runtime是java.lang的一个类,主要是与操作系统交互执行命令,其中最常用的 就是exec()

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 = 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); }
|

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
|

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 = 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); }
|

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

java.lang.ProcessBuilder
ProcessBuilder同样也是lang包中的一个类,这个类主要用于创建操作系统进程,此类两个重要且常用的方法command()
和start()
command()+start()
command()有四种重载形式,也都和exec大同小异,一种是可变的字符串,简单说就是可以传入普通字符串,或者字符串数组,另一种是字符串列表。有exec的案例,就不演示command()的重载了

start()用来执行通过command()设置的命令
使用此方法执行命令流程:command()设置命令 ==》start()启动进程
java实现
1 2 3 4
| ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command("calc"); processBuilder.start();
|

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
|

java.lang.ProcessImpl
这里就不赘述反射相关的知识了,不懂java反射的同学可以先去学下反射再来看
在JDK9之前,它是两个类:UNIXProcess
和ProcessImpl
,JDK9之后就把它们俩合并到了一个类中了

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

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

到这里,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又是什么,为什么需要用到这个方法来执行命令?
跟进类源码可以看到

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

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"); }
|

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

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()方法去执行命令

end