第4种打整包插件,urlfactory already set

0 背景

maven设置打jar包并引入依赖包中有3种插件打整包,现在有第4种,这种方式与spring boot相似,因为jvm本身不支持加载jar中jar,所以自一开始,我便认定它是用一个特殊的自定义的类加载器去加载jar中jar我们在

使用resource中的jar包资源作为UrlClassloader(二)

JDBC注册原理与自定义类加载器解决com.cloudera.hive.jdbc41.HS2Driver的加载【重点】

也干过。

通过打包后解压,及解析源码,果不其然

1 插件

  <build>
    <plugins>
        <plugin>
          <groupId>de.ntcomputer</groupId>
          <artifactId>executable-packer-maven-plugin</artifactId>
          <version>1.0.1</version>
          <configuration>
            <mainClass>com.sunyuming.DemoPack</mainClass><!--replace your mainClass-->
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>pack-executable-jar</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
    </plugins>
  </build>

2 manifest

Manifest-Version: 1.0
Built-By: mac
Application-Main-Class: com.sunyuming.DemoPack
Dependency-Libpath: lib/
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_121
Dependency-Jars: cglib-2.2.2.jar/asm-3.3.1.jar

Main-Class: de.ntcomputer.executablepacker.runtime.ExecutableLauncher

显然,该插件如同spring boot一般篡改了主函数

3 我们看一下这个类:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package de.ntcomputer.executablepacker.runtime;

import de.ntcomputer.executablepacker.runtime.JarInJarURLStreamHandlerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

public class ExecutableLauncher {
public static final String MANIFEST_APPLICATION_MAIN_CLASS = "Application-Main-Class";
public static final String MANIFEST_DEPENDENCY_LIBPATH = "Dependency-Libpath";
public static final String MANIFEST_DEPENDENCY_JARS = "Dependency-Jars";

public ExecutableLauncher() {
}

public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
ClassLoader outerJarClassLoader = Thread.currentThread().getContextClassLoader();
Object usedClassLoader = outerJarClassLoader;
String applicationMainClassName = null;
String dependencyLibPath = null;
String dependencyJarFilenames = null;
Enumeration manifestURLs = outerJarClassLoader.getResources("META-INF/MANIFEST.MF");
if(manifestURLs != null && manifestURLs.hasMoreElements()) {
while(manifestURLs.hasMoreElements()) {
URL applicationMainClass = (URL)manifestURLs.nextElement();

try {
InputStream applicationMainMethod = applicationMainClass.openStream();

try {
Manifest dependencyJarURLArray = new Manifest(applicationMainMethod);
Attributes jarInJarClassLoader = dependencyJarURLArray.getMainAttributes();
String manifestApplicationMainClassName = jarInJarClassLoader.getValue("Application-Main-Class");
String manifestDependencyLibPath = jarInJarClassLoader.getValue("Dependency-Libpath");
String dependencyJarUrl = jarInJarClassLoader.getValue("Dependency-Jars");
if(manifestApplicationMainClassName != null) {
manifestApplicationMainClassName = manifestApplicationMainClassName.trim();
if(!manifestApplicationMainClassName.trim().isEmpty()) {
applicationMainClassName = manifestApplicationMainClassName;
dependencyLibPath = manifestDependencyLibPath;
dependencyJarFilenames = dependencyJarUrl;
break;
}
}
} finally {
applicationMainMethod.close();
}
} catch (Exception var18) {
;
}
}

if(applicationMainClassName == null) {
throw new IOException("Manifest is missing entry Application-Main-Class");
} else {
if(dependencyLibPath == null) {
dependencyLibPath = "";
} else {
dependencyLibPath = dependencyLibPath.trim();
}

if(dependencyJarFilenames != null && !dependencyJarFilenames.isEmpty()) {
URL.setURLStreamHandlerFactory(new JarInJarURLStreamHandlerFactory(outerJarClassLoader));
String[] var19 = dependencyJarFilenames.split("/");
ArrayList var21 = new ArrayList(var19.length);
var21.add(new URL("jij:./"));
String[] var28 = var19;
int var27 = var19.length;

for(int var25 = 0; var25 < var27; ++var25) {
String var23 = var28[var25];
var23 = var23.trim();
if(!var23.isEmpty()) {
URL var29 = new URL("jar:jij:" + dependencyLibPath + var23 + "!/");
var21.add(var29);
}
}

if(var21.size() > 1) {
URL[] var24 = (URL[])var21.toArray(new URL[var21.size()]);
URLClassLoader var26 = new URLClassLoader(var24, (ClassLoader)null);
usedClassLoader = var26;
Thread.currentThread().setContextClassLoader(var26);
}
}

Class var20 = Class.forName(applicationMainClassName, true, (ClassLoader)usedClassLoader);
Method var22 = var20.getMethod("main", new Class[]{String[].class});
var22.invoke((Object)null, new Object[]{args});
}
} else {
throw new MissingResourceException("Manifest file (META-INF/MANIFEST.MF) is missing", ExecutableLauncher.class.getName(), "META-INF/MANIFEST.MF");
}
}
}

该实现与使用resource中的jar包资源作为UrlClassloader(二) 第3种方式相似,因此它也具有无法和tomcat等本身具有UrlFactory的工具集成,因为Factory是静态的(java.net.URL.setURLStreamHandlerFactory)

4 显然,tomcat的urlfactory会先于这个类ExecutableLaucher

至于报错我们可以做个实验:

public class DemoPack {
public static void main(String []f) {
System.out.print("demo pack");
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
public URLStreamHandler createURLStreamHandler(String urlProtocol) {
return null;
}
});
}
}

打包后java-jar

mac@macdeMacBook MyPack % java -jar target/MyPack-1.0.0-pkg.jar
demo packException in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at de.ntcomputer.executablepacker.runtime.ExecutableLauncher.main(ExecutableLauncher.java:122)
Caused by: java.lang.Error: factory already defined
at java.net.URL.setURLStreamHandlerFactory(URL.java:1112)
at com.sunyuming.DemoPack.main(DemoPack.java:13)
... 5 more
mac@macdeMacBook MyPack % 

5 spring boot打包器也是如此

https://www.jianshu.com/p/da773da3fdb1

Main-Class: org.springframework.boot.loader.JarLauncher
LaunchedURLClassLoader

LaunchedURLClassLoader和普通的URLClassLoader的不同之处是,它提供了从Archive里加载.class的能力。

创建好ClassLoader之后,再从MANIFEST.MF里读取到Start-Class,即com.example.SpringBootDemoApplication,然后创建一个新的线程来启动应用的Main函数。


看到这里,可以总结下Spring Boot应用的启动流程:

  1. spring boot应用打包之后,生成一个fat jar,里面包含了应用依赖的jar包,还有Spring boot loader相关的类
  2. Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载/lib下面的jar,并以一个新线程启动应用的Main函数。


6 这个工具不甚稳定
java运行时发现阻塞了几分钟,用jstack看,最终是到ZipFile的delete好像
原文地址:https://www.cnblogs.com/silyvin/p/14699395.html