前言

需要打一个可执行jar包,附带依赖。已知的做法是使用maven-assembly-plugin加maven-compiler-plugin插件。

固定写法如下:

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>

<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<mainClass>cn.org.unk.Mainn</mainClass> <!-- Replace with your main class -->
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>assembly</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>

打包出来的结构像这样,所有依赖的jar包都会被解压,这样java -jar 执行时,类加载器才找得到类文件。

java -jar 执行jar包,此时classpath就是xxx.jar,类加载器寻找一个类如org.springframework.boot.loader.archive.Archive,就是以xxx.jar开始,然后逐目录向下寻找。

image-20240702220412869.png

但是我觉得这样不优雅,如果能像springboot打包那样,每个依赖都是一个单独的jar,那就清爽多了。像这样:

image-20240702220905770.png

springboot启动原理

前置知识:类加载机制。

现在问题就是,类加载器是如何找到需要的类的?这就涉及到springboot的启动原理。

参考:https://blog.csdn.net/kaihuishang666/article/details/108405691

springboot loader 依赖,随便添加到一个idea项目中,方便查看源码。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.7.11</version>
</dependency>

先看一个springboot 应用的MANIFEST文件

image-20240702221308646.png

Main-Class 表示 java -jar 时,首先进入的类,即main方法所在的类(所有jar包都一样)。别的属性就属于springboot特有的属性了。

入口点是JarLauncher,查看之。

image-20240702221620074.png

image-20240702221659813.png

先将结论放前面:

1、所有Springboot应用启动时,无一例外都执行JarLauncher的main方法。

2、这个JarLauncher首先对jar协议进行扩展,确保jar:file:/这种格式的URL能够读取多层jar包里面的类。

3、获取当前jar包中,所有依赖jar的url,形如:jar:file:/xxx.jar!/BOOT-INF/lib/a.jar!/,以及用户类的url:jar:file:/xxx.jar!/BOOT-INF/classes/

4、然后创建一个URLClassLoader作为新的上下文类加载器,将上面的urls传入。

5、启动用户程序的main方法。

下面是分析。

registerUrlProtocolHandler

image-20240702221719070.png

其中PROTOCOL_HANDLERjava.protocol.handler.pkgsHANDLERS_PACKAGEorg.springframework.boot.loader

这个方法的操作就是,将java.protocol.handler.pkgs这个变量增加一个org.springframework.boot.loader的值。

这个变量有什么用?

每个URL对象,都会有一个handler属性。协议不同,handler就不同。当URL对象openConnection时,具体操作交由对应的handler完成。

协议,决定了handler。去哪里找handler,则由java.protocol.handler.pkgs这个变量决定。可以有多个包,由竖线分隔。sun.net.www.protocol这个包默认都会查找。

image-20240702222902976.png

查http协议的handler时,去的包就是sun.net.www.protocol.http包,找名字叫Handler的类。

image-20240702223017042.png

为什么要registerUrlProtocolHandler

目的就是为了扩展处理jar协议handler,加载多层jar包里面的类。

下面进行测试的jar包结构。

image-20240702224138304.png

image-20240702224126819.png

正常情况,使用java自带的jar协议的handler,只能读取一层jar,如下,这样加载类可以正常加载。

public static void main(String[] args) throws Exception{

URL url = new URL("jar:file:/C:/Users/Acer/.m2/repository/cn/unk/org/Java-unser-utils/1.0-SNAPSHOT/Java-unser-utils-1.0-SNAPSHOT.jar!");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
urlClassLoader.loadClass("cn.org.unk.Gadget");

}

如果套两层jar,尝试去加载第二层jar里的类,不可。

public static void main(String[] args) throws Exception{

URL url = new URL("jar:file:/C:/Users/Acer/.m2/repository/cn/unk/org/Java-unser-utils/1.0-SNAPSHOT/Java-unser-utils-1.0-SNAPSHOT.jar!/main_agent_jackson.jar!/");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
urlClassLoader.loadClass("example.MyAgent");

}

image-20240702224845289.png

registerUrlProtocolHandler后,成功加载类并能够反射获取到方法。

public static void registerUrlProtocolHandler() {
String handlers = System.getProperty("java.protocol.handler.pkgs", "");
System.setProperty("java.protocol.handler.pkgs", ("".equals(handlers) ? "org.springframework.boot.loader"
: handlers + "|" + "org.springframework.boot.loader"));
}
public static void main(String[] args) throws Exception{
registerUrlProtocolHandler();
URL url = new URL("jar:file:/C:/Users/Acer/.m2/repository/cn/unk/org/Java-unser-utils/1.0-SNAPSHOT/Java-unser-utils-1.0-SNAPSHOT.jar!/main_agent_jackson.jar!/");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
Class<?> aClass = urlClassLoader.loadClass("example.MyAgent");
for (Method method : aClass.getMethods()) {
System.out.println(method.getName());
}

}

image-20240702224817240.png

现在我们实现了加载多层jar包里的类。

下一个问题是,如何获取内层jar包的url?若能获取,则将url数组传给ClassLoader,那么这个ClassLoader就能加载这些地方的类了。

createClassLoader

查看这个方法,传入的参数是archives,猜测这个就是jar包里的所有jar包。

image-20240702230654787.png

往上找,看看是哪个方法返回的,最终找到的是ExecutableArchiveLauncher的getClassPathArchivesIterator。

image-20240702230800713.png

关键是调用了this.archive.getNestedArchives。

这里传入两个Filter,即可返回一个Archive里面的所有Archive。

image-20240702230900969.png

新的Launcher

类的查找和加载,解决了,再参考别处的代码,补齐这些功能:

1、获取当前jar包的绝对路径。

2、返回一个jar包中所有jar包的URL,形如jar:file:/xxx.jar!/a.jar!/

3、获取一个jar包中Mainfest文件的属性值,如Main-Class。

综合起来:拿到第1步的路径后,进行第2步,获取所有urls,将urls传给新的URLClassLoader。设置该URLClassLoader为上下文类加载器,此后所有类加载操作由他完成。进入Main-Class的main方法。

最终得到的Launcher如下。

package cn.org.unk.boot.loader;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.JarFileArchive;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class MyLauncher {

private final JarFileArchive archive; // java -jar 执行的jar包
private static final String libFolder = "BOOT-INF/lib/"; // 存放依赖的jar包

public static final String MANIFEST_MAIN_CLASS = "Main-Class"; // 该Launcher

public static final String MANIFEST_START_CLASS = "Start-Class"; // 用户主程序


public MyLauncher() throws Exception {
this.archive = createArchive();
}

private String getManifestValue(String key) throws Exception{
return archive.getManifest().getMainAttributes().getValue(key);
}

// 返回一个jar包中所有jar包的URL
public List<URL> getJarArchivesInJar(JarFileArchive jarFileArchive) throws Exception{
Iterator<Archive> nestedArchives = jarFileArchive.getNestedArchives(null, new Archive.EntryFilter() {
@Override
public boolean matches(Archive.Entry entry) {
return entry.getName().endsWith(".jar") && (libFolder == null || entry.getName().startsWith(libFolder));
}
});

ArrayList<URL> urls = new ArrayList<>();

while(nestedArchives.hasNext()){
Archive next = nestedArchives.next();
System.out.println(next.getUrl());
urls.add(next.getUrl());
}

return urls;
}

// 获取当前jar包的绝对路径,并将当前jar包变成JarFileArchive对象
protected final JarFileArchive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException("Unable to determine code source archive from " + root);
}
return new JarFileArchive(root);
}
public static void registerUrlProtocolHandler() {
String handlers = System.getProperty("java.protocol.handler.pkgs", "");
System.setProperty("java.protocol.handler.pkgs", ("".equals(handlers) ? "org.springframework.boot.loader"
: handlers + "|" + "org.springframework.boot.loader"));
}
public void launch(String[] args) throws Exception{

// 类加载器的查询目录,getJarArchivesInJar返回的是BOOT-INF/lib/下的所有jar的url
// 还要手动将BOOT-INF/classes/加入,这里存放用户程序的类
// 所有URL最后都要以/结尾,否则无法查找该路径。
List<URL> archives = getJarArchivesInJar(archive);
archives.add(new URL(String.format("jar:%s!/BOOT-INF/classes/",archive.getUrl())));
URL[] urls = archives.toArray(new URL[0]);

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println("old contextClassLoader: " + contextClassLoader);

// 设置当前上下文的类加载器为新的类加载器
URLClassLoader loader = new URLClassLoader(urls, contextClassLoader);
Thread.currentThread().setContextClassLoader(loader);

System.out.println("new contextClassLoader: " + loader);

// 执行用户main方法
Class<?> aClass = loader.loadClass(getManifestValue("Start-CLass"));
aClass.getMethod("main",new Class[]{String[].class}).invoke(null, new Object[]{new String[]{}});

}
public static void main(String[] args) throws Exception{
registerUrlProtocolHandler();
new MyLauncher().launch(args);
}

}

编写maven插件

Launcher有了,现在就是看怎么自动化打包,考虑maven插件。

插件要完成的任务:

1、设置一个最终打的输出jar包,假设为target.jar。

2、读取项目中所有依赖的jar,然后打进target.jar!/BOOT-INF/lib/里面

3、读取项目编译之后产生的classes,打进target.jar!/BOOT-INF/classes/

4、把loader的字节码打进target.jar!/org/springframework/...,自己的字节码打进target.jar!/cn/org/unk/...

5、设置MANIFEST.MF文件,设置Main-Class,Start-Class,然后打进target.jar!/META-INF/

下文先给出已完成项目的组织方式,再写如何编写一个插件。

创建项目

image-20240702233032414.png

idea自动创建的项目会给你生成一个it文件夹,亲测没什么用,删去无妨。

image-20240702233313609.png

pom文件也是,会给你生成一坨profiles,也可以删去,留下plugins即可。

注意<packaging>必须得是maven-plugin。

点package时,生成的jar里应该会有一个plugin.xml。

image-20240702233612163.png

然后install,安装到本地仓库,即可被后续使用。

项目合并

先看写好的项目的组织方式。合并loader与plugin。

image-20240703113025491.png

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.org.unk</groupId>
<artifactId>unk-boot</artifactId>
<version>1.0</version>
</parent>

<artifactId>unk-boot-loader</artifactId>
<packaging>jar</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.7.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>

<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<mainClass>cn.org.unk.boot.loader.MyLauncher</mainClass> <!-- Replace with your main class -->
</manifest>
</archive>

</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>assembly</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.org.unk</groupId>
<artifactId>unk-boot</artifactId>
<version>1.0</version>
</parent>

<artifactId>unk-boot-plugin</artifactId>
<packaging>maven-plugin</packaging>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-archiver</artifactId>
<version>3.0.3</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>../unk-boot-loader/target</directory>
<includes>
<include>unk-boot-loader-1.0.jar</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.2</version>
<configuration>
<goalPrefix>unk-boot-loader</goalPrefix>
<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
</configuration>
<executions>
<execution>
<id>mojo-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
<execution>
<id>help-goal</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

写插件

随便创建一个项目,引用该插件,pom文件这样配置:

<plugin>
<groupId>cn.org.unk</groupId>
<artifactId>unk-boot-plugin</artifactId>
<version>1.0</version>
<configuration>
<startClass>
cn.org.unk.Mainn
</startClass>
</configuration>
<executions>
<execution>
<goals>
<goal>extract</goal>
<goal>pack</goal>
</goals>
</execution>
</executions>
</plugin>

本项目只用了两个mojo,ExtractMojo用于把loader的字节码提取出来,PackMojo用于打包。

ExtractMojo

package cn.org.unk.boot.plugin;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.JarURLConnection;
import java.util.jar.JarFile;

@Mojo( name = "extract", defaultPhase = LifecyclePhase.PREPARE_PACKAGE)
public class ExtractMojo extends AbstractMojo {


// 自己的Launcher和Springboot loader 依赖都打在这个包下
@Parameter( defaultValue = "${project.build.directory}", property = "outputDir", required = true )
private File outputTargetDir;

public void extract(){

// 得到的URl形如 jar:file:/xxx.jar!/ExtractMojo.class
// 由于没有扩展jar协议,后面openConnection,open的还是插件本身,即一个jar
URL resource = this.getClass().getResource("ExtractMojo.class");

try {
JarURLConnection urlConnection = (JarURLConnection)resource.openConnection();
JarFile jarFile = urlConnection.getJarFile();

// 获取插件里的unk-boot-launcher-1.0.jar,并输出
// 输出之后再解压,得到的就是launcher的字节码
// 但是不全部解压,只解压需要的,如META-INF里的文件就不需要
// 解压完成后删去unk-boot-launcher-1.0.jar
byte[] bootLoaderContent = JarUtils.getJarEntryContent(jarFile, Config.LOADER_JAR);
File bootDir = new File(outputTargetDir,Config.BOOT_CLASS_DIR);

if(!bootDir.exists()){
bootDir.mkdirs();
}

File file = new File(bootDir, Config.LOADER_JAR);

FileOutputStream fileOutputStream = new FileOutputStream(new File(bootDir, Config.LOADER_JAR));
fileOutputStream.write(bootLoaderContent);
fileOutputStream.close();
JarFile jarFile1 = new JarFile(file);

JarUtils.extractTo(jarFile1,bootDir,
Config.EXTRACT_INCLUDE
,Config.EXTRACT_EXCLUDE);
jarFile1.close();
file.delete();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
extract();
}
}

PackMojo

package cn.org.unk.boot.plugin;


import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.jar.JarArchiver;

import java.io.*;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

@Mojo( name = "pack", defaultPhase = LifecyclePhase.PACKAGE )
public class PackMojo extends AbstractMojo {

/**
* Location of the file.
*/
@Parameter( defaultValue = "${project.build.directory}", property = "outputDir", required = true )
private File outputTargetDir;
@Parameter( defaultValue = "${project.build.outputDirectory}", required = true )
private File outputClassDir;

@Parameter( defaultValue = "${project}", readonly = true, required = true )
private MavenProject project;

@Parameter(defaultValue = "")
private String localRepo;

@Parameter( defaultValue = Config.OUTPUT_JAR_NAME, required = true )
private String outPutJarName;

@Parameter(readonly = true, required = true )
private String startClass;

// 根据依赖的坐标,还有本地仓库的绝对路径,获取依赖的绝对路径
public String getDependencyAbsolutePath(String repository,Dependency dependency){
String groupId = dependency.getGroupId();
String artifactId = dependency.getArtifactId();
String version = dependency.getVersion();

String path = String.format("%s/%s/%s/%s/%s-%s.jar",
repository,
groupId.replace('.','/'),
artifactId,
version,
artifactId,
version);
if(!new File(path).exists()){
if(new File(dependency.getSystemPath()).exists()){
path = dependency.getSystemPath();
}else {
path = "";
}
}
return path;
}

public File createTempManifestFile() throws IOException {
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, Config.MANIFEST_VERSION);
attributes.put(new Attributes.Name("Created-By"), Config.MANIFEST_CREATE_BY);
attributes.put(Attributes.Name.MAIN_CLASS, Config.MAIN_CLASS);
attributes.put(new Attributes.Name("Start-Class"), startClass);

File tempFile = File.createTempFile("kkkkkkkkk", ".mf");
tempFile.deleteOnExit();
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
manifest.write(fileOutputStream);
fileOutputStream.close();
return tempFile;
}

public void addDependencies(JarArchiver jarArchiver,List<Dependency> dependencies){
for (Dependency dependency : dependencies) {
String path = getDependencyAbsolutePath(localRepo, dependency);
System.out.println(path);
String dependencyName = String.format("%s/lib/%s-%s.jar", Config.BOOT_INF, dependency.getArtifactId(), dependency.getVersion());
jarArchiver.addFile(new File(path),dependencyName);
}
}

public static void main(String[] args) {

}
public void execute() throws MojoExecutionException {
System.out.println("[+] MavenProject: "+project.toString());

if("".equals(localRepo) || localRepo == null){
localRepo = System.getProperty("user.home").replace('\\','/') + "/.m2/repository";
}
System.out.println("[+] localRepo: " + localRepo);
System.out.println(outputClassDir);

List<Dependency> dependencies = project.getDependencies();

JarArchiver jarArchiver = new JarArchiver();
jarArchiver.setDestFile(new File(outputTargetDir, outPutJarName));
jarArchiver.setCompress(false);

File bootdir = new File(outputTargetDir,Config.BOOT_CLASS_DIR);

jarArchiver.addDirectory(outputClassDir, Config.BOOT_INF + "/classes/");
jarArchiver.addDirectory(bootdir);
addDependencies(jarArchiver,dependencies);


try {
File manifestFile = createTempManifestFile();
jarArchiver.setManifest(manifestFile);
jarArchiver.createArchive();
} catch (IOException e) {
throw new RuntimeException(e);
}

}
}

JarUtils

由于要操作jar包,遂糊了一个轮子

package cn.org.unk.boot.plugin;

import java.io.*;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

public class JarUtils {
public static byte[] inputStream2ByteArray(InputStream in) throws IOException {

ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}

public static void extractTo(JarFile jarFile, File directory, Pattern includePattern, Pattern excludePattern) throws Exception {
if(!directory.exists()){
directory.mkdirs();
}
Enumeration<JarEntry> entries = jarFile.entries();
while(entries.hasMoreElements()){
JarEntry entry = entries.nextElement();
if(!entry.isDirectory() && entry.getName().matches(includePattern.pattern()) && !entry.getName().matches(excludePattern.pattern())){
File file = new File(directory, entry.getName());

File parentFile = file.getParentFile();

if(!parentFile.exists()){
parentFile.mkdirs();
}
byte[] bytes = getJarEntryContent(jarFile, entry.getName());
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(bytes);
fileOutputStream.close();
System.out.println(String.format("%s %s",entry.getName(),bytes.length));
}
}
}


public static void extractTo(JarFile jarFile,File directory,Pattern pattern,boolean include) throws Exception {
if(include){
extractTo(jarFile,directory,pattern,Pattern.compile(""));
}else {
extractTo(jarFile,directory,Pattern.compile(".*"),pattern);
}

}
public static void extractTo(JarFile jarFile,File directory) throws Exception {
extractTo(jarFile,directory,Pattern.compile(".*"),Pattern.compile(""));
}

public static void listJarEntries(JarFile jarFile) throws Exception{
Enumeration<JarEntry> entries = jarFile.entries();
while(entries.hasMoreElements()){
JarEntry entry = entries.nextElement();
byte[] bytes = getJarEntryContent(jarFile, entry.getName());
System.out.println(String.format("%s %s",entry.getName(),bytes.length));
}
}
public static byte[] getJarEntryContent(JarFile jarFile,String entryName) throws Exception{
JarEntry entry = jarFile.getJarEntry(entryName);
InputStream inputStream = jarFile.getInputStream(entry);
byte[] bytes = inputStream2ByteArray(inputStream);
inputStream.close();
return bytes;
}
}

结果

随便写一个测试类

image-20240703122459058.png

pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>ldap_exp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>exp</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>

<dependency>
<groupId>cn.unk.org</groupId>
<artifactId>Java-unser-utils</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>E:/ideaProjects/Java-unser-utils/target/Java-unser-utils-1.0-SNAPSHOT.jar</systemPath>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>cn.org.unk</groupId>
<artifactId>unk-boot-plugin</artifactId>
<version>1.0</version>
<configuration>
<startClass>
cn.org.unk.Mainn
</startClass>
</configuration>
<executions>
<execution>
<goals>
<goal>extract</goal>
<goal>pack</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

打包出来的结构:

image-20240703122851585.png

正常执行,没问题。

image-20240703122916452.png