关于图上图标含义,这里就不再赘述,烦请移步 IntelliJ IDEA Icon reference
流程分析
jar规范
对于 Java 标准的 jar 文件来说,规定在一个 jar 文件中,我们必须要将指定 main.class 的类直接放置在文件的顶层目录中(也就是说,它不予许被嵌套),否则将无法加载,对于 BOOT-INF/class/ 路径下的 class 因为不在顶层目录,因此也是无法直接进行加载, 而对于 BOOT-INF/lib/ 路径的 jar 属于嵌套的(Fatjar),也是不能直接加载,因此 Spring 要想启动加载,就需要自定义实现自己的类加载器去加载。
/** * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are * included inside a {@code /BOOT-INF/lib} directory and that application classes are * included inside a {@code /BOOT-INF/classes} directory. * * 用于基于JAR的归档。这个启动程序假设依赖jar包含在{@code /BOOT-INF/lib}目录中, * 应用程序类包含在{@code /BOOT-INF/classes}目录中 * * @author Phillip Webb * @author Andy Wilkinson */
紧接着,要进行源码分析,那肯定是找到入口,一步步深入,那么对于 JarLauncher 就是它的 main 方法了
/** * 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. * * 启动一个应用,这个方法应该被初始的入口点,这个入口点应该是一个Launcher的子类的 * public static void main(String[] args)这样的方法调用 * * @param args the incoming arguments * @throws Exception if the application fails to launch */ protectedvoidlaunch(String[] args)throws Exception { // 1. 注册一些 URL的属性 JarFile.registerUrlProtocolHandler(); // 2. 创建类加载器(LaunchedURLClassLoader),加载得到集合要么是BOOT-INF/classes/ // 或者BOOT-INF/lib/的目录或者是他们下边的class文件或者jar依赖文件 ClassLoaderclassLoader= createClassLoader(getClassPathArchives()); // 3. 启动给定归档文件和完全配置的类加载器的应用程序 launch(args, getMainClass(), classLoader); }
/** * Returns nested {@link Archive}s for entries that match the specified filter. * * 返回与过滤器相匹配的嵌套归档文件 * * @param filter the filter used to limit entries * @return nested archives * @throws IOException if nested archives cannot be read */ List<Archive> getNestedArchives(EntryFilter filter)throws IOException;
/** * Determine if the specified {@link JarEntry} is a nested item that should be added * to the classpath. The method is called once for each entry. * * 确定指定的{@link JarEntry}是否是应该添加到类路径的嵌套项。对每个条目调用该方法一次 * * @param entry the jar entry * @return {@code true} if the entry is a nested item (jar or folder) */ protectedabstractbooleanisNestedArchive(Archive.Entry entry);
/** * Create a classloader for the specified archives. * * 创建一个所指定归档文件的类加载器 * * @param archives the archives * @return the classloader * @throws Exception if the classloader cannot be created */ protected ClassLoader createClassLoader(List<Archive> archives)throws Exception { List<URL> urls = newArrayList<>(archives.size()); // 遍历传进来的 archives,将每一个 Archive 的 URL(归档文件在磁盘上的完整路径)添加到 urls 集合中 for (Archive archive : archives) { urls.add(archive.getUrl()); } // return createClassLoader(urls.toArray(newURL[0])); }
/** * Create a classloader for the specified URLs. * * 创建指定 URL 的类加载器 * * @param urls the URLs * @return the classloader * @throws Exception if the classloader cannot be created */ protected ClassLoader createClassLoader(URL[] urls)throws Exception { // 这里的 LaunchedURLClassLoader 是 SpringBoot loader 给我们提供的一个全新的类加载器 // 参数 urls 是 class 文件或者资源配置文件的路径地址 // 参数 getClass().getClassLoader() 是应用类加载器 returnnewLaunchedURLClassLoader(urls, getClass().getClassLoader()); }
/** * 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 */ publicLaunchedURLClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); }
/** * Returns the main class that should be launched. * @return the name of the main class * @throws Exception if the main class cannot be obtained */ protectedabstract String getMainClass()throws Exception;
/** * 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 * @throws Exception if the launch fails */ protectedvoidlaunch(String[] args, String mainClass, ClassLoader classLoader)throws Exception { // 将应用的加载器换成了自定义的 LaunchedURLClassLoader 加载器,然后入到线程类加载器中 // 最终在未来的某个地方,通过线程的上下文中取出类加载进行加载 Thread.currentThread().setContextClassLoader(classLoader); // 创建一个主方法运行器运行 createMainMethodRunner(mainClass, args, classLoader).run(); }
/** * Create the {@code MainMethodRunner} used to launch the application. * * 创建一个 MainMethodRunner 用于启动这个应用 * * @param mainClass the main class * @param args the incoming arguments * @param classLoader the classloader * @return the main method runner */ protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { returnnewMainMethodRunner(mainClass, args); }
/** * Utility class that is used by {@link Launcher}s to call a main method. The class * containing the main method is loaded using the thread context class loader. * * 被 Launcher 使用来调用 main 方法的辅助类,使用线程类加载来加载包含 main 方法的类 * * @author Phillip Webb * @author Andy Wilkinson */ publicclassMainMethodRunner {
privatefinal String mainClassName;
privatefinal String[] args;
/** * Create a new {@link MainMethodRunner} instance. * @param mainClass the main class * @param args incoming arguments */ publicMainMethodRunner(String mainClass, String[] args) { this.mainClassName = mainClass; this.args = (args != null) ? args.clone() : null; }