观察 archive 的值,会发现就是 jar 在本地的绝对路径,前面的 jar:file:/ 和后面的 !/ 都是 jar 规范。
launch(args)
/** * Launch the application. This method is the initial entry point that should be * called by a subclass {@code public static void main(String[] args)} method. * @param args the incoming arguments * @throwsException if the application fails to launch */protectedvoidlaunch(String[] args) throws Exception {JarFile.registerUrlProtocolHandler();ClassLoader classLoader =createClassLoader(getClassPathArchives());launch(args, getMainClass(), classLoader);}
根据 isNestedArchive 方法提供的判断,来构建 List。这里的 for 循环,是把整个被执行 jar 文件的每一个文件和目录都循环一次,当满足 isNestedArchive 要求(目录等于 BOOT_INF_CLASSES 或者文件等于 BOOT_INF_LIB 下的每一个三方依赖 jar)的 entry 都会被加入到集合当中,并且返回。
createClassLoader
/** * Create a classloader for the specified archives. * @param archives the archives * @return the classloader * @throwsException if the classloader cannot be created */protectedClassLoadercreateClassLoader(List<Archive> archives) throws Exception {List<URL> urls =newArrayList<>(archives.size());for (Archive archive : archives) {urls.add(archive.getUrl()); }returncreateClassLoader(urls.toArray(newURL[0]));}
进行一次 for 循环,把 List 转换成 List,再观察 List 的值,这里转换成 List 的原因是,Spring Boot 自定义的类加载器 LaunchedURLClassLoader,其实也是继承 URLClassLoader,最终通过 URL 来进行类加载器的加载。
继续跟踪。
/** * Create a classloader for the specified URLs. * @param urls the URLs * @return the classloader * @throwsException if the classloader cannot be created */protectedClassLoadercreateClassLoader(URL[] urls) throws Exception {returnnewLaunchedURLClassLoader(urls, getClass().getClassLoader());}
上面的过程,也解释了为什么 Spring Boot 要把 spring-boot-loader.jar 整个 jar 包直接解压到 Spring Boot 应用 jar 包的最顶层,而不是采用传递依赖方式。通过这种方式,由系统类加载器来加载 Launcher 这些类,然后在由自定义的 LaunchedURLClassLoader 来加载 BOOT-INF 下面的工程文件和三方依赖 jar 文件。
/** * Launch the application given the archive file and a fully configured classloader. * @param args the incoming arguments * @param mainClass the main class to run * @param classLoader the classloader * @throwsException if the launch fails */protectedvoidlaunch(String[] args,String mainClass,ClassLoader classLoader) throws Exception {Thread.currentThread().setContextClassLoader(classLoader);createMainMethodRunner(mainClass, args, classLoader).run();}
第一行代码把自定义的 classloader 设置到当前线程上下文类加载器,在默认情况下,当前线程上下文类加载器就是 AppClassLoader。通过这种方式就把当前线程上下文的默认类加载器换成了 Spring Boot 自定义的类加载器。
/** * Create the {@code MainMethodRunner} used to launch the application. * @param mainClass the main class * @param args the incoming arguments * @param classLoader the classloader * @return the main method runner */protectedMainMethodRunnercreateMainMethodRunner(String mainClass,String[] args,ClassLoader classLoader) {returnnewMainMethodRunner(mainClass, args);}