仿照springboot手搓一个maven打包插件
前言 需要打一个可执行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开始,然后逐目录向下寻找。
但是我觉得这样不优雅,如果能像springboot打包那样,每个依赖都是一个单独的jar,那就清爽多了。像这样:
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文件
Main-Class 表示 java -jar 时,首先进入的类,即main方法所在的类(所有jar包都一样)。别的属性就属于springboot特有的属性了。
入口点是JarLauncher,查看之。
先将结论放前面:
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
其中PROTOCOL_HANDLER
是java.protocol.handler.pkgs
,HANDLERS_PACKAGE
是org.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这个包默认都会查找。
查http协议的handler时,去的包就是sun.net.www.protocol.http包,找名字叫Handler的类。
为什么要registerUrlProtocolHandler 目的就是为了扩展处理jar协议handler,加载多层jar包里面的类。
下面进行测试的jar包结构。
正常情况,使用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"); }
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()); } }
现在我们实现了加载多层jar包里的类。
下一个问题是,如何获取内层jar包的url?若能获取,则将url数组传给ClassLoader,那么这个ClassLoader就能加载这些地方的类了。
createClassLoader 查看这个方法,传入的参数是archives,猜测这个就是jar包里的所有jar包。
往上找,看看是哪个方法返回的,最终找到的是ExecutableArchiveLauncher的getClassPathArchivesIterator。
关键是调用了this.archive.getNestedArchives。
这里传入两个Filter,即可返回一个Archive里面的所有Archive。
新的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/
里面
读取所有依赖,获取到依赖的artifactId和version,再获取本地仓库绝对路径,即可获取依赖的绝对路径,然后读取,写入jar包。
3、读取项目编译之后产生的classes,打进target.jar!/BOOT-INF/classes/
插件可以获取到target/classes的绝对路径,直接整个文件夹放进去即可。
4、把loader的字节码打进target.jar!/org/springframework/...
,自己的字节码打进target.jar!/cn/org/unk/...
。
loader的字节码,必须放在jar包里第一级目录,才能被初始的类加载器加载。
loader的字节码和插件的jar包放在一起,插件必须先解压出来,然后才能打进target.jar。
5、设置MANIFEST.MF文件,设置Main-Class,Start-Class,然后打进target.jar!/META-INF/
。
下文先给出已完成项目的组织方式,再写如何编写一个插件。
创建项目
idea自动创建的项目会给你生成一个it文件夹,亲测没什么用,删去无妨。
pom文件也是,会给你生成一坨profiles,也可以删去,留下plugins即可。
注意<packaging>必须得是maven-plugin。
点package时,生成的jar里应该会有一个plugin.xml。
然后install,安装到本地仓库,即可被后续使用。
项目合并 先看写好的项目的组织方式。合并loader与plugin。
<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>
configuration表示传给mojo的参数。无需指定传给那个mojo,根据属性名自动识别。
mojo的属性可以使用maven内置变量。
${project.build.outputDirectory} // target ${project.build.directory} // target/classes ${project.artifactId} ${project.version} ${project} //返回当前project对象,类型为MavenProject
execution包含多个goal,每个goal都对应一个mojo类,插件执行时,调用mojo类的execute方法。
每个mojo都会有一个LifecyclePhase,表示在maven生命周期的什么阶段执行这个goal。
本项目只用了两个mojo,ExtractMojo用于把loader的字节码提取出来,PackMojo用于打包。
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包,遂糊了一个轮子
extractTo 根据pattern解压jar里面的文件
getJarEntryContent 获取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; } }
结果 随便写一个测试类
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>
打包出来的结构:
正常执行,没问题。