• 第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好像
  • 相关阅读:
    C# 桥接模式(Bridge)
    C# 中介者模式(Mediator)
    C# 命令模式(Command)
    C# 模板方法(TempleteMethod)
    C# 装饰模式(Decorate)
    C# 策略模式(Strategy)
    C# 职责链模式(Chain of Responsibility)
    C# 外观模式(Facade)
    C# 单例模式(Single)
    C# 原型模式(Prototype)
  • 原文地址:https://www.cnblogs.com/silyvin/p/14699395.html
Copyright © 2020-2023  润新知