呜,双亲委派模式伤心史

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包文件,当放置了多份,就可能导致多个应用间出现类加载不到的错误。

五、尾声

双亲委派模式伤心史,完成了。

天天打码,天天进步!!!

以上是关于呜,双亲委派模式伤心史的主要内容,如果未能解决你的问题,请参考以下文章

JDBC是如何打破双亲委派模式的

jvm 双亲委派模式

什么是双亲委派机制

JVM 专题四:类加载子系统双亲委派机制

四“双亲委派“与”沙盒安全“机制

slf4j 日志门面模式应用(双亲委派特例)