✅SpringBoot和传统的双亲委派有什么不一样吗?

✅SpringBoot和传统的双亲委派有什么不一样吗?

典型回答

传统的采用双亲委派的类加载机制大家都知道,要加载一个类的额时候,先委托父类加载器加载该类;如果父类加载器无法找到(类不存在),才由当前 ClassLoader 加载;这样保证了核心类库(rt.jar)不会被重复加载,避免了类冲突问题。

在JDK 1.8之前,传统的双亲委派如下:

✅什么是双亲委派?如何破坏?

在JDK 1.9中,传统的双亲委派如下:

✅JDK1.8和1.9中类加载器有哪些不同

Spring Boot 打破了传统的双亲委派模型,采用 自定义的 **LaunchedURLClassLoader** 进行类加载,主要为了支持 “Fat JAR”(可执行 JAR) 的运行。


✅什么是fat jar?


在 Spring Boot 中,使用 Maven 或 Gradle 构建项目时,lib/ 目录中的第三方依赖是以 JAR 形式打包进主 JAR 内部,默认会生成一个包含所有依赖项的 fat jar。

传统的 <font style="background-color:#FBDE28;">Application ClassLoader</font> 只能从 外部 **<font style="background-color:#FBDE28;">classpath</font>** 加载类,无法直接加载 JAR 包内嵌的其他 JAR(fat jar)。 因此 Spring Boot 需要自定义类加载器。

为了支持 Fat JAR 运行模式,Spring Boot **使用 **LaunchedURLClassLoader** 替代 ****AppClassLoader**,打破双亲委派机制,核心做法是:

  • 先加载 **BOOT-INF/classes** 目录下的应用类(优先于 JDK 类)。
  • 再加载 **BOOT-INF/lib/** 目录下的依赖 JAR(传统 AppClassLoader 无法加载嵌套 JAR)。
  • 最后才交给父类加载器(即 JDK 提供的 AppClassLoader)。

也就是说Spring Boot 先尝试加载自身的类和依赖 JAR,找不到才交给父类加载器,从而打破双亲委派。

Talk is Cheap,Show me the Code:

https://github.com/joansmith/spring-boot/blob/master/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java

以下是LaunchedURLClassLoader 的源码:

@Override
	protected Class<?> loadClass(String name, boolean resolve)
			throws ClassNotFoundException {
		synchronized (LaunchedURLClassLoader.LOCK_PROVIDER.getLock(this, name)) {
			Class<?> loadedClass = findLoadedClass(name);
			if (loadedClass == null) {
				Handler.setUseFastConnectionExceptions(true);
				try {
					loadedClass = doLoadClass(name);
				}
				finally {
					Handler.setUseFastConnectionExceptions(false);
				}
			}
			if (resolve) {
				resolveClass(loadedClass);
			}
			return loadedClass;
		}
	}

	private Class<?> doLoadClass(String name) throws ClassNotFoundException {

		// 1) Try the root class loader
		try {
			if (this.rootClassLoader != null) {
				return this.rootClassLoader.loadClass(name);
			}
		}
		catch (Exception ex) {
			// Ignore and continue
		}

		// 2) Try to find locally
		try {
			findPackage(name);
			Class<?> cls = findClass(name);
			return cls;
		}
		catch (Exception ex) {
			// Ignore and continue
		}

		// 3) Use standard loading
		return super.loadClass(name, false);
	}

可以看到,在loadClass方法中,主要调用doLoadClass,仔细看doLoadClass的第37行,和第45行,可以看到,先执行37行的findClass,然后再执行45行的super.loadClass,这就意味着,他会先自己加载,然后加载不到再委托给父加载器加载的。