本文将简单介绍一下 JSch 这个Java的第三方库的一个简单用法,并以此为实例,讲解 IntelliJ 中打包成 jar 包的2种方式。
实现目标
我们的目标是,做出一个jar包,它能够实现类似于 scp 命令的远程传输文件的功能。用法如下:
java -jar UseJsch.jar scp /home/finix/1.txt 192.168.10.21:/home/finix/1.txt
即,将本地的文件 /home/finix/1.txt 拷贝到远程机器 192.168.10.21 的 /home/finix/1.txt
简介 JSch
JSch 的官网是:http://www.jcraft.com/jsch/
JSch 是SSH2的纯Java实现。 它拥有诸多的和ssh相关的功能,比如 ssh 执行命令、scp等等,并被使用在一些著名的开源软件中。这个库的最新版本似乎就是 0.1.55, 于2018年左右就停止更新了。
本文不打算深入罗列JSch的各种功能,只是使用了其ChannelSftp
类的put
功能用于远程传输文件。现将ChannelSftp
类的一些关键方法罗列如下:
put
文件上传get
文件下载cd
进入指定目录ls
取得指定目录下的文件列表rename
重命名指定的文件或目录mkdir
创建目录rmdir
删除目录rm
删除指定文件
JSch的maven地址
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
Jsch的gradle写法(build.gradle)
dependencies {
'com.jcraft:jsch:0.1.55'
}
核心代码
要实现scp文件的功能,可以使用下面的这个类。先看代码,其中具体使用已经写了注释,就不在这里介绍了。
java
package com.use_jsch;
import com.jcraft.jsch.*;
import java.io.*;
import java.util.Properties;
public class UseJsch {
public static void scpFile(int servicePort, String srcFilePath, String dstFilePath, String dstAddress) throws IOException {
final String USER = "finix";
final String PASSWORD = "123456789";
final String PRIVATE_KEY_FILE = "/home/finix/.ssh/id_rsa";
final String SESSION_TYPE = "sftp";
final int SSH_TIMEOUT = 30000; // 30 seconds
final int SSH_COMMAND_ACK_TIMEOUT = 5000; // 5 seconds
final int CHANNEL_TIMEOUT = 5000; // 5 seconds
File file = new File(srcFilePath);
if (!file.exists()) {
throw new RuntimeException(String.format("%s does not exist", srcFilePath));
}
Session session = null;
Channel channel = null;
ChannelSftp sftp = null;
FileInputStream fileInputStream = null;
try {
JSch jsch = new JSch();
// jsch.addIdentity(PRIVATE_KEY_FILE);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
config.put("PreferredAuthentications", "publickey,password");
session = jsch.getSession(USER, dstAddress, servicePort);
session.setPassword(PASSWORD);
session.setConfig(config);
System.out.println("SCP_FILE: 1. session connecting...");
session.connect(SSH_TIMEOUT);
session.setTimeout(SSH_COMMAND_ACK_TIMEOUT);
System.out.println("SCP_FILE: 2. open channel and connect...");
channel = session.openChannel(SESSION_TYPE);
channel.connect(CHANNEL_TIMEOUT);
sftp = (ChannelSftp) channel;
System.out.println("SCP_FILE: 3. scp file...");
fileInputStream = new FileInputStream(file);
sftp.setInputStream(fileInputStream);
sftp.put(fileInputStream, dstFilePath);
System.out.println("SCP_FILE: END");
} catch (JSchException e) {
System.err.println("SCP_FILE: JSchException: " + e);
throw new RuntimeException("SCP_FILE: JSchException: " + e);
} catch (IOException e) {
System.err.println("SCP_FILE: IOException: " + e);
throw new RuntimeException("SCP_FILE: IOException: " + e);
} catch (Exception e) {
System.err.println("SCP_FILE: ERROR: " + e);
throw new RuntimeException("SCP_FILE: ERROR: " + e);
} finally {
if (fileInputStream != null) fileInputStream.close();
if (channel != null) channel.disconnect();
if (session != null) session.disconnect();
}
}
public static void main(String[] args) {
if (args.length != 3 || !args[0].equals("scp")) {
System.out.println("Usage: java -jar UseJsch.jar scp <src_file_path> <ip>:<dest_file_path>");
System.exit(1);
}
String srcFilePath = args[1];
String[] destFileAndDestAddress = args[2].split(":");
String dstFilePath = destFileAndDestAddress[1];
String dstAddress = destFileAndDestAddress[0];
try {
System.out.printf("Start to scp file from %s to %s:%s%n", srcFilePath, dstAddress, dstFilePath);
scpFile(22,srcFilePath, dstFilePath, dstAddress);
}
catch (Exception ex) {
System.err.println("SCP FILE ERROR: " + ex);
}
}
}
这个java文件如果要直接测的话,可以将第一行 "package xxx" 删除,然后运行
shell
java -cp lib/jsch-0.1.55.jar UseJsch.java /home/finix/1.txt 192.168.10.21:/home/finix/1.txt
运行效果就是:
Start to scp file from /home/finix/1.txt to 192.168.10.21:/home/finix/1.txt
SCP_FILE: 1. session connecting...
SCP_FILE: 2. open channel and connect...
SCP_FILE: 3. scp file...
SCP_FILE: END
值得一提的有2个地方:
-
config.put("StrictHostKeyChecking", "no");
这句话相当于 ssh 或 scp 的时候用的命令行参数-o StrictHostKeyChecking=no
, 其作用是,令其不再出现交互式的提问The authenticity of host '192.168.10.21 (192.168.10.21)' can't be established.
ECDSA key fingerprint is SHA256:lSbwytjbqqvCC2FILkMb+M+yaeUTxEYPAPFTVIjTFOg.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
这是因为这个目标IP没有被加到~/.ssh/known_hosts
文件中,所以询问你是否要继续连接。我们用了上面的选项-o StrictHostKeyChecking=no
后,就不会再交互式提问,而是默认yes.
config.put("PreferredAuthentications", "publickey,password");
这一行代表优先使用的认证方式是publickey
和password
这两种。
(据说JSch提供了4种认证方式,但比较常用的应该还是以上提到的这2种)
这一句的意思是优先使用publickey
, 如果不行了再使用password
. 如果要使用publickey
, 那么其实还要再加一句
java
final String PRIVATE_KEY_FILE = "/home/finix/.ssh/id_rsa";
...
jsch.addIdentity(PRIVATE_KEY_FILE);
而在本文程序中, jsch.addIdentity(PRIVATE_KEY_FILE);
被注释掉了,所以本文程序仍旧使用的是密码的方式。
程序部分就介绍到这里。下面是打包的流程。
打包为jar包
Step 1. 首先,在IntelliJ中新建一个gradle的项目,并加入源代码UseJsch.java, 而build.gradle中依赖包的加法已经在上文中描述过了,就不再重复。这里的目录结构大约是这样的:
Step 2. File -> Project Structure -> Module, 此处鼠标单击选中 "UseJsch" (不要选中其下面的"main"),此处应该能看到 "Gradle: com.jcraft:jsch:0.1.55";若看不到,则此处需添加之。
这个添加的动作很重要:
1> 选中 "Module Source"
2> 点击加号 "+"
3> 弹出框中单击 Library
4> 继续的弹出框中选中 "Gradle: com.jcraft:jsch:0.1.55" , 点击 Add Selected
5> 最后点击OK.
Step 3. 还是在上面的界面,鼠标单击选中 "Artifacts", 再点击"+" 和 "From modules with dependencies"
Step 4. 在弹出框中填"Module"、"Main class"、"JAR files from libraries"
注意,这里的 "JAR files from libraries" 有2个选项
- extract to the target JAR
这个选项的含义是:将第三方库的jar文件解压成class文件,然后拷贝到和主类相同的目录结构中。
如果用了这个选项,我们就不用设置classpath来寻找第三方的库了。 - copy to the output directory and link via manifest
这个选项的含义是:第三方库将仍以jar包的形式存在,它会被拷贝到指定的地方(如lib目录);主类自己会被打一个jar包;因此需要在给主类打包的时候,指定MANIFEST文件中的classpath怎么写(见下文"打包方式-2")
打包方式-1: extract to the target JAR
Step 5.
上文Step-4中点击OK后,我们可以看到如下的界面:
注意,这里没有主类 UseJsch, 所以还得点击加号, 以添加主类。添加之后的效果如下:
注意,此处不用填写Class Path,因为第三方库已经被解压并和主类放置到了相同的目录结构下。
现在点击OK.
Step 6. Build -> Build Artifacts
此处点击 Build 或 Rebuild.
Build结束之后,我们在 output/artifacts/UseJsch_jar 目录下可以看到 UseJsch.jar 文件了!
右键选winrar打开,我们可以看到 jcraft 库的代码已经被解压和use_jsch包放到了一起:
最后,可以运行这个UseJsch.jar了:
shell
$ java -jar UseJsch.jar scp /home/finix/1.txt 192.168.10.21:/home/finix/1.txt
Start to scp file from /home/finix/1.txt to 192.168.10.21:/home/finix/1.txt
SCP_FILE: 1. session connecting...
SCP_FILE: 2. open channel and connect...
SCP_FILE: 3. scp file...
SCP_FILE: END
打包方式-2: copy to the output directory and link via manifest
在上文Step-4的时候选中 "copy to the output directory and link via manifest", 点OK
然后可以看到如下的界面。
注意,
这里我们已经可以看到第三方库 jsch-0.1.55.jar了,这是我们在Step-2所作的工作的效果;
(如果没有Step-2的工作,这里将不会显示第三方库,因此就需要手动添加:点击加号"+",再点击 Library Files)。
但是,此时并没有 主类,所以我们需要手动加主类添加上去:**单击选中"UseJsch.jar", 点击加号,再点击"Module Output"*, 如下:
选中主类,最后点击OK, 效果见下一步的图。
Step-5. 点击选中"UseJsch.jar", 然后在下面出现的框中填写 "Manifest File"、"Main Class"、和"Class Path"
最后,剩下来的步骤和打包方式-1相同,就是Build -> Build Artifacts.
出来的效果是这样的。
运行效果也是一样,不再赘述。
(完)