Springboot启动Windows程序的方法-核心代码

         目前java提供两种方式调用Windows下的其他exe程序, Runtime和ProcessBulider。原理相同,同时通过执行类似cmd命令的方式启动和关闭程序。两种方法都会返回一个用于管理操作系统进程的Process对象,最终生成的Process子进程独立于Jvm之外,就算没有对象应用指向它,也不会被GC关闭或者释放。

        创建子进程Process后,要及时取走子进程输出信息和错误信息,否则很可能因为信息太多导致子进程阻塞。

        这里我就以ProcessBulider为例,介绍一下Springboot代码中如何实现其功能。

        ProcessBuilder是java 5.0引入的,start()方法返回Process的一个实例。创建Controller层代码,只要实现两个方法,开启程序的startEXE()方法和关闭程序的closetEXE()方法:

@RestController
@Slf4j
@RequestMapping("/test")
public class ProcessController {
	
	@RequestMapping("/start")
    //通过cmd命令开启exe程序
    public String startEXE() throws IOException {
		log.info("Line[" + Thread.currentThread().getStackTrace()[1].getLineNumber() + "] :start cuteftppro.exe");
        ProcessBuilder process=createProcessBuilder("cuteftppro.exe","D:\\yaken\\Program Files\\CuteFTP9",null);
        try {
            Process proc = process.start();
            //执行程序
//            BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream(), "gbk"));
//          SequenceInputStream是一个串联流,能够把两个流结合起来,可以将getInputStream和getErrorStream获取到的流一起查看
            BufferedReader br = new BufferedReader(new InputStreamReader(new SequenceInputStream(proc.getInputStream(), proc.getErrorStream()), "gbk"));
            CmdGetter cg = new CmdGetter(br, proc);
            cg.start();
            log.info("Line[" + Thread.currentThread().getStackTrace()[1].getLineNumber() + "] :process log - "+cg.output.toString());
        } catch (Exception e) {
            log.error("子线程错误");
        }
        return "Your application has been launched !!!";
    }
	
    private ProcessBuilder createProcessBuilder(String executable,String filePath, List<String> args) {
        // 针对以.exe结尾的可执行文件 将先判断其是否存在
        if (!(FileUtil.exist(filePath+File.separator+executable))) {
            throw new IllegalArgumentException("找不到对应可执行文件:" + executable);
        }
        List<String> cmd = new ArrayList<>();
        cmd.add("cmd");
        cmd.add("/c");
        cmd.add(executable);
        if (CollUtil.isNotEmpty(args)) {
            cmd.addAll(args);
        }
 
        log.info("将执行的命令-->{}", cmd);
        File dir = FileUtil.file(filePath);
        log.info("命令执行的目录-->{}", dir.getAbsolutePath());
        //directory的参数为工作路径
        return new ProcessBuilder(cmd).directory(dir).redirectErrorStream(true);
    }
 
    @RequestMapping("/close")
    //通过cmd命令,关闭程序
    public String closeEXE(){
    	log.info("Line[" + Thread.currentThread().getStackTrace()[1].getLineNumber() + "] :close cuteftppro.exe");
        closeHistoryProgram("cuteftppro.exe");
        return "Your application has been closed !!!";
    }
 
    public void closeHistoryProgram(String processName){
        String cmd = "taskkill /f /t /im "+processName;
        try
        {
            //exec执行cmd命令
            Process process = Runtime.getRuntime().exec(cmd);
            //获取CMD命令结果的输出流
            InputStream fis=process.getInputStream();
            InputStream isE=process.getErrorStream();
            InputStreamReader isr=new InputStreamReader(new SequenceInputStream(isI, isE), "GBK");
            //使用缓冲器读取
            BufferedReader br=new BufferedReader(isr);
            String line=null;
            //全部读取完成为止,一行一行读取
            while((line=br.readLine())!=null)
            {
                //输出读取的内容
                System.out.println( line );
            }
        }catch (IOException e)
        {
            e.printStackTrace();
        }
    }

}

        通过SequenceInputStream对象将getInputStream和getErrorStream合并。

        专门创建了CmdGetter,并继承Thread,以便开启多线程支持。

public class CmdGetter extends Thread {

    private BufferedReader br;
    public static List<String> output;
    private Process process;
 
    public CmdGetter(BufferedReader br, Process p) {
        this.br = br;
        this.process = p;
    } 
 
    @Override
    public void run() {
        output = new ArrayList<>();
        String line;
        try {
            while (process.isAlive() && (line = br.readLine()) != null) {
                output.add(line);
                if (output.size() >= 20) {
                    output.clear();
                    output.add(line);
                }
 
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}

最后在前端添加两个按钮用于测试程序的启动与停止,并绑定事件。我这里使用了vue3,前后端分离。

<template>
    <!--用于测试打开系统安装的程序-->
    <el-button type="warning" @click="startExe">Start</el-button>
    <el-button type="warning" @click="closeExe">Close</el-button>
</template>
<script setup>

  import requestUtil from '@/util/request'

   const startExe=async ()=>{
     let result=await requestUtil.get("test/start");
     console.log("start cuteftppro.exe : "+result.data);
   }
  
   const closeExe=async ()=>{
     let result=await requestUtil.get("test/close");
     console.log("close cuteftppro.exe : "+result.data);
  }

</script>

        其中用到了request组件的get请求方法,组件代码如下所示:

// 引入axios
import axios from 'axios';
import store from '@/store'

//请求地址:后台地址
// let baseUrl="http://localhost:80/";
// let baseUrl="http://192.168.9.50:80/";
let baseUrl="http://127.0.0.1:80/";

// 创建实例
const httpService = axios.create({
    baseURL: baseUrl,
    timeout: 3000
});

// 请求拦截器
httpService.interceptors.request.use(
    function (config) {
        // 在发送请求前做处理
        let token = store.getters.GET_TOKEN;
        if(token){
            console.log("从sessionStorage中获取到的token:"+token);
            config.headers.token=token;
        }else {
            let token_1 = store.state.token;
            console.log("从store.state中获取到的token:"+token_1);
            config.headers.token=token_1;
        }
        return config;
    }, function (error) {
        // 对请求错误做处理
        return Promise.reject(error);
    });

// 响应拦截器
httpService.interceptors.response.use(function (response) {
    // 对响应数据做处理
    return response;
}, function (error) {
    // 对响应错误做处理
    return Promise.reject(error);
});

/*
 *  get请求
 *  url:请求地址
 *  params:参数
 * */
export function get(url, params = {}) {
    return new Promise((resolve, reject) => {
        httpService({
            url: url,
            method: 'get',
            params: params,
        }).then(response => {
            console.log(response);
            resolve(response);
        }).catch(error => {
            console.log(error);
            reject(error);
        });
    });
}

export function getServerUrl(){
    return baseUrl;
}

export default {
    get,
    post,
    fileUpload,
    getServerUrl
}

        前后台代码编写完成,现在来做个测试,启动前、后台工程,分别点击两个按钮查看结果:

        可以看到从后台控制台打出来的消息, 分别对应着点击事件。到此为止,基本功能已实现。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值