应用背景
问题的解决来源于问题的产生,今天就遇到了这样的问题:在linux环境下without IDE,做图像修复填充方面的工作,从github上clone了一个项目patchMatch(java source code)。通过命令javac编译和java运行,但是这样需要每次更改参数,又重新编译source code,太麻烦了。于是,我就想:能不能将source code编译成.class文件,打包成带参数的jar文件,在命令行运行jar,指定参数。像如下图所示执行一样:
$java -jar XXX.jar -param1 paramValue1 -param2 paramValue2 -param3 paramValue3
这样的话,就不用每次去更改source code的参数再编译了,只需要一次编译打包成jar,命令行里指定不同的参数就可以了,岂不是很方便,嘿嘿嘿......你还觉得麻烦的话,可以写shell脚本,把这些繁琐的命令行操作写在一起,执行shell脚本就可以了。
申明:我浏览了网络中相关问题的处理办法是:借助像eclipse的IDE,用maven插件或者Ant插件来打包jar解决问题。那么像我只运行一次java code,还去在linux下安装IDE,插件maven,岂不是划不来。所以我单纯只用java中jar命令。
文件说明
首先我列出我的文件结构,方便理解。看下source file有4个java文件:PatchMatchMain.java ; Inpaint.java ; NNF.java ; MaskedImage.java.
./lib/commons-cli-1.4.jar是我自己创建的文件夹(后面有用),放进去的第三方包:包括了命令行参数包装和解析的函数,带参数的jar必须要导入这个包,我给出下载地址:http://commons.apache.org/proper/commons-cli/download_cli.cgi,下载Commons-cli-1.4-bin.tar.gz,解压就有commons-cli-1.4.jar这个包了
其中文件的调用顺序为PatchMatchMain(主入口,要导入commons-cli-1.4.jar) 调用 Inpaint,Inpaint调用NNF和MaskedImage,NNF调用MashedImage。
实现步骤
先来明确下操作步骤:
- 首先我应该把source file的主入口PatchMatchMain.java添加带参数并且能解析的部分;
- 把所有的.java编译成.class文件,编译过程引入了外部包需要指定外部包的位置;
- 用jar命令把编译好的.class文件打包成jar文件,需要手动编写清单文件MANIFEST.MF指定程序主入口和外部包位置(因为我们没有用IDE和maven插件)
- java -jar运行打包好的带参数的jar
1.添加带参数并且能解析的部分
首先导入需要的包:
import org.apache.commons.cli.*;
直接方便点,直接.*全部包括,这其中包含了以下要用的函数。
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException;
在你的主入口函数(我的是PatchMatchMain.java)里的public static void main(String[] args){}入口函数添加以下代码:(参数命名及参数个数根据自己需求更改),(项目需要更改三个参数,现在把这三个参数做成命令行指定)
public static void main(String[] args) { Options opts = new Options(); opts.addOption("h", false,"help"); //输入-h,可以显示所有参数及用法 //============================================================================== /**这部分:.withLongOpt("XXX"),.create("xx")改成自己的参数**/ Option inputOption = OptionBuilder.withArgName("args").withLongOpt("input_image_path").hasArg().withDescription("input image path").create("ii"); opts.addOption(inputOption); Option maskOption = OptionBuilder.withArgName("args").withLongOpt("mask_image_path").hasArg().withDescription("mask image path").create("mi"); opts.addOption(maskOption); Option targetOption = OptionBuilder.withArgName("args").withLongOpt("target_image_path").hasArg().withDescription("target image path").create("ti"); opts.addOption(targetOption); //================================================================================ BasicParser parser = new BasicParser(); CommandLine cl; String input_image_path = ""; String mask_image_path = ""; String target_image_path = ""; try { cl = parser.parse(opts, args); if (cl.getOptions().length > 0) { if (cl.hasOption('h')) { HelpFormatter hf = new HelpFormatter(); hf.printHelp("Options", opts); } else { //================================================================ /**这部分改成拿到自己的参数**/ input_image_path = cl.getOptionValue("ii"); mask_image_path = cl.getOptionValue("mi"); target_image_path = cl.getOptionValue("ti"); //================================================================== } } else { System.out.println("the paramter is null"); } } catch (Exception e) { e.printStackTrace(); } /*这部分是input_image_path,mask_image_path参数的使用*/ BufferedImage input = loadImage(input_image_path); BufferedImage maskimage = loadImage(mask_image_path); //下面是项目的代码,略...... }
将//=============之间的部分更改成自己的需求,其他可以不用更改。
2.把所有的.java编译成.class文件
首先说明下我的4个.java文件都处于包package com.example.patchmatch;中,如下:
那么编译时会生成该包的结构。
按照上面说的类调用顺序:PatchMatchMain(主入口,要导入commons-cli-1.4.jar) 调用 Inpaint,Inpaint调用NNF和MaskedImage,NNF调用MashedImage
那么编译顺序反着来,先编译被调用的,如下:
$javac -d . MaskedImage.java $javac -d . NNF.java $javac -d . Inpaint.java
由于PatchMatchMain中引入了第三方包commons-cli-1.4.jar,因此编译时需要指定第三方包的位置,如下:
$javac -cp .:./lib/commons-cli-1.4.jar -d . PatchMatchMain.java
说明:-cp和-classpath 一样,是指定类运行所依赖其他类的路径,通常是类库,jar包之类,需要指定路径到jar包(我的第三方包路径是当前路径下的lib文件夹中),window上分号“;分隔,linux上是分号“:”分隔。不支持通配符,需要列出所有jar包,用一点“.”代表当前路径。
当然你也可以把编译过程写成sh脚本一次运行,如图:
$chmod a+x shScript.sh #添加权限 $sh shScript.sh #运行
此时文件结构如下:多了.class编译好的文件
3. 用jar命令把编译好的.class文件打包成jar文件
我们首先来看下jar命令用法:
用法:jar {ctxui}[vfm0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
选项包括:
-c 创建新的归档文件
-t 列出归档目录
-x 解压缩已归档的指定(或所有)文件
-u 更新现有的归档文件
-v 在标准输出中生成详细输出
-f 指定归档文件名
-m 包含指定清单文件中的清单信息
-e 为捆绑到可执行 jar 文件的独立应用程序
指定应用程序入口点
-0 仅存储;不使用任何 ZIP 压缩
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含其中的文件
如果有任何目录文件,则对其进行递归处理。
清单文件名、归档文件名和入口点名的指定顺序
与 "m"、"f" 和 "e" 标志的指定顺序相同。示例 1:将两个类文件归档到一个名为 classes.jar 的归档文件中:
jar -cvf classes.jar Foo.class Bar.class
示例 2:使用现有的清单文件 "mymanifest" 并
将 foo/ 目录中的所有文件归档到 "classes.jar" 中:
jar -cvfm classes.jar mymanifest -C foo/ .======================================================================================
这儿有个问题:我们知道函数主入口是PatchMatchMain,但机器并不知道,我们需要编辑某个清单文件让机器知道函数主入口以及包含的第三方库在哪(这就是我们不用IDE和maven,自己要进行的操作)
清单文件MANIFEST.MF有4个变量需要指明:Manifest-Version ; Created-By ; Class-Path ; Main-Class
那么创建一个MANIFEST.MF文件,加入如下内容,比如我的内容:
Manifest-Version: 1.0 Created-By: xxxx #随便写个 Class-Path: ./lib/commons-cli-1.4.jar #第三方包位置,如果有多个jar包需要引用的情况,用空格隔开 Main-Class: com.example.patchmatch.PatchMatchMain #主入口
注意:第三方包位置,如果有多个jar包需要引用的情况,用空格隔开
此时,需要指定-m参数,指定MANIFEST.MF文件位置,如下:
$jar -cvfm PatchMatch-1.0.jar MANIFEST.MF com/example/patchmatch/*.class
此时会出现打包情况:
此时看下包结构,多了一个.jar文件,此时就完成了打包带参数可执行的jar了。
4. java -jar运行打包好的带参数的jar
下面来测试下能否运行,不好测试我的项目来展示,我用写好的-h(help)参数可以显示所有参数
我擦....这不就是我第一部分代码添加的参数么,完成测试,jar可以使用了。
我的使用格式是:
$java -jar PatchMatch-4.0.jar -ii ./inputImage.png -mi ./maskImage.png -ti ./targetImage.png
题外话:这打包好的jar怎么在python中运行呢?
很简单,利用os模块的system
import os cmd = 'java -jar PatchMatch-4.0.jar -ii ./inputImage.png -mi ./maskImage.png -ti ./targetImage.png' os.system(cmd) #执行jar
是不是很简单呢,就相当于在cmd模式下运行jar,os模块帮我们去执行