第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的能力。
看到这里,可以总结下Spring Boot应用的启动流程:
- spring boot应用打包之后,生成一个fat jar,里面包含了应用依赖的jar包,还有Spring boot loader相关的类
- Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载/lib下面的jar,并以一个新线程启动应用的Main函数。