1、类加载器的创建

JAR 启动是由系统类加载器加载 JarLauncher 启动的,因此在JarLauncher 的main方法中,可以看到

  1. public class JarLauncher extends ExecutableArchiveLauncher {
  2. static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
  3. static final String BOOT_INF_LIB = "BOOT-INF/lib/";
  4. public JarLauncher() {
  5. }
  6. protected JarLauncher(Archive archive) {
  7. super(archive);
  8. }
  9. @Override
  10. protected boolean isNestedArchive(Archive.Entry entry) {
  11. if (entry.isDirectory()) {
  12. return entry.getName().equals(BOOT_INF_CLASSES);
  13. }
  14. return entry.getName().startsWith(BOOT_INF_LIB);
  15. }
  16. public static void main(String[] args) throws Exception {
  17. new JarLauncher().launch(args);
  18. }
  19. }

调用的JarLauncher的无参构造器,JarLauncher 继承 ExecutableArchiveLauncher ,并调用 ExecutableArchiveLauncher的无参构造函数方法, 该方法中调用 createArchive 尝试构造 Archive,Archive 是压缩,存档的意思。

  1. public ExecutableArchiveLauncher() {
  2. try {
  3. this.archive = createArchive();
  4. }
  5. catch (Exception ex) {
  6. throw new IllegalStateException(ex);
  7. }
  8. }
  9. protected final Archive createArchive() throws Exception {
  10. ProtectionDomain protectionDomain = getClass().getProtectionDomain();
  11. CodeSource codeSource = protectionDomain.getCodeSource();
  12. URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
  13. // 这里的path指的就是启动的绝对路径
  14. String path = (location != null) ? location.getSchemeSpecificPart() : null;
  15. if (path == null) {
  16. throw new IllegalStateException("Unable to determine code source archive");
  17. }
  18. // 判断该JAR文件是否存在
  19. File root = new File(path);
  20. if (!root.exists()) {
  21. throw new IllegalStateException("Unable to determine code source archive from " + root);
  22. }
  23. // 如果是目录的话,构造的是展开的归档,否则就是JAR的归档
  24. return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
  25. }

在构造 JarLauncher 完成之后,会创建一个归档,然后执行JarLauncher 的launch() 方法

  1. protected void launch(String[] args) throws Exception {
  2. JarFile.registerUrlProtocolHandler();
  3. // 重点 创建自定义的构造器,getClassPathArchives返回了classes目录和多个内部Lib的信息
  4. ClassLoader classLoader = createClassLoader(getClassPathArchives());
  5. launch(args, getMainClass(), classLoader);
  6. }
  7. // getClassPathArchives 方法是ExecutableArchiveLauncher的一个方法,返回的是是JAR和classes嵌套目录
  8. // 这里调用了 getClassPathArchives,这个方法是子类实现的方法,内容如下,标识是否是嵌套的Archive
  9. protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
  10. // 根据类路径的数据,创建等同大小的集合并搜索URL
  11. List<URL> urls = new ArrayList<>(archives.size());
  12. for (Archive archive : archives) {
  13. urls.add(archive.getUrl());
  14. }
  15. // urls 形式如同
  16. // jar:file:/Users/zhoutao/workspace/MyGithub/SpringBoot/build/libs/springboot-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/
  17. // jar:file:/Users/zhoutao/workspace/MyGithub/SpringBoot/build/libs/springboot-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-starter-web-2.2.6.RELEASE.jar!/
  18. return createClassLoader(urls.toArray(new URL[0]));
  19. }
  20. /**
  21. * Create a classloader for the specified URLs.
  22. * @param urls the URLs
  23. * @return the classloader
  24. * @throws Exception if the classloader cannot be created
  25. */
  26. protected ClassLoader createClassLoader(URL[] urls) throws Exception {
  27. // 创建自定义类加载器 LaunchedURLClassLoader ,这和上篇文章中打印出来的加载器一致
  28. // 显然这也是符合类的双亲委派机制,显然这里LaunchedURLClassLoader的父级类加载器为应用类加载器
  29. return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
  30. }

最终自定义的CLassLoader创建完成之后,我们在调试界面可以看到如下内容,可以看到新的类加载器为LauncherURLClassLoader ,其父类为常规的应用类加载器,应用类加载器的父类为拓展类加载器。所以SpringBoot的类加载器也是符合双亲委派机制的。

image.png

2、启动luancher方法

在创建类加载器完成之后,程序继续运行,调用getMainClass() 方法,这个方法是获取JAR文件中MAINFEST.MF 文件中的Start-class 属性,也就是启动的类名。

  1. @Override
  2. protected String getMainClass() throws Exception {
  3. Manifest manifest = this.archive.getManifest();
  4. String mainClass = null;
  5. if (manifest != null) {
  6. mainClass = manifest.getMainAttributes().getValue("Start-Class");
  7. }
  8. if (mainClass == null) {
  9. throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
  10. }
  11. // 此时值为: com.zhoutao123.spring.springboot.SpringbootApplication
  12. return mainClass;
  13. }
  14. /* MAINFEST.MF 内容如下
  15. $ cat ./build/libs/springboot-0.0.1-SNAPSHOT/META-INF/MANIFEST.MF
  16. Manifest-Version: 1.0
  17. Start-Class: com.zhoutao123.spring.springboot.SpringbootApplication
  18. Spring-Boot-Classes: BOOT-INF/classes/
  19. Spring-Boot-Lib: BOOT-INF/lib/
  20. Spring-Boot-Version: 2.2.6.RELEASE
  21. Main-Class: org.springframework.boot.loader.JarLauncher */

获取到启动类的全类名之后,继续调用 launch(String[] args, String mainClass, ClassLoader classLoader) 方法,并尝试创建 MainMethodRunner 对象

  1. /**
  2. * Launch the application given the archive file and a fully configured classloader.
  3. * @param args the incoming arguments
  4. * @param mainClass the main class to run
  5. * @param classLoader the classloader
  6. * @throws Exception if the launch fails
  7. */
  8. protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
  9. Thread.currentThread().setContextClassLoader(classLoader);
  10. createMainMethodRunner(mainClass, args, classLoader).run();
  11. }
  12. /**
  13. * Create the {@code MainMethodRunner} used to launch the application.
  14. * @param mainClass the main class
  15. * @param args the incoming arguments
  16. * @param classLoader the classloader
  17. * @return the main method runner
  18. */
  19. protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
  20. return new MainMethodRunner(mainClass, args);
  21. }
  22. /** MainClass 值得调用就是Start-class的main()方法
  23. 在run方法中通过 反射获取main方法,
  24. 然后将args参数传递,并调用 mainMethod.invoke 指定StartClass的main方法
  25. */
  26. public class MainMethodRunner {
  27. private final String mainClassName;
  28. private final String[] args;
  29. /**
  30. * Create a new {@link MainMethodRunner} instance.
  31. * @param mainClass the main class
  32. * @param args incoming arguments
  33. */
  34. public MainMethodRunner(String mainClass, String[] args) {
  35. this.mainClassName = mainClass;
  36. this.args = (args != null) ? args.clone() : null;
  37. }
  38. public void run() throws Exception {
  39. Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
  40. Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
  41. // 调用静态方法(也就是类的方法,而非实例的方法,instance可以不需要,设置为null即可)
  42. mainMethod.invoke(null, new Object[] { this.args });
  43. }
  44. }

需要注意的是launcher 方法中 Thread.currentThread().setContextClassLoader(classLoader); 将自定的classLoader设置为当前上下文类加载器,然后 Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); 获取到了类加载器,加载MainClass 这里也在一次说明了SpringBoot应用的加载器确实是SpringBoot的自定义类加载器而非JDK的应用类加载器