FatJar技術


概念

  將一個jar及其依賴的三方jar全部打到一個包中,這個包即為FatJar。

作用

  作用: Jar包隔離,避免Jar沖突。

打包方式

  1. maven-shade-plugin插件;
  2. spring-boot-maven-plugin插件(Spring Boot打包插件);

嵌套Jar資源加載方案

  思路:擴展Jar URL協議+定制ClassLoader;

擴展Jar URL協議

  問題: JDK內置的Jar URL協議只支持一個’!/’,需要擴展此協議使其支持多個’!/’,以便能夠加載jar in jar的資源,如下所示:

 jar:file:/data/spring-boot-theory/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class
   
   
  
  
          
jar:file:/data/spring-boot-theory.jar!/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class
   
   
  
  
          

  解決方案:定制協議處理器Handler,定制規則查看另一篇,SpringBoot實現如下圖所示:
這里寫圖片描述

定制ClassLoader

  加載class:重載loadclass,添加對應的包路徑;
  加載其它資源:使用URLClassLoader原有邏輯即可;
SpringBoot提供了LaunchedURLClassLoader ,實現如下所示:

/*
 * Copyright 2012-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.loader;

import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Enumeration;
import java.util.jar.JarFile;

import org.springframework.boot.loader.jar.Handler;

/**
 * {@link ClassLoader} used by the {@link Launcher}.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Andy Wilkinson
 */
public class LaunchedURLClassLoader extends URLClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }

    /**
     * Create a new {@link LaunchedURLClassLoader} instance.
     * @param urls the URLs from which to load classes and resources
     * @param parent the parent class loader for delegation
     */
    public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    public URL findResource(String name) {
        Handler.setUseFastConnectionExceptions(true);
        try {
            return super.findResource(name);
        }
        finally {
            Handler.setUseFastConnectionExceptions(false);
        }
    }

    @Override
    public Enumeration<URL> findResources(String name) throws IOException {
        Handler.setUseFastConnectionExceptions(true);
        try {
            return new UseFastConnectionExceptionsEnumeration(super.findResources(name));
        }
        finally {
            Handler.setUseFastConnectionExceptions(false);
        }
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        Handler.setUseFastConnectionExceptions(true);
        try {
            try {
                definePackageIfNecessary(name);
            }
            catch (IllegalArgumentException ex) {
                // Tolerate race condition due to being parallel capable
                if (getPackage(name) == null) {
                    // This should never happen as the IllegalArgumentException indicates
                    // that the package has already been defined and, therefore,
                    // getPackage(name) should not return null.
                    throw new AssertionError("Package " + name + " has already been "
                            + "defined but it could not be found");
                }
            }
            return super.loadClass(name, resolve);
        }
        finally {
            Handler.setUseFastConnectionExceptions(false);
        }
    }

    /**
     * Define a package before a {@code findClass} call is made. This is necessary to
     * ensure that the appropriate manifest for nested JARs is associated with the
     * package.
     * @param className the class name being found
     */
    private void definePackageIfNecessary(String className) {
        int lastDot = className.lastIndexOf('.');
        if (lastDot >= 0) {
            String packageName = className.substring(0, lastDot);
            if (getPackage(packageName) == null) {
                try {
                    definePackage(className, packageName);
                }
                catch (IllegalArgumentException ex) {
                    // Tolerate race condition due to being parallel capable
                    if (getPackage(packageName) == null) {
                        // This should never happen as the IllegalArgumentException
                        // indicates that the package has already been defined and,
                        // therefore, getPackage(name) should not have returned null.
                        throw new AssertionError(
                                "Package " + packageName + " has already been defined "
                                        + "but it could not be found");
                    }
                }
            }
        }
    }

    private void definePackage(String className, String packageName) {
        try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> { String packageEntryName = packageName.replace('.', '/') + "/"; String classEntryName = className.replace('.', '/') + ".class"; for (URL url : getURLs()) { try { URLConnection connection = url.openConnection(); if (connection instanceof JarURLConnection) { JarFile jarFile = ((JarURLConnection) connection) .getJarFile(); if (jarFile.getEntry(classEntryName) != null && jarFile.getEntry(packageEntryName) != null && jarFile.getManifest() != null) { definePackage(packageName, jarFile.getManifest(), url); return null; } } } catch (IOException ex) { // Ignore } } return null; }, AccessController.getContext()); } catch (java.security.PrivilegedActionException ex) { // Ignore } } /** * Clear URL caches. */ public void clearCache() { for (URL url : getURLs()) { try { URLConnection connection = url.openConnection(); if (connection instanceof JarURLConnection) { clearCache(connection); } } catch (IOException ex) { // Ignore } } } private void clearCache(URLConnection connection) throws IOException { Object jarFile = ((JarURLConnection) connection).getJarFile(); if (jarFile instanceof org.springframework.boot.loader.jar.JarFile) { ((org.springframework.boot.loader.jar.JarFile) jarFile).clearCache(); } } private static class UseFastConnectionExceptionsEnumeration implements Enumeration<URL> { private final Enumeration<URL> delegate; UseFastConnectionExceptionsEnumeration(Enumeration<URL> delegate) { this.delegate = delegate; } @Override public boolean hasMoreElements() { Handler.setUseFastConnectionExceptions(true); try { return this.delegate.hasMoreElements(); } finally { Handler.setUseFastConnectionExceptions(false); } } @Override public URL nextElement() { Handler.setUseFastConnectionExceptions(true); try { return this.delegate.nextElement(); } finally { Handler.setUseFastConnectionExceptions(false); } } } } 
   
   
  
  
          

加載步驟

  1. 注冊定制Handler;
  2. 獲取當前Jar包及其嵌套Jar包URL;
  3. 創建ClassLoader,進行資源加載;

使用示例代碼,如下所示:

import java.net.URL;
import java.util.List;
import com.cainiao.iots.client.utils.loader.ExecutableArchiveLauncher;
import com.cainiao.iots.client.utils.loader.archive.Archive;
import com.cainiao.iots.client.utils.loader.jar.JarFile;

public class IotClientLauncher extends ExecutableArchiveLauncher {
    static final String BOOT_INF_LIB = "sar/jars/";

    private ClassLoader classLoader;

    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        return entry.getName().startsWith(BOOT_INF_LIB);
    }

    @Override
    protected void launch(String[] args) throws Exception {
        //step1:注冊handler
        JarFile.registerUrlProtocolHandler();

        //step2:獲取當前Jar包及其嵌套Jar包URL
        List<Archive> archives = getClassPathArchives();
        for(int i = 0; i < archives.size(); i++){
            System.out.println("Archive url: " + archives.get(i).getUrl());
        }

        //step3:創建ClassLoader
        this.classLoader = createClassLoader(archives);
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public static void main(String[] args) throws Exception {
        //1. 創建ClassLoader
        IotClientLauncher launcher = new IotClientLauncher();
        launcher.launch(args);
        ClassLoader loader = launcher.getClassLoader();

        //2. 加載jar in jar的資源
        URL url = loader.getResource("1.jpg");
        Class<?> clazz = loader.loadClass("*.*.*");
    }
}

   
   
  
  
          

參考:

  1. https://segmentfault.com/a/1190000013532009


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM