zoukankan      html  css  js  c++  java
  • Springboot源码学习_启动类

    Springboot源码学习_启动类

    概述

    SpringBoot生成的jar包

    ​ 当使用java -jar命令执行Spring Boot应用的可执行jar文件时,该命令引导标准可执行的jar文件,通过在jar中MANIFES.MF找到Main-Class,以JarLauncher.java为入口,加载所有的需要的启动资源(BOOT-INF/classes/*,BOOT-INF/lib/*),交给自定义的类加载器,然后通过反射,启动MANIFES.MF中定义的Start-Classmain()方法,即应用启动类的main()方法。

    ​ SpringBoot 的可执行jar包又称fat jar ,是包含所有依赖的 jar 包,jar 包中嵌入了除 java 虚拟机以外的所有依赖,是一个 all-in-one jar 包。普通插件maven-jar-plugin生成的包和spring-boot-maven-plugin生成的包之间的直接区别,是fat jar中主要增加了两部分,第一部分是lib目录,存放的是Maven依赖的jar包文件,第二部分是spring boot loader相关的类。

    但是系统自带的AppClassLoarder不支持读取嵌套jar包,引入自定义类加载器就是为了能解决jar包嵌套jar包的问题,于是springboot将自定义的类加载器放在最顶成目录,使它可以被加载.

    为什么SpringBoot要将Loader 类下的所有文件复制出来呢

    如果将SpringBoot Class Loader 也放到lib文件下,是根本无法被加载到的,因为它根本不符合jar文件的一个标准规范.

    程序要有一个启动入口,这个入口要由应用类加载器加载,先将SpringBoot Class Loader加载到内存中,然后通过后续的一些操作创建线程上下文加载器,去加载第三方jar。

    1 Maven

    也就是说想要知道fat jar是如何生成的,就必须知道spring-boot-maven-plugin工作机制,而spring-boot-maven-plugin属于自定义插件,因此我们又必须知道,Maven的自定义插件是如何工作的

    Maven的自定义插件

    Maven 拥有三套相互独立的生命周期: cleandefaultsite, 而每个生命周期包含一些phase阶段, 阶段是有顺序的, 并且后面的阶段依赖于前面的阶段。生命周期的阶段phase与插件的目标goal相互绑定,用以完成实际的构建任务。

    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <executions>
            <execution>
                <goals>
                    <goal>repackage</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    

    repackage目标对应的将执行到org.springframework.boot.maven.RepackageMojo#execute,该方法的主要逻辑是调用了org.springframework.boot.maven.RepackageMojo#repackage

    private void repackage() throws MojoExecutionException {
         //获取使用maven-jar-plugin生成的jar,最终的命名将加上.orignal后缀
       Artifact source = getSourceArtifact();
        //最终文件,即Fat jar
       File target = getTargetFile();
        //获取重新打包器,将重新打包成可执行jar文件
       Repackager repackager = getRepackager(source.getFile());
        //查找并过滤项目运行时依赖的jar
       Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
             getFilters(getAdditionalFilters()));
        //将artifacts转换成libraries
       Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
             getLog());
       try {
           //提供Spring Boot启动脚本
          LaunchScript launchScript = getLaunchScript();
           //执行重新打包逻辑,生成最后fat jar
          repackager.repackage(target, libraries, launchScript);
       }
       catch (IOException ex) {
          throw new MojoExecutionException(ex.getMessage(), ex);
       }
        //将source更新成 xxx.jar.orignal文件
       updateArtifact(source, target, repackager.getBackupFile());
    }
    

    我们关心一下org.springframework.boot.maven.RepackageMojo#getRepackager这个方法,知道Repackager是如何生成的,也就大致能够推测出内在的打包逻辑。

    private Repackager getRepackager(File source) {
       Repackager repackager = new Repackager(source, this.layoutFactory);
       repackager.addMainClassTimeoutWarningListener(
             new LoggingMainClassTimeoutWarningListener());
        //设置main class的名称,如果不指定的话则会查找第一个包含main方法的类,repacke最后将会设置org.springframework.boot.loader.JarLauncher
       repackager.setMainClass(this.mainClass);
       if (this.layout != null) {
          getLog().info("Layout: " + this.layout);
           //重点关心下layout 最终返回了 org.springframework.boot.loader.tools.Layouts.Jar
          repackager.setLayout(this.layout.layout());
       }
       return repackager;
    }
    /**
     * Executable JAR layout.
     */
    public static class Jar implements RepackagingLayout {
       @Override
       public String getLauncherClassName() {
          return "org.springframework.boot.loader.JarLauncher";
       }
       @Override
       public String getLibraryDestination(String libraryName, LibraryScope scope) {
          return "BOOT-INF/lib/";
       }
       @Override
       public String getClassesLocation() {
          return "";
       }
       @Override
       public String getRepackagedClassesLocation() {
          return "BOOT-INF/classes/";
       }
       @Override
       public boolean isExecutable() {
          return true;
       }
    }
    

    layout我们可以将之翻译为文件布局,或者目录布局,代码一看清晰明了,同时我们需要关注,也是下一个重点关注对象org.springframework.boot.loader.JarLauncher,从名字推断,这很可能是返回可执行jar文件的启动类。

    MANIFEST.MF文件内容
    Manifest-Version: 1.0
    Start-Class: com.shengsiyuan.boot.MyApplication
    Main-Class: org.springframework.boot.loader.JarLauncher
    

    repackager生成的MANIFEST.MF文件为以上信息,可以看到两个关键信息Main-ClassStart-Class。我们可以进一步,程序的启动入口并不是我们SpringBoot中定义的main,而是JarLauncher#main,而再在其中利用反射调用定义好的Start-Classmain方法

    JarLauncher
    重点类介绍
    • java.util.jar.JarFile JDK工具类提供的读取jar文件

    • org.springframework.boot.loader.jar.JarFileSpringboot-loader 继承JDK提供JarFile

    • java.util.jar.JarEntryDK工具类提供的``jar```文件条目

    • org.springframework.boot.loader.jar.JarEntry Springboot-loader 继承JDK提供JarEntry

    • org.springframework.boot.loader.archive.Archive
      

      Springboot抽象出来的统一访问资源的层

      • JarFileArchivejar包文件的抽象
      • ExplodedArchive文件目录

    这里重点描述一下JarFile的作用,每个JarFileArchive都会对应一个JarFile。在构造的时候会解析内部结构,去获取jar包里的各个文件文件夹类。我们可以看一下该类的注释。

    /* Extended variant of {@link java.util.jar.JarFile} that behaves in the same way but
    * offers the following additional functionality.
    * <ul>
    * <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} based
    * on any directory entry.</li>
    * <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} for
    * embedded JAR files (as long as their entry is not compressed).</li>
    **/ </ul>
    

    jar里的资源分隔符是!/,在JDK提供的JarFile URL只支持一个’!/‘,而Spring boot扩展了这个协议,让它支持多个’!/‘,就可以表示jar in jar、jar in directory、fat jar的资源了。

    自定义类加载机制
    • 最基础:Bootstrap ClassLoader(加载JDK的/lib目录下的类)
    • 次基础:Extension ClassLoader(加载JDK的/lib/ext目录下的类)
    • 普通:Application ClassLoader(程序自己classpath下的类)

    首先需要关注双亲委派机制很重要的一点是,如果一个类可以被委派最基础的ClassLoader加载,就不能让高层的ClassLoader加载,这样是为了范围错误的引入了非JDK下但是类名一样的类。其二,如果在这个机制下,由于fat jar中依赖的各个jar文件,并不在程序自己classpath下,也就是说,如果我们采用双亲委派机制的话,根本获取不到我们所依赖的jar包,因此我们需要修改双亲委派机制的查找class的方法,自定义类加载机制

    先简单的介绍Springboot2中LaunchedURLClassLoader,该类继承了java.net.URLClassLoader,重写了java.lang.ClassLoader#loadClass(java.lang.String, boolean),然后我们再探讨他是如何修改双亲委派机制。

    在上面我们讲到Spring boot支持多个’!/‘以表示多个jar,而我们的问题在于,如何解决查找到这多个jar包。我们看一下LaunchedURLClassLoader的构造方法。

    public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
       super(urls, parent);
    }
    

    urls注释解释道the URLs from which to load classes and resources,即fat jar包依赖的所有类和资源,将该urls参数传递给父类java.net.URLClassLoader,由父类的java.net.URLClassLoader#findClass执行查找类方法,该类的查找来源即构造方法传递进来的urls参数

    //LaunchedURLClassLoader的实现
    protected Class<?> loadClass(String name, boolean resolve)
          throws ClassNotFoundException {
       Handler.setUseFastConnectionExceptions(true);
       try {
          try {
              //尝试根据类名去定义类所在的包,即java.lang.Package,确保jar in jar里匹配的manifest能够和关联	           //的package关联起来
             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.
                 
                //这里异常表明,definePackageIfNecessary方法的作用实际上是预先过滤掉查找不到的包
                throw new AssertionError("Package " + name + " has already been "
                      + "defined but it could not be found");
             }
          }
          return super.loadClass(name, resolve);
       }
       finally {
          Handler.setUseFastConnectionExceptions(false);
       }
    }
    

    方法super.loadClass(name, resolve)实际上会回到了java.lang.ClassLoader#loadClass(java.lang.String, boolean),遵循双亲委派机制进行查找类,而Bootstrap ClassLoaderExtension ClassLoader将会查找不到fat jar依赖的类,最终会来到Application ClassLoader,调用java.net.URLClassLoader#findClass

    如何真正的启动

    Springboot2和Springboot1的最大区别在于,Springboo1会新起一个线程,来执行相应的反射调用逻辑,而SpringBoot2则去掉了构建新的线程这一步。方法是org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)反射调用逻辑比较简单,这里就不再分析,比较关键的一点是,在调用main方法之前,将当前线程的上下文类加载器设置成LaunchedURLClassLoader

    protected void launch(String[] args, String mainClass, ClassLoader classLoader)
          throws Exception {
       Thread.currentThread().setContextClassLoader(classLoader);
       createMainMethodRunner(mainClass, args, classLoader).run();
    }
    
    Demo
    public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
            JarFile.registerUrlProtocolHandler();
    // 构造LaunchedURLClassLoader类加载器,这里使用了2个URL,分别对应jar包中依赖包spring-boot-loader和spring-boot,使用 "!/" 分开,需要org.springframework.boot.loader.jar.Handler处理器处理
            LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(
                    new URL[] {
                            new URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-loader-1.2.3.RELEASE.jar!/")
                            , new URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-2.1.3.RELEASE.jar!/")
                    },
                    Application.class.getClassLoader());
    // 加载类
    // 这2个类都会在第二步本地查找中被找出(URLClassLoader的findClass方法)
            classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
            classLoader.loadClass("org.springframework.boot.SpringApplication");
    // 在第三步使用默认的加载顺序在ApplicationClassLoader中被找出
       classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");
    
    //        SpringApplication.run(Application.class, args);
        }
    

    2启动器实现原理

    Launcher的继承关系如下:

    启动类:JarLauncher

    //JarLauncher.java
    public class JarLauncher extends ExecutableArchiveLauncher {
    
    	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    
    	static final String BOOT_INF_LIB = "BOOT-INF/lib/";
    
    	public JarLauncher() {
    	}
    
    	protected JarLauncher(Archive archive) {
    		super(archive);
    	}
    
    	@Override
    	protected boolean isNestedArchive(Archive.Entry entry) {
    		if (entry.isDirectory()) {
    			return entry.getName().equals(BOOT_INF_CLASSES);
    		}
    		return entry.getName().startsWith(BOOT_INF_LIB);
    	}
    
    	public static void main(String[] args) throws Exception {
    		new JarLauncher().launch(args);
    	}
    }
    

    JarLauncher默认构造函数实现为空,它父类ExecutableArchiveLauncher会调用再上一级父类Launcher的createArchive方法加载jar包, 加载了jar包之后,我们就能获取到里面所有的资源。

    	//JarLauncher.java
    	
    	//JarLauncher默认构造函数
    	public JarLauncher() {
    	}
    	
    //ExecutableArchiveLauncher.java	
    
    public ExecutableArchiveLauncher() {
    		try {
          //开始加载jar包
    			this.archive = createArchive();
    		}
    		catch (Exception ex) {
    			throw new IllegalStateException(ex);
    		}
    	}
    //Launcher.java	
    
    protected final Archive createArchive() throws Exception {
        //通过获取当前Class类的信息,查找到当前归档文件的路径
    		ProtectionDomain protectionDomain = getClass().getProtectionDomain();
    		CodeSource codeSource = protectionDomain.getCodeSource();
    		URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
    		String path = (location != null) ? location.getSchemeSpecificPart() : null;
    		if (path == null) {
    			throw new IllegalStateException("Unable to determine code source archive");
    		}
        //获取到路径之后,创建对应的文件,并检查是否存在
    		File root = new File(path);
    		if (!root.exists()) {
    			throw new IllegalStateException(
    					"Unable to determine code source archive from " + root);
    		}
        //如果是目录,则创建ExplodedArchive,否则创建JarFileArchive
    		return (root.isDirectory() ? new ExplodedArchive(root)
    				: new JarFileArchive(root));
    	}
    

    核心方法:launch(String[] args)

    launch方法实际上是调用父类Launcher的launch方法

    // Launcher.java	
    
    protected void launch(String[] args) throws Exception {
        //注册 Spring Boot 自定义的 URLStreamHandler 实现类,用于 jar 包的加载读取, 可读取到内嵌的jar包
    		JarFile.registerUrlProtocolHandler();
        //创建自定义的 ClassLoader 实现类,用于从 jar 包中加载类。
    		ClassLoader classLoader = createClassLoader(getClassPathArchives());
        //执行我们声明的 Spring Boot 启动类,进行 Spring Boot 应用的启动。
    		launch(args, getMainClass(), classLoader);
    	}
    

    简单来说,就是创建一个可以读取 jar 包中类的加载器,保证 BOOT-INF/lib 目录下的类和 BOOT-classes 内嵌的 jar 中的类能够被正常加载到,之后执行 Spring Boot 应用的启动。

    方法一:registerUrlProtocolHandler

    public static void registerUrlProtocolHandler() {
        	// 获得 URLStreamHandler 的路径
    		String handlers = System.getProperty(PROTOCOL_HANDLER, "");
        	// 将 Spring Boot 自定义的 HANDLERS_PACKAGE(org.springframework.boot.loader) 补充上去
    		System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE
    				: handlers + "|" + HANDLERS_PACKAGE));
        	// 重置已缓存的 URLStreamHandler 处理器们
    		resetCachedUrlHandlers();
    	}
    

    该方法的目的就是通过将 org.springframework.boot.loader 包设置到 "java.protocol.handler.pkgs" 环境变量,从而使用到自定义的 URLStreamHandler 实现类 Handler,处理 jar: 协议的 URL。

    利用java url协议实现扩展原理,自定义jar协议
    将org.springframework.boot.loader包 追加到java系统 属性java.protocol.handler.pkgs中,实现自定义jar协议
    
    java会在java.protocol.handler.pkgs系统属性指定的包中查找与协议同名的子包和名为Handler的类,
    即负责处理当前协议的URLStreamHandler实现类必须在 <包名>.<协议名定义的包> 中,并且类名称必须为Handler
    例如:
    org.springframework.boot.loader.jar.Handler这个类 将用于处理jar协议
    
    这个jar协议实现作用:
    默认情况下,JDK提供的ClassLoader只能识别jar中的class文件以及加载classpath下的其他jar包中的class文件。
    对于jar包中的jar包是无法加载的
    所以spring boot 自己定义了一套URLStreamHandler实现类和JarURLConnection实现类,用来加载jar包中的jar包的class类文件
    

    举个例子:

    jar:file:C:/Users/Administrator/Desktop/demo/demo/target/jarlauncher-0.0.1-SNAPSHOT.jar!/lib/spring-boot-1.5.10.RELEASE.jar!/
    
    jar:file:C:/Users/Administrator/Desktop/demo/demo/target/jarlauncher-0.0.1-SNAPSHOT.jar!/lib/spring-boot-1.5.10.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class
    123
    

    我们看到如果有 jar 包中包含 jar,或者 jar 包中包含 jar 包里面的 class 文件,那么会使用 !/分隔开,这种方式只有 org.springframework.boot.loader.jar.Handler 能处理,它是 SpringBoot 内部扩展出来一种URL协议.

    通常,jar里的资源分隔符是!/,在JDK提供的JarFile URL只支持一层“!/”,而Spring Boot扩展了该协议,可支持多层“!/”。

    方法二:createClassLoader

    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    
    getClassPathArchives()
    // ExecutableArchiveLauncher.java
    
    @Override
    protected List<Archive> getClassPathArchives() throws Exception {
     // <1> 获得所有 Archive
     List<Archive> archives = new ArrayList<>(
       this.archive.getNestedArchives(this::isNestedArchive));
     // <2> 后续处理:是个空方法
     postProcessClassPathArchives(archives);
     return archives;
    }
    

    <1> 处,this::isNestedArchive 代码段,创建了 EntryFilter 匿名实现类,用于过滤 jar 包不需要的目录。目的就是过滤获得,BOOT-INF/classes/ 目录下的类,以及 BOOT-INF/lib/ 的内嵌 jar 包。

    // JarLauncher.java
    
    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    
    static final String BOOT_INF_LIB = "BOOT-INF/lib/";
    
    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        // 如果是目录的情况,只要 BOOT-INF/classes/ 目录
     if (entry.isDirectory()) {
      return entry.getName().equals(BOOT_INF_CLASSES);
     }
     // 如果是文件的情况,只要 BOOT-INF/lib/ 目录下的 `jar` 包
     return entry.getName().startsWith(BOOT_INF_LIB);
    }
    

    <1>处getNestedArchives()方法实现

    	//JarFileArchive.java
    	
    	@Override
    	public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
    		List<Archive> nestedArchives = new ArrayList<>();
    		for (Entry entry : this) {
    			if (filter.matches(entry)) {
    				nestedArchives.add(getNestedArchive(entry));
    			}
    		}
    		return Collections.unmodifiableList(nestedArchives);
    	}
    
    createClassLoader(List archives)
    // ExecutableArchiveLauncher.java
    
    protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
     // 获得所有 Archive 的 URL 地址
        List<URL> urls = new ArrayList<>(archives.size());
     for (Archive archive : archives) {
      urls.add(archive.getUrl());
     }
     // 创建加载这些 URL 的 ClassLoader
     return createClassLoader(urls.toArray(new URL[0]));
    }
    
    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
     return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
    }
    

    基于获得的 Archive 数组,创建自定义 ClassLoader 实现类 LaunchedURLClassLoader,通过它来加载 BOOT-INF/classes 目录下的类,以及 BOOT-INF/lib 目录下的 jar 包中的类。

    方法三:launch(String[] args, String mainClass, ClassLoader classLoader)

    给定存档文件和完全配置的类加载器,启动应用程序。

    launch(args, getMainClass(), classLoader);
    protected void launch(String[] args, String mainClass, ClassLoader classLoader)
    			throws Exception {
        	  // <1> 设置 LaunchedURLClassLoader 作为类加载器
    		Thread.currentThread().setContextClassLoader(classLoader);
        	 // <2> 创建 MainMethodRunner 对象,并执行 run 方法,启动 Spring Boot 应用
    		createMainMethodRunner(mainClass, args, classLoader).run();
    	}
    

    <1> 处:设置 LaunchedURLClassLoader 作为类加载器,从而保证能够从 jar 包中加载到相应的类。

    getMainClass()
    // ExecutableArchiveLauncher.java
    
    @Override
    protected String getMainClass() throws Exception {
        // 获得启动的类的全名
     Manifest manifest = this.archive.getManifest();
     String mainClass = null;
     if (manifest != null) {
      mainClass = manifest.getMainAttributes().getValue("Start-Class");
     }
     if (mainClass == null) {
      throw new IllegalStateException(
        "No 'Start-Class' manifest entry specified in " + this);
     }
     return mainClass;
    }
    

    jar 包的 MANIFEST.MF 文件的 Start-Class 配置项,,获得我们设置的 Spring Boot 的启动类。

    createMainMethodRunner
    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
    			ClassLoader classLoader) {
    		return new MainMethodRunner(mainClass, args);
    	}
    
    run()
    public void run() throws Exception {
        // <1> 加载 Spring Boot
       Class<?> mainClass = Thread.currentThread().getContextClassLoader()
             .loadClass(this.mainClassName);
      // <2> 反射调用 main 方法
       Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
       mainMethod.invoke(null, new Object[] { this.args });
    }
    

    该方法负责最终的 Spring Boot 应用真正的启动

    SpringBoot自定义的类加载器: LaunchedURLClassLoader

    LaunchedURLClassLoader 是 spring-boot-loader 项目自定义的类加载器,实现对 jar 包中 META-INF/classes 目录下的META-INF/lib 内嵌的 jar 包中的加载

    该ClassLoader继承自UrlClassLoader。UrlClassLoader加载class就是依靠初始参数传入的Url数组,并且尝试从Url指向的资源中加载Class文件

    //LaunchedURLClassLoader.java
    
    protected Class<?> loadClass(String name, boolean resolve)
          throws ClassNotFoundException {
       Handler.setUseFastConnectionExceptions(true);
       try {
          try {
              //尝试根据类名去定义类所在的包,即java.lang.Package,确保jar in jar里匹配的manifest能够和关联的package关联起来
             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.
    
                //这里异常表明,definePackageIfNecessary方法的作用实际上是预先过滤掉查找不到的包
                throw new AssertionError("Package " + name + " has already been "
                      + "defined but it could not be found");
             }
          }
          return super.loadClass(name, resolve);
       }
       finally {
          Handler.setUseFastConnectionExceptions(false);
       }
    }
    

    方法super.loadClass(name, resolve)实际上会回到了java.lang.ClassLoader#loadClass(java.lang.String, boolean),遵循双亲委派机制进行查找类,而Bootstrap ClassLoader和Extension ClassLoader将会查找不到fat jar依赖的类,最终会来到Application ClassLoader,调用java.net.URLClassLoader#findClass

    总结

    • SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载
    • SpringBoot通过扩展URLClassLoader--LauncherURLClassLoader,实现了jar in jar中class文件的加载
    • JarLauncher通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,实现了fat jar的启动
    • WarLauncher通过加载WEB-INF/classes目录及WEB-INF/lib和WEB-INF/lib-provided目录下的jar文件,实现了war文件的直接启动及web容器中的启动
  • 相关阅读:
    Linux基础命令-cp
    Linux基础命令-mkdir
    Linux基础命令-touch
    Linux基础命令-diff
    Linux基础命令-cut
    Linux基础命令-stat
    System.Web.HttpException: 请求在此上下文中不可用
    数据库日志删除、压缩操作
    如何收缩和删除SQL日志文件
    Excel 常用宏代码大全
  • 原文地址:https://www.cnblogs.com/ideaAI/p/13870105.html
Copyright © 2011-2022 走看看