• 精尽Spring Boot源码分析


    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读

    Spring Boot 版本:2.2.x

    最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章

    如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~

    该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》

    概述

    Spring Boot 提供了 Maven 插件 spring-boot-maven-plugin,可以很方便的将我们的 Spring Boot 项目打成 jar 包或者 war 包。

    考虑到部署的便利性,我们绝大多数(99.99%)的场景下,都会选择打成 jar 包,这样一来,我们就无需将项目部署于 Tomcat、Jetty 等 Servlet 容器中。

    那么,通过 Spring Boot 插件生成的 jar 包是如何运行,并启动 Spring Boot 应用的呢?这个就是本文的目的,我们一起来弄懂 Spring Boot jar 包的运行原理

    这里,我通过 Spring Boot Maven Plugin 生成了一个 jar 包,其里面的结构如下所示:

    1. BOOT-INF 目录,里面保存了我们自己 Spring Boot 项目编译后的所有文件,其中 classes 目录下面就是编译后的 .class 文件,包括项目中的配置文件等,lib 目录下就是我们引入的第三方依赖
    2. META-INF 目录,通过 MANIFEST.MF 文件提供 jar 包的元数据,声明 jar 的启动类等信息。每个 Java jar 包应该是都有这个文件的,参考 Oracle 官方对于 jar 的说明,里面有一个 Main-Class 配置用于指定启动类
    3. org.springframework.boot.loader 目录,也就是 Spring Boot 的 spring-boot-loader 工具模块,它就是 java -jar xxx.jar 启动 Spring Boot 项目的秘密所在,上面的 Main-Class 指定的就是该工具模块中的一个类

    MANIFEST.MF

    META-INF/MANIFEST.MF 文件如下:

    Manifest-Version: 1.0
    Implementation-Title: spring-boot-study
    Implementation-Version: 1.0.0-SNAPSHOT
    Built-By: jingping
    Implementation-Vendor-Id: org.springframework.boot.demo
    Spring-Boot-Version: 2.0.3.RELEASE
    Main-Class: org.springframework.boot.loader.JarLauncher # spring-boot-loader 中的启动类
    Start-Class: org.springframework.boot.demo.Application # 你的 Spring Boot 项目中的启动类
    Spring-Boot-Classes: BOOT-INF/classes/ # 你的 Spring Boot 项目编译后的 .class 文件所在目录
    Spring-Boot-Lib: BOOT-INF/lib/ # 你的 Spring Boot 项目所引入的第三方依赖所在目录
    Created-By: Apache Maven 3.6.3
    Build-Jdk: 1.8.0_251
    Implementation-URL: https://projects.spring.io/spring-boot/#/spring-boot-starter-parent/info-dependencies/dwzq-info/info-stock-project/sp-provider
    

    参考 Oracle 官方对该的说明:

    • Main-Class:Java 规定的 jar 包的启动类,这里设置为 spring-boot-loader 项目的 JarLauncher 类,进行 Spring Boot 应用的启动
    • Start-Class:Spring Boot 规定的启动类,这里通过 Spring Boot Maven Plugin 插件打包时,会设置为我们定义的 Application 启动类

    为什么不直接将我们的 Application 启动类设置为 Main-Class 启动呢?

    因为通过 Spring Boot Maven Plugin 插件打包后的 jar 包,我们的 .class 文件在 BOOT-INF/classes/ 目录下,在 Java 默认的 jar 包加载规则下找不到我们的 Application 启动类,也就需要通过 JarLauncher 启动加载。

    当然,还有一个原因,Java 规定可执行器的 jar 包禁止嵌套其它 jar 包,在 BOOT-INF/lib 目录下有我们 Spring Boot 应用依赖的所有第三方 jar 包,因此spring-boot-loader 项目自定义实现了 ClassLoader 实现类 LaunchedURLClassLoader,支持加载 BOOT-INF/classes 目录下的 .class 文件,以及 BOOT-INF/lib 目录下的 jar 包。

    接下来,我们一起来看看 Spring Boot 的 JarLauncher 这个类

    1. JarLauncher

    类图:

    上面的 WarLauncher 是针对 war 包的启动类,和 JarLauncher 差不多,感兴趣的可以看一看,这里我们直接来看到 JarLauncher 这个类

    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) {
    		// 只接受 `BOOT-INF/classes/` 目录
    		if (entry.isDirectory()) {
    			return entry.getName().equals(BOOT_INF_CLASSES);
    		}
    		// 只接受 `BOOT-INF/lib/` 目录下的 jar 包
    		return entry.getName().startsWith(BOOT_INF_LIB);
    	}
    
    	/**
    	 * 这里是 java -jar 启动 SpringBoot 打包后的 jar 包的入口
    	 * 可查看 jar 包中的 META-INF/MANIFEST.MF 文件(该文件用于对 Java 应用进行配置)
    	 * 参考 Oracle 官方对于 jar 的说明(https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html)
    	 * 该文件其中会有一个配置项:Main-Class: org.springframework.boot.loader.JarLauncher
    	 * 这个配置表示会调用 JarLauncher#main(String[]) 方法,也就当前方法
    	 */
    	public static void main(String[] args) throws Exception {
    		// <1> 创建当前类的实例对象,会创建一个 Archive 对象(当前应用),可用于解析 jar 包(当前应用)中所有的信息
    		// <2> 调用其 launch(String[]) 方法
    		new JarLauncher().launch(args);
    	}
    }
    

    可以看到它有个 main(String[]) 方法,前面说到的 META-INF/MANIFEST.MF 文件中的 Main-Class 配置就是指向了这个类,也就会调用这里的 main 方法,会做下面两件事:

    1. 创建一个 JarLauncher 实例对象,在 ExecutableArchiveLauncher 父类中会做以下事情:

      public abstract class ExecutableArchiveLauncher extends Launcher {
      
      	private final Archive archive;
      
      	public ExecutableArchiveLauncher() {
      		try {
      			// 为当前应用创建一个 Archive 对象,可用于解析 jar 包(当前应用)中所有的信息
      			this.archive = createArchive();
      		}
      		catch (Exception ex) {
      			throw new IllegalStateException(ex);
      		}
      	}
      	
      	protected final Archive createArchive() throws Exception {
      		// 获取 jar 包(当前应用)所在的绝对路径
      		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");
      		}
      		// 当前 jar 包
      		File root = new File(path);
      		if (!root.exists()) {
      			throw new IllegalStateException("Unable to determine code source archive from " + root);
      		}
      		// 为当前 jar 包创建一个 JarFileArchive(根条目),需要通过它解析出 jar 包中的所有信息
      		// 如果是文件夹的话则创建 ExplodedArchive(根条目)
      		return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
      	}
      }
      

      会为当前应用创建一个 Archive 对象,可用于解析 jar 包(当前应用)中所有的信息,可以把它理解为一个“根”对象,可以通过它获取我们所需要的类信息

    2. 调用 JarLauncher#launch(String[]) 方法,也就是调用父类 Launcher 的这个方法

    2. Launcher

    org.springframework.boot.loader.Launcher,Spring Boot 应用的启动器

    2. launch 方法

    public abstract class Launcher {
    
    	/**
    	 * 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
    	 * @throws Exception if the application fails to launch
    	 */
    	protected void launch(String[] args) throws Exception {
    		// <1> 注册 URL(jar)协议的处理器
    		JarFile.registerUrlProtocolHandler();
    		// <2> 先从 `archive`(当前 jar 包应用)解析出所有的 JarFileArchive
    		// <3> 创建 Spring Boot 自定义的 ClassLoader 类加载器,可加载当前 jar 中所有的类
    		ClassLoader classLoader = createClassLoader(getClassPathArchives());
    		// <4> 获取当前应用的启动类(你自己写的那个 main 方法)
    		// <5> 执行你的那个 main 方法
    		launch(args, getMainClass(), classLoader);
    	}
    }
    

    会做以下几件事:

    1. 调用 JarFile#registerUrlProtocolHandler() 方法,注册 URL(jar)协议的处理器,主要是使用自定义的 URLStreamHandler 处理器处理 jar 包
    2. 调用 getClassPathArchives() 方法,先从 archive(当前 jar 包应用)解析出所有的 JarFileArchive,这个 archive 就是在上面创建 JarLauncher 实例对象过程中创建的
    3. 调用 createClassLoader(List<Archive>) 方法,创建 Spring Boot 自定义的 ClassLoader 类加载器,可加载当前 jar 包中所有的类,包括依赖的第三方包
    4. 调用 getMainClass() 方法,获取当前应用的启动类(你自己写的那个 main 方法所在的 Class 类对象)
    5. 调用 launch(...) 方法,执行你的项目中那个启动类的 main 方法(反射)

    你可以理解为会创建一个自定义的 ClassLoader 类加载器,主要可加载 BOOT-INF/classes 目录下的类,以及 BOOT-INF/lib 目录下的 jar 包中的类,然后调用你 Spring Boot 应用的启动类的 main 方法

    接下来我们逐步分析上面的每个步骤

    2.1 registerUrlProtocolHandler 方法

    备注:注册 URL(jar)协议的处理器

    这个方法在 org.springframework.boot.loader.jar.JarFile 中,这个类是 java.util.jar.JarFile 的子类,对它进行扩展,提供更多的功能,便于操作 jar

    public static void registerUrlProtocolHandler() {
        // <1> 获取系统变量中的 `java.protocol.handler.pkgs` 配置的 URLStreamHandler 路径
        String handlers = System.getProperty(PROTOCOL_HANDLER, "");
        // <2> 将 Spring Boot 自定义的 URL 协议处理器路径(`org.springframework.boot.loader`)添加至系统变量中
        // JVM 启动时会获取 `java.protocol.handler.pkgs` 属性,多个用 `|` 分隔,以他们作为包名前缀,然后使用 `包名前缀.协议名.Handler` 作为该协议的实现
        // 那么这里就会将 `org.springframework.boot.loader.jar.Handler` 作为 jar 包协议的实现
        System.setProperty(PROTOCOL_HANDLER,
                ("".equals(handlers) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));
        // <3> 重置已缓存的 URLStreamHandler 处理器们,避免重复创建
        resetCachedUrlHandlers();
    }
    

    方法的处理过程如下:

    1. 获取系统变量中的 java.protocol.handler.pkgs 配置的 URLStreamHandler 路径

    2. 将 Spring Boot 自定义的 URL 协议处理器路径(org.springframework.boot.loader)添加至系统变量中

      JVM 启动时会获取 java.protocol.handler.pkgs 属性,多个用 | 分隔,以他们作为包名前缀,然后使用 包名前缀.协议名.Handler 作为该协议的实现

      那么这里就会将 org.springframework.boot.loader.jar.Handler 作为 jar 包协议的实现,用于处理 jar 包

    3. 重置已缓存的 URLStreamHandler 处理器们,避免重复创建

      private static void resetCachedUrlHandlers() {    try {        URL.setURLStreamHandlerFactory(null);    } catch (Error ex) {        // Ignore    }}
      

    2.2 getClassPathArchives 方法

    备注:从 archive(当前 jar 包应用)解析出所有的 JarFileArchive

    该方法在 org.springframework.boot.loader.ExecutableArchiveLauncher 子类中实现,如下:

    @Overrideprotected List<Archive> getClassPathArchives() throws Exception {    // <1> 创建一个 Archive.EntryFilter 类,用于判断 Archive.Entry 是否匹配,过滤 jar 包(当前应用)以外的东西    // <2> 从 `archive`(当前 jar 包)解析出所有 Archive 条目信息    List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));    postProcessClassPathArchives(archives);    // <3> 返回找到的所有 JarFileArchive    // `BOOT-INF/classes/` 目录对应一个 JarFileArchive(因为就是当前应用中的内容)    // `BOOT-INF/lib/` 目录下的每个 jar 包对应一个 JarFileArchive    return archives;}
    

    过程如下:

    1. 创建一个 Archive.EntryFilter 实现类,用于判断 Archive.Entry 是否匹配,过滤掉 jar 包(当前应用)以外的东西

      public class JarLauncher extends ExecutableArchiveLauncher {	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);	}}
      
    2. archive(当前 jar 包)解析出所有 Archive 条目信息,这个 archive 在上面 1. JarLauncher 讲到过,创建 JarLauncher 实例化对象的时候会初始化 archive,是一个 JarFileArchive 对象,也就是我们打包后的 jar 包,那么接下来需要从中解析出所有的 Archive 对象

      // JarFileArchive.java@Overridepublic List<Archive> getNestedArchives(EntryFilter filter) throws IOException {    List<Archive> nestedArchives = new ArrayList<>();    // 遍历 jar 包(当前应用)中所有的 Entry    for (Entry entry : this) {        // 进行过滤,`BOOT-INF/classes/` 目录或者 `BOOT-INF/lib/` 目录下的 jar 包        if (filter.matches(entry)) {            // 将 Entry 转换成 JarFileArchive            nestedArchives.add(getNestedArchive(entry));        }    }    // 返回 jar 包(当前应用)找到的所有 JarFileArchive    // `BOOT-INF/classes/` 目录对应一个 JarFileArchive(因为就是当前应用中的内容)    // `BOOT-INF/lib/` 目录下的每个 jar 包对应一个 JarFileArchive    return Collections.unmodifiableList(nestedArchives);}
      

      返回 jar 包(当前应用)找到的所有 JarFileArchive:

      • BOOT-INF/classes/ 目录对应一个 JarFileArchive(因为就是当前 Spring Boot 应用编译后的内容)
      • BOOT-INF/lib/ 目录下的每个 jar 包对应一个 JarFileArchive
    3. 返回从 jar 包中找到的所有 JarFileArchive

    这一步骤就是从 jar 包中解析出我们需要的东西来,如上描述,每个 JarFileArchive 会对应一个 JarFile 对象

    2.3 createClassLoader 方法

    备注:创建 Spring Boot 自定义的 ClassLoader 类加载器,可加载当前 jar 中所有的类

    protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {    // <1> 获取所有 JarFileArchive 对应的 URL    List<URL> urls = new ArrayList<>(archives.size());    for (Archive archive : archives) {        urls.add(archive.getUrl());    }    // <2> 创建 Spring Boot 自定义的 ClassLoader 类加载器,并设置父类加载器为当前线程的类加载器    // 通过它解析这些 URL,也就是加载 `BOOT-INF/classes/` 目录下的类和 `BOOT-INF/lib/` 目录下的所有 jar 包    return createClassLoader(urls.toArray(new URL[0]));}protected ClassLoader createClassLoader(URL[] urls) throws Exception {    return new LaunchedURLClassLoader(urls, getClass().getClassLoader());}
    

    该过程如下:

    1. 获取所有 JarFileArchive 对应的 URL
    2. 创建 Spring Boot 自定义的 ClassLoader 类加载器,并设置父类加载器为当前线程的类加载器

    可以看到 LaunchedURLClassLoader 为自定义类加载器,这样就能从我们 jar 包中的 BOOT-INF/classes/ 目录下和 BOOT-INF/lib/ 目录下的所有三方依赖包中加载出 Class 类对象

    2.4 getMainClass 方法

    备注:获取当前应用的启动类(你自己写的那个 main 方法)

    // ExecutableArchiveLauncher.java@Overrideprotected String getMainClass() throws Exception {    // 获取 jar 包(当前应用)的 Manifest 对象,也就是 META-INF/MANIFEST.MF 文件中的属性    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;}
    

    过程如下:

    1. 获取 jar 包(当前应用)的 Manifest 对象,也就是 META-INF/MANIFEST.MF 文件中的属性
    2. 获取启动类(当前应用自己的启动类),也就是 Start-Class 配置,并返回

    可以看到,这一步就是找到你 Spring Boot 应用的启动类,前面 ClassLoader 类加载器都准备好了,那么现在不就可以直接调用这个类的 main 方法来启动应用了

    2.5 launch 方法

    备注:执行你的 Spring Boot 应用的启动类的 main 方法

    protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {    // 设置当前线程的 ClassLoader 为刚创建的类加载器    Thread.currentThread().setContextClassLoader(classLoader);    // 创建一个 MainMethodRunner 对象(main 方法执行器)    // 执行你的 main 方法(反射)    createMainMethodRunner(mainClass, args, classLoader).run();}protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {    return new MainMethodRunner(mainClass, args);}
    

    整个过程很简单,先设置当前线程的 ClassLoader 为刚创建的类加载器,然后创建一个 MainMethodRunner 对象(main 方法执行器),执行你的 main 方法(反射),启动 Spring Boot 应用

    public class MainMethodRunner {	private final String mainClassName;	private final String[] args;	public MainMethodRunner(String mainClass, String[] args) {		this.mainClassName = mainClass;		this.args = (args != null) ? args.clone() : null;	}	public void run() throws Exception {		// 根据名称加载 main 方法所在类的 Class 对象		Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);		// 获取 main 方法		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);		// 执行这个 main 方法(反射)		mainMethod.invoke(null, new Object[] { this.args });	}}
    

    这里就是通过反射调用你的 Spring Boot 应用的启动类的 main 方法

    LaunchedURLClassLoader

    org.springframework.boot.loader.LaunchedURLClassLoaderspring-boot-loader 中自定义的类加载器,实现对 jar 包中 BOOT-INF/classes 目录下的BOOT-INF/lib 下第三方 jar 包中的加载

    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);	}	/**	 * 重写类加载器中加载 Class 类对象方法	 */	@Override	protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {		Handler.setUseFastConnectionExceptions(true);		try {			try {				// 判断这个类是否有对应的 Package 包				// 没有的话会从所有 URL(包括内部引入的所有 jar 包)中找到对应的 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.					throw new AssertionError("Package " + name + " has already been defined but it could not be found");				}			}			// 加载对应的 Class 类对象			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);			// 没找到对应的 Package 包则进行解析			if (getPackage(packageName) == null) {				try {					// 遍历所有的 URL,从所有的 jar 包中找到这个类对应的 Package 包并进行设置					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>) () -> {				// 把类路径解析成类名并加上 .class 后缀				String packageEntryName = packageName.replace('.', '/') + "/";				String classEntryName = className.replace('.', '/') + ".class";				// 遍历所有的 URL(包括应用内部引入的所有 jar 包)				for (URL url : getURLs()) {					try {						URLConnection connection = url.openConnection();						if (connection instanceof JarURLConnection) {							JarFile jarFile = ((JarURLConnection) connection).getJarFile();							// 如果这个 jar 中存在这个类名,且有对应的 Manifest							if (jarFile.getEntry(classEntryName) != null && jarFile.getEntry(packageEntryName) != null									&& jarFile.getManifest() != null) {								// 定义这个类对应的 Package 包								definePackage(packageName, jarFile.getManifest(), url);								return null;							}						}					}					catch (IOException ex) {						// Ignore					}				}				return null;			}, AccessController.getContext());		}		catch (java.security.PrivilegedActionException ex) {			// Ignore		}	}}
    

    上面的代码就不一一讲述了,LaunchedURLClassLoader 重写了 ClassLoader 的 loadClass(String, boolean) 加载 Class 类对象方法,在加载对应的 Class 类对象之前新增了一部分逻辑,会尝试从 jar 包中定义 Package 包对象,这样就能加载到对应的 Class 类对象。

    总结

    Spring Boot 提供了 Maven 插件 spring-boot-maven-plugin,可以很方便的将我们的 Spring Boot 项目打成 jar 包,jar 包中主要分为三个模块:

    • BOOT-INF 目录,里面保存了我们自己 Spring Boot 项目编译后的所有文件,其中 classes 目录下面就是编译后的 .class 文件,包括项目中的配置文件等,lib 目录下就是我们引入的第三方依赖
    • META-INF 目录,通过 MANIFEST.MF 文件提供 jar 包的元数据,声明 jar 的启动类等信息。每个 Java jar 包应该是都有这个文件的,参考 Oracle 官方对于 jar 的说明,里面有一个 Main-Class 配置用于指定启动类
    • org.springframework.boot.loader 目录,也就是 Spring Boot 的 spring-boot-loader 子模块,它就是 java -jar xxx.jar 启动 Spring Boot 项目的秘密所在,上面的 Main-Class 指定的就是里面的一个类

    通过 java -jar 启动应用时,根据 Main-Class 配置会调用 org.springframework.boot.loader.JarLaunchermain(String[]) 方法;其中会先创建一个自定义的 ClassLoader 类加载器,可从BOOT-INF目录下加载出我们 Spring Boot 应用的 Class 类对象,包括依赖的第三方 jar 包中的 Class 类对象;然后根据 Start-Class 配置调用我们 Spring Boot 应用启动类的 main(String[]) 方法(反射),这样也就启动了应用,至于我们的 main(String[]) 方法中做了哪些事情,也就是后续所讲的内容。

  • 相关阅读:
    第十四届中北大学ACM程序设计竞赛 J.ZBT的游戏
    洛谷P1248 加工生产调度
    洛谷P1736 创意吃鱼法
    洛谷P3372 【模板】线段树 1
    洛谷P1330 封锁阳光大学
    洛谷P3275 [SCOI2011]糖果
    Android 开发60条技术经验总结(转)
    Genymotion常见问题汇总(转)
    页面跳转与数据传递
    网络编程(二)
  • 原文地址:https://www.cnblogs.com/lifullmoon/p/14953064.html
Copyright © 2020-2023  润新知