zoukankan      html  css  js  c++  java
  • 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
    原文地址:https://blog.csdn.net/yangguosb/article/details/80764971
  • 相关阅读:
    Eclipse中使用Working Set来管理项目
    Hibernate基于注解方式配置来实现实体和数据库之间存在某种映射关系
    身份证工具类,可以解析出身份证号是否通过校验、性别、年龄和出生所在地
    使用Spring整合Hibernate,并实现对数据表的增、删、改、查的功能
    Hibernate案例-------基于xml配置,使用Hibernate实现对员工表的增、删、改、查功能
    Hibernate学习笔记整理系列-------一、Hibernate简介
    基于注解配置的Spring MVC 简单的HelloWorld实例应用
    基于XML配置的Spring MVC 简单的HelloWorld实例应用
    详细的图文教程来实现 eclipse环境下如何配置tomcat,并且把项目部署到Tomcat服务器上
    webstorm 注册码
  • 原文地址:https://www.cnblogs.com/jpfss/p/10997447.html
Copyright © 2011-2022 走看看