呜,双亲委派模式伤心史
Posted 毛奇志
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了呜,双亲委派模式伤心史相关的知识,希望对你有一定的参考价值。
一、前言
双亲委托模式不是固定死的,它就是Java指定的一个类加载的一个规则,你完全可以不遵守它,只要你能帮类加载到JVM内存中即可。
二、双亲委派模式
2.1 双亲委派模式的结构
JVM类加载机制的结构,即双亲委派模式的结构支持,如下:
如上图,双亲委派模式分为四个层
(1) Bootstrap ClassLoader :最顶层的加载类,主要加载核心类库,也就是我们环境变量下面%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar和class等;
(2) Extention ClassLoader :扩展的类加载器,加载目录%JRE_HOME%\\lib\\ext目录下的jar包和class文件;
(3) Appclass Loader: 加载当前应用的classpath的所有类;
(4) 自定义类加载器:程序员自己定义的类加载器。
2.2 先加载父类,再加载子类
双亲委派模式就是JVM默认的类加载方式,先加载父类,在加载子类,一步步来,Java 类加载遵循双亲委托模式,用白话说就是 子类类加载类之前都会咨询父类类加载器,这个类属不属于你的呀,你有加载过吗?如果BootStrapClassloader和ExtentionClassloader都说我没有加载过,ApplicationClassload才会加载。
我们常说的,双亲委派模式在JVM的源码支持,就是JavaSE源码支持,就是loadClass()方法里面的源码。JDK中具体的具体实现的双亲委托模式的,大家可以参考Xxx.class.getClassLoader().loadClass("")
源码,下面的代码就是典型双亲委托模式:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
synchronized (getClassLoadingLock(name))
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null)
long t0 = System.nanoTime();
try
if (parent != null)
c = parent.loadClass(name, false);
else
c = findBootstrapClassOrNull(name);
catch (ClassNotFoundException e)
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
if (c == null)
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
if (resolve)
resolveClass(c);
return c;
2.3 不可以跨模块加载
强调一下在目前双亲委托模式下,Bootstrap ClassLoader 不可以跨模块加载Appclass Loader,Appclass Loader也不可以跨模块加载Bootstrap ClassLoader,原因很简单,他们在自己的模块路径下中找不到要加载类,比如 com.mysql.jdbc.Driver 这个类,它的类路径是在classpath下,你在rt.jar下下,你怎么找到这个类。
2.4 小结
双亲委派模式小结:抓住定义,就可以知道为什么jdbct、tomcat、JNDI破坏了双亲委派模式。
第一,结构上,双亲委派模式是JVM的一种特性,JVM里面包括三个类加载器
Bootstrap ClassLoader :最顶层的加载类,主要加载核心类库,也就是我们环境变量下面%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar和class等;
Extention ClassLoader :扩展的类加载器,加载目录%JRE_HOME%\\lib\\ext目录下的jar包和class文件;
Appclass Loader: 加载当前应用的classpath的所有类。
第二,使用上,子类类加载类之前都会咨询父类类加载器(双亲委派模式就是JVM默认的类加载方式,先加载父类,在加载子类,一步步来,其实一开始学java的时候我们就见过了,这里解释原理罢了)
Java 类加载遵循双亲委托模式,即 子类类加载类之前都会咨询父类类加载器,这个类属不属于你的呀,你有加载过吗?如果BootStrapClassload,ExtentionClassload 都说我没有加载过,那么ApplicationClassload才会加载。
第三,使用上,不可以跨模块加载
双亲委托模式下,Bootstrap ClassLoader 不可以跨模块加载Appclass Loader,同理,Appclass Loader也不可以跨模块加载Bootstrap ClassLoader,原因很简单,他们在自己的模块路径下中找不到要加载类(比如:com.mysql.jdbc.Driver 这个类,它的类路径是在classpath下,你在rt.jar下下,你怎么找到这个类)。
抓住这三点,下面凡是破坏了任意一点的就是破坏了双亲委派模式。
三、JDBC的SPI机制违背双亲委派模式
3.1 为什么说JDBC破坏了双亲委派模式
为什么说JDBC破坏了双亲委派模式?先来看一个例子。
导入依赖
<dependency>
groupId>mysql /groupId>
artifactId> mysql-connector-java /artifactId>
version> 5.1.45 /version>
</dependency>
main方法
public class OOMTest
public static void main(String[] args)
Enumeration<Driver> drivers = DriverManager.getDrivers();
Driver driver;
while (drivers.hasMoreElements())
driver = drivers.nextElement();
System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
System.out.println(DriverManager.class.getClassLoader());
输出结果如下:
class com.mysql.jdbc.Driver-----sun.misc.Launcher$ AppClassLoader@18b4aac2
class com.mysql.fabric.jdbc.FabricMySQLDriver-----sun.misc.Launcher$ AppClassLoader@18b4aac2
DriverManager classLoader:null
解释(为什么说JDBC的SPI机制破坏了双亲委派模式):
可以看到代码中并没有调用 Class.forName(“”) 的代码,但DriverManager中已经加载了两个 jdbc 驱动,而却这两个驱动都是使用的应用类加载器(AppClassLoader)加载的,而DriverManager本身的类加载器即BootstrapClassLoader确实是 null ,按照双亲委派模型的规则,委派链如下: SystemApp class loader -> Extension class loader -> Bootstrap class loader
,父加载器BootstrapClassLoader是无法找到AppClassLoader加载的类的,所以,JDBC确实破坏了双亲委派模型,破坏了第二条,先加载父类,再加载子类。
3.2 JDBC是如何破坏JVM的双亲委派模式的
问题:JDBC是如何破坏JVM的双亲委派模式的?
回答:JDBC使用了SPI设计,这个设计破坏了JVM的双亲委派模式,先加载父类,再加载子类。
SPI机制,即获取META-INF/services/java.sql.Driver中下一个Driver的全限定名。以mysql-connector-java依赖为例,它是一个具体的数据库驱动,类似介绍spi三工程中的第二个工程,这个工程的配置文件java.sql.Driver文件中,有这些内容,就是在上面例子中输出结果就是这两个Driver。
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
就是我们说的SPI机制,java.util.ServiceLoader类中的读取内容,然后打印对象(并调用具体实现函数)。
3.3 JDBC是如何使用SPI机制的
问题:jdbc中如何使用SPI机制(Springboot一篇博客中说的很清楚,这里摘过来)?
回答:loadInitialDrivers()方法中扫描配置文件。 jdbc4.0以前,程序员需要基于Class.forName(“xxx”)的方式来装载驱动,程序员需要在代码中硬编码指定具体的数据库驱动;jdbc4.0之后,基于spi的机制来找到驱动提供商了,可以通过META-INF/services/java.sql.Driver文件里指定实现类的方式来暴露驱动提供者,不用再写Class.forName(“xxx”)硬编码。
static
loadInitialDrivers();
println("JDBC DriverManager initialized");
loadInitialDrivers 方法的部分
AccessController.doPrivileged(new PrivilegedAction<Void>()
public Void run()
//这里典型的SPI,Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制
// jdbc源码就像我们在上面三工程中的appdemo中一样使用SPI
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 第一,迭代出来
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try
while(driversIterator.hasNext())
driversIterator.next();
catch(Throwable t)
// Do nothing
return null;
);
小结:SPI机制在jdbc源码和springboot里面都有使用。
3.4 源码解析JavaSE中 java.sql.getConnection() 对SPI的支持
Java SE之前,数据库驱动加载,先编程式加载MySQL数据库驱动,然后通过mysql驱动连接数据库:
Class.forName(""com.mysql.jdbc.Driver"),
Connection conn = DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);
Java SE之后,数据库驱动加载,基于SPI方式:
Connection conn = DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);
Java se之前和Jave se之后区别(对于程序员来说):
以前程序员(SPI的第三个工程)需要指定要加载那个数据库驱动,是mysql,oracel还是sqlserver的,现在程序员(SPI的第三个工程)只要在maven中引入数据库驱动jar包即可,其他的通过SPI内部通过接口和实现类之间的某种搞定,SPI能智能识别到底要调用那个驱动连接数据库,原理很简单,在这个方法中 getConnection(sqlUrl,sqlUserName,sqlPassword);
。
java.sql.getConnection(String url, java.util.Properties info, Class<?> caller) 方法源码解析
for(DriverInfo aDriver : registeredDrivers)
if(isDriverAllowed(aDriver.driver, callerCL)) **// isDriverAllowed(aDriver.driver, callerCL) 方法遍历所有已在maven中引入的数据库驱动**
try
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info); **// aDriver.driver.connect(url, info); 方法会调用当期的驱动尝试连接数据库,如果能通过此驱动连接数据库成功,就返回,否则继续尝试(各个数据库库驱动如果无法连接数据库, aDriver.driver.connect(url, info); 此方法会返回null)**
if (con != null)
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con); **// 如果能通过此驱动连接数据库成功,就返回**
catch (SQLException ex)
if (reason == null) reason = ex;
else **// 如果无法通过此驱动连接数据库成功,就继续尝试**
println(" skipping: " + aDriver.getClass().getName());
至此通过SPI 优雅的加载各大厂商的驱动就实现了。
3.5 JDBC通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类
JDBC通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类。通过Java SPI 技术加载数据库驱动的时候,他是直接获取线程上下文类加载器加载数据库驱动(直接通过ApplicationClassloader 来加载驱动的)。
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
load源码
public static ServiceLoader load(Class service)
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
这里,BootStrapClassload偷偷滴指派Appclassloader,说帮我加载一下驱动类吧,本来只能由Appclassloader委托BootStrapClassload来加载对应的类,但是,这里由BootStrapClassload委派了 Appclassloader,所以说,JDBC数据库加载破坏了双亲委托模式。
解释1:父ClassLoader可以通过使用Thread.current.currentThread().getContextClassLoader()语句,得到当前线程所指定的classLoader加载的类,然后就可以使用这个当前线程的类加载器了。这就改变了父ClassLoader不能使用子ClassLoader加载的类的情况,即改变了双亲委托模型,破坏了双亲委派模式。
解释2(核心:双亲委派模式无法满足实际要求):在双亲委托模型下,只能是下层的类加载器会委托上层进行加载(不能上层加载器委派下层加载器),所以只能先进行上层类加载器,然后是下层的类加载器。但是对于SPI来说,有些接口是JAVA核心库提供的,而JAVA核心库是由启动类加载器来加载的,而这些接口的实现却来自于不同的jar包(厂商提供),JAVA的启动类加载器是不会加载第三方的jar包,那这些第三方jar包怎么办,谁来加载,所以,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以设置的上下文类加载器来实现对于接口实现类的加载,如jdbc-mysql驱动。
根据双亲委派模式(即不破坏双亲委派模式的情况下),JDBC的核心在rt.jar中由启动类加载器加载,而其实现则在各厂商实现的的jar包中,由应用程序类加载器加载。
根据双亲委派模式,即JVM默认的类加载机制(双亲委派模式就是JVM默认的类加载方式,先加载父类,在加载子类,一步步来),若DriverManager类调用Driver实现类,则Driver实现类由DriverManager类的加载器加载,也就是说启动类加载器要加载jar包下的类,我们都知道这是不可能的,启动类加载器负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class。
金手指:关于getContextClassLoader()
首先,线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader c1),分别用来获取和设置类加载器。
其次,初始线程的上下文类加载器是系统类加载器。如果没有通过setContextClassLoader方法进行设置的话,线程将继承其父线程的上下文加载器,java应用运行时的初始线程的上下文类加载器是系统类加载器(这里是由Launcher类设置的)。在线程中运行的代码可以通过该类加载器来加载类和资源。
解释:每个线程都有自己的线程上下文类加载器,并且每个线程的上下文类记载器都是父线程的类加载器,那么我只要考虑第一个线程类加载器就可以啦,因为他是所有的线程的父类,那么对于普通Java程序,谁是第一个线程,这个大家肯定知道 Java 的 Main 方法哈哈,那么main 方法的上下文来加载器是谁尼?? 我们去看一下,main方法的入口类 Launcher 类,如下:
public Launcher() // 源码解析Launcher类的构造函数,这个类在rt.jar包中
Launcher.ExtClassLoader var1;
try
var1 = Launcher.ExtClassLoader.getExtClassLoader(); // var1
catch (IOException var10)
throw new InternalError("Could not create extension class loader", var10);
try
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); // 从var1得到AppClassLoader,记录到this.loader
catch (IOException var9)
throw new InternalError("Could not create application class loader", var9);
// 将这个this.loader设置为当前线程(即main线程)的上下文线程加载器,所以,第一个上下文线程类加载器的是AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
// 读取属性,记录到var2变量中
String var2 = System.getProperty("java.security.manager");
if (var2 != null) // 当var2不为null
SecurityManager var3 = null; // 声明局部变量var3
if (!"".equals(var2) && !"default".equals(var2)) // 当var2不为空字符串或“default”字符串
try
// 调用loadClass(var2)得到字节码对象,即加载var2得到字节码对象,
// 然后新建实例,记录到SecurityManager类型的var3变量中
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
catch (IllegalAccessException var5) //连续四个catch
;
catch (InstantiationException var6)
;
catch (ClassNotFoundException var7)
;
catch (ClassCastException var8)
;
else
// 如果var2为空字符串或者为“default”字符串,表示配置文件为空,
// 就new一个SecurityManager,记录到var3中;上面配置文件不为空,
// 就是读取配置文件然后得到字节码再新建实例
var3 = new SecurityManager();
if (var3 == null) // 如果var3为null,就是进入到了上面连续四个catch throw结束函数
throw new InternalError("Could not create SecurityManager: " + var2);
// 如果var3不为null,将var3设置为系统的SecurityManager
System.setSecurityManager(var3);
四、Tomcat的违背双亲委派模式
4.1 Tomcat的四个业务需求
Tomcat作为一个web容器, 它要解决四个问题,如下:
(1) 应用程序之间的隔离性:一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因为在设计上要保证每个应用程序的类库都是独立的,保证相互隔离。
(2) 应用程序间共享相同版本的类库:部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,这是荒唐的。
(3) 容器类库与应用程序类库相互隔离:web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
(4) 支持JSP文件热部署:web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, 所以,web容器需要支持 jsp 修改后不用重启。
4.2 Tomcat 如果使用默认的类加载机制行不行
问题:Tomcat 如果使用默认的类加载机制行不行?
回答:不行,无法满足tomcat的四个业务需求。
第一个问题,无法保证隔离性:如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的累加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
第三个问题和第一个问题一样,无法保证隔离性。
第四个问题,要实现jsp文件的热修改,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
4.3 Tomcat 如何实现自己独特的类加载机制来满足四个需求
问题:Tomcat 如何实现自己独特的类加载机制来满足四个需求的?
Tomcat类加载器结构图:
我们看到,前面3个类加载和默认的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/、/server/、/shared/*(在tomcat 6之后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中,WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。
我们来解释一下后面四个Loader
commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身可见,对所有Webapp可见;
catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Tomcat容器本身可见,对于所有Webapp都不可见;
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp都可见,但是对于Tomcat容器不可见;
WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;对于其他WebApp和Tomcat容器本身不可见。
所以,从图中的委派关系中可以看出:
前三个:CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离,两个刚好是对立集合。
第四个:WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。
第五个:JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。
4.4 Tomcat是否违背了双亲委派模型
好了,至此,我们已经知道了tomcat为什么要这么设计,以及是如何设计的,那么,tomcat 是否违背了java 推荐的双亲委派模型?
答案是:违背了。
我们前面说过:双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
我们扩展出一个问题:如果tomcat 的 Common ClassLoader 想加载 WebApp ClassLoader 中的类,该怎么办?
回答:我们可以使用线程上下文类加载器实现,使用线程上下文加载器getContextClassLoader,可以让父类加载器请求子类加载器去完成类加载的动作。
问题:为什么说Tomcat的类加载机制违反了双亲委托原则?
回答:每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,所以破坏双亲委派。对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。因此,按照这个过程可以想到,如果同样在CLASSPATH指定的目录中和自己工作目录中存放相同的class,会优先加载CLASSPATH目录中的文件。
4.5 Tomcat不遵循双亲委派机制,如果我自己定义一个恶意的HashMap,会不会有风险
问题:既然 Tomcat 不遵循双亲委派机制,那么如果我自己定义一个恶意的HashMap,会不会有风险?
回答: 显然不会有风险, tomcat不遵循双亲委派机制,只是自定义的classLoader顺序不同,但顶层还是相同的,还是要去顶层请求classloader。
4.6 类加载 + JVM类加载 + Tomcat类加载 + 一个宏观问题
4.6.1 类加载
在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加载。
比如JVM启动时,会通过不同的类加载器加载不同的类。当用户在自己的代码中,需要某些额外的类时,再通过加载机制加载到JVM中,并且存放一段时间,便于频繁使用。
因此使用哪种类加载器、在什么位置加载类都是JVM中重要的知识。
4.6.2 JVM类加载
JVM类加载采用 父类委托机制,如下图所示:
JVM中包括集中类加载器:BootStrapClassLoader 引导类加载器、ExtClassLoader 扩展类加载器、
AppClassLoader 应用类加载器、CustomClassLoader 用户自定义类加载器,他们的区别上面也都有说明。需要注意的是,不同的类加载器加载的类是不同的,因此如果用户加载器1加载的某个类,其他用户并不能够使用。
当JVM运行过程中,用户需要加载某些类时,会按照下面的步骤(父类委托机制):
(1)用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器树的顶层。
(2)最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类。
(3)如果一直到底层的类加载都没有加载到,那么就会抛出异常ClassNotFoundException。因此,按照这个过程可以想到,如果同样在CLASSPATH指定的目录中和自己工作目录中存放相同的class,会优先加载CLASSPATH目录中的文件。
4.6.3 Tomcat类加载
在Tomcat中类的加载稍有不同,如下图:
当tomcat启动时,会创建几种类加载器:
(1) Bootstrap 引导类加载器:加载JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下);
(2) System 系统类加载器:加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定,位于CATALINA_HOME/bin下;
(3) Common 通用类加载器:加载tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下,比如servlet-api.jar;
(4) webapp 应用类加载器:每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。
当应用需要到某个类时,则会按照下面的顺序进行类加载:
(1) 使用bootstrap引导类加载器加载;
(2) 使用system系统类加载器加载;
(3) 使用应用类加载器在WEB-INF/classes中加载;
(4) 使用应用类加载器在WEB-INF/lib中加载;
(5) 使用common类加载器在CATALINA_HOME/lib中加载。
4.6.4 为什么java文件放在Eclipse中的src文件夹下会优先jar包中的class
通过对上面tomcat类加载机制的理解,就不难明白 为什么java文件放在Eclipse中的src文件夹下会优先jar包中的class?
这是因为Eclipse中的src文件夹中的文件java以及webContent中的JSP都会在tomcat启动时,被编译成class文件放在 WEB-INF/class 中。
而Eclipse外部引用的jar包,则相当于放在 WEB-INF/lib 中。因此肯定是 java文件或者JSP文件编译出的class优先加载。
通过这样,我们就可以简单的把java文件放置在src文件夹中,通过对该java文件的修改以及调试,便于学习拥有源码java文件、却没有打包成xxx-source的jar包。
另外呢,开发者也会因为粗心而犯下面的错误。在 CATALINA_HOME/lib 以及 WEB-INF/lib 中放置了 不同版本的jar包,此时就会导致某些情况下报加载不到类的错误。
还有如果多个应用使用同一jar包文件,当放置了多份,就可能导致多个应用间出现类加载不到的错误。
五、尾声
双亲委派模式伤心史,完成了。
天天打码,天天进步!!!
以上是关于呜,双亲委派模式伤心史的主要内容,如果未能解决你的问题,请参考以下文章