热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析Spring框架中的双亲委派机制突破方法

在探讨Spring框架中突破双亲委派机制的方法之前,首先需要了解类加载器的基本概念。类加载器负责将类的全限定名转换为对应的二进制字节流。每个类在被特定的类加载器加载后,其唯一性得到保证。然而,这种机制在某些场景下可能会限制灵活性,因此Spring框架提供了一些策略来突破这一限制,以实现更加动态和灵活的类加载。这些策略不仅能够提升系统的可扩展性,还能在复杂的运行环境中确保类的正确加载和管理。


类加载器简介在介绍正经的月球委托模型之前,先介绍一下类加载器。类加载器将类的完全限定名转换为描述该类的二进制字节流。


对于任何类,被同一个类加载器加载后都是唯一的,但是被不同的加载器加载后就不是唯一的了。即使它们来自同一个类文件,由同一个JVM加载,只要加载这些类的加载器不同,这些类也会不同。


如何判断类是否相同,可以通过class对象的equals()方法、isAssignableFrom()方法和isInstance()方法的返回结果,或者通过instanceof关键字来判断。让我们编写一个由不同类加载器加载的类,看看它对instanceof的关键字操作有什么影响:


publicclassOneMoreStudy{


public static void main(String[]args)throwsException {


class loadermyloader=new class loader(){ 0


@覆盖


publicClass?load class(Stringname)throws classnotfoundexception {


尝试{


StringfileName=name . substring(name . LastIndexof(' . ')) 1) '.类';


InputStreaminputStream=getClass()。getResourceAsStream(文件名);


if(InputStream==null){ 0


returnsuper.loadClass(名称);


}


byte[]数组=new byte[InputStream . available()];


inputStream.read(数组);


returndefineClass(名称,数组,0,array . length);


} catch(IOexceptione){ 0


thrownewClassNotFoundException(名称);


}


}


};


object object=MyLoader . LoadClass(' OneMoreStudy ')。new INSTANCE();


system . out . println(' class name : ' object . GetClass()。getName());


system . out . println(' instance of : '(objectinstance of nemerestudy));


}


}运行结果:


类名: one more研究


Instanceof:false在运行结果中,第一行显示这个对象确实是由onemorestore类实例化的,但是在第二行,操作实例的结果为false,这表明JVM中有两个onemorestore类,一个是由系统应用程序类加载器加载的,另一个是由我们自定义的类加载器加载的。虽然它们都来自同一个类文件,并且在同一个JVM中,但是在被不同的类加载器加载之后,它们仍然是两个独立的类。


类加载器的划分除了我们在上面的示例代码中实现的自定义类加载器之外,还有三个系统提供的类加载器:


Bootstrap ClassLoader:它负责将存储在%JAVA_HOME%\lib目录中或Bootstrap ClassLoader参数指定的路径中并被JVM识别的类库加载到JVM内存中。它只通过文件名来标识,例如,名称不匹配的rt.jar类库即使放在lib目录下也不会被加载。它是用C语言实现的,不能被Java程序直接引用。扩展类加载器:它负责加载目录%JAVA_HOME%\lib\ext或由java.ext.dirs系统变量指定的路径中的所有类库。它由sun . misc . launcher . ext classloader实现,开发人员可以直接使用扩展的class loader。应用程序类加载器:它负责加载用户类路径上指定的类库。因为它是类加载器中getsystemClassLoader()方法的返回值,所以也叫系统类加载器。它由sun.misc.Launcher.Ap组成

pClassLoader来实现,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

认真的月亮委托模型

之前提到,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在JVM中的唯一性。可是有这么多种的类加载器,如何保证一个类在JVM中的唯一性呢?为了解决这个问题,认真的月亮委托模型(Parents Delegation Model)应运而生,它就是下图所展示的类加载器之间的层次关系:

除了顶层的启动类加载器外,其余的类加载器都必须有自己的父类加载器。类加载器之间的父子关系,一般不会以继承的关系来实现,而是都使用组合关系来复用父类加载器。

类加载器收到类加载的请求后,它不会首先自己去尝试加载这个类,而是把这个请求委派给父类加载器去尝试加载。每一个类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这样就保证了类在JVM中的唯一性,也保证了Java程序稳定运作。

实现认真的月亮委派模型的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如下:

protected Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先,检查该类是否已经被加载过了
        Class c = findLoadedClass(name);
        //如果没有加载过,就调用父类加载器的loadClass()方法
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    //如果父类加载器为空,就使用启动类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //如果在父类加载器中找不到该类,就会抛出ClassNotFoundException
            }
            if (c == null) {
                //如果父类找不到,就调用findClass()来找到该类。
                long t1 = System.nanoTime();
                c = findClass(name);
                //记录统计数据
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

破坏认真的月亮委派模型

认真的月亮委派模型并不是一个强制性的约束模型,而是Java设计者们推荐给开发者们的类加载器实现方式。大部分的类加载器都遵循这个模型,但也有例外的情况,比如下面这三种情况:

重写ClassLoader的loadClass()方法

在上面例子代码中,就是重写了ClassLoader的loadClass()方法,破坏了认真的月亮委派模型,产生了不唯一的类。所以,不提倡开发人员覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里如果父类加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合认真的月亮委派模型。

SPI(服务提供者接口)

Java提供了很多SPI(Service Provider Interface,服务提供者接口),允许第三方为这些接口提供实现,常见的SPI有JDBC、JNDI、JCE、JAXB和JBI等。

SPI的接口由Java核心库来提供,而这些SPI的实现代码则是作为Java应用所依赖的jar包被包含进类路径(ClassPath)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;SPI的实现类是由系统类加载器来加载的。引导类加载器是无法找到SPI的实现类的,因为依照认真的月亮委派模型,启动类加载器无法委派系统类加载器来加载类。

这时候就会使用线程上下文类加载器(Thread Context ClassLoader),在JVM中会把当前线程的类加载器加载不到的类交给线程上下文类加载器来加载,直接使用Thread.currentThread().getContextClassLoader()来获得,默认返回的就是应用程序类加载器,也可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。

而线程上下文类加载器破坏了认真的月亮委派模型,也就是父类加载器请求子类加载器去完成类加载的动作,但为了实现功能,这也是一种巧妙的实现方式。

OSGi(开放服务网关协议)

OSGi(Open Service Gateway Initiative,开放服务网关协议)技术是面向Java动态化模块化系统模型,程序模块(称为Bundle)无需重新引导可以被远程安装、启动、升级和卸载。实现程序模块热部署的关键则是它自定义的类加载器机制的实现。

在OSGi中,类加载器不再是认真的月亮委派模型中的树状结构,而是一个较为复杂的网状结构,类加载的规则简要介绍如下:

若类属于java.*包,则将加载请求委托给父加载器若类定义在启动委托列表(org.osgi.framework.bootdelegation)中,则将加载请求委托给父加载器若类属于在Import-Package中定义的包,则框架通过ClassLoader依赖关系图找到导出此包的Bundle的ClassLoader,并将加载请求委托给此ClassLoader若类资源属于在Require-Bundle中定义的Bundle,则框架通过ClassLoader依赖关系图找到此Bundle的ClassLoader,将加载请求委托给此ClassLoaderBundle搜索自己的类资源( 包括Bundle-Classpath里面定义的类路径和属于Bundle的Fragment的类资源)若类在DynamicImport-Package中定义,则开始尝试在运行环境中寻找符合条件的Bundle

如果在经过上面一系列步骤后,仍然没有正确地加载到类资源,则会向外抛出类未发现异常。

总结

类加载器通过一个类的全限定名来转换为描述这个类的二进制字节流,可划分为启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器。在认真的月亮委托模型中,将上述各种类加载器组成一系列的父子关系,子类加载器先把类加载请求委派给父类加载器去尝试加载,父类加载器无法加载时子类加载器才自己尝试加载,这样保证了类在JVM中的唯一性。不过,也不遵循认真的月亮委托模型的情况,比如:重写ClassLoader的loadClass()方法、SPI(服务提供者接口)、OSGi(开放服务网关协议)。


推荐阅读
  • 在Java开发中,保护代码安全是一个重要的课题。由于Java字节码容易被反编译,因此使用代码混淆工具如ProGuard变得尤为重要。本文将详细介绍如何使用ProGuard进行代码混淆,以及其基本原理和常见问题。 ... [详细]
  • 本文详细介绍了如何利用 Bootstrap Table 实现数据展示与操作,包括数据加载、表格配置及前后端交互等关键步骤。 ... [详细]
  • 本打算教一步步实现koa-router,因为要解释的太多了,所以先简化成mini版本,从实现部分功能到阅读源码,希望能让你好理解一些。希望你之前有读过koa源码,没有的话,给你链接 ... [详细]
  • 本文探讨了如何将个人经历,特别是非传统的职业路径,转化为职业生涯中的优势。通过作者的亲身经历,展示了舞蹈生涯对商业思维的影响。 ... [详细]
  • 本文详细介绍了如何在ARM架构的目标设备上部署SSH服务端,包括必要的软件包下载、交叉编译过程以及最终的服务配置与测试。适合嵌入式开发人员和系统集成工程师参考。 ... [详细]
  • Logging all MySQL queries into the Slow Log
    MySQLoptionallylogsslowqueriesintotheSlowQueryLog–orjustSlowLog,asfriendscallit.However,Thereareseveralreasonstologallqueries.Thislistisnotexhaustive:Belowyoucanfindthevariablestochange,astheyshouldbewritteninth ... [详细]
  • 在尝试启动Java应用服务器Tomcat时,遇到了org.apache.catalina.LifecycleException异常。本文详细记录了异常的具体表现形式,并提供了有效的解决方案。 ... [详细]
  • 本文详细介绍了在 CentOS 7 系统中安装 Python 3.7 的步骤,包括编译工具的安装、Python 3.7 源码的下载与编译、软链接的创建以及常见错误的处理方法。 ... [详细]
  • mybatis 详解(七)一对一、一对多、多对多
    mybatis详解(七)------一 ... [详细]
  • iOS 百度地图使用指南:基本定位与地理编码
    本文详细介绍如何在 iOS 应用中集成百度地图,实现基本的地图定位和地理编码功能。配置详情请参考官方文档:http://developer.baidu.com/map/index.php?title=iossdk ... [详细]
  • Java 中的控制流与作用域
    本文详细介绍了 Java 中的控制流语句,包括块作用域、if 语句、for 循环、while 循环、do-while 循环、switch 语句以及 break 和 continue 语句的使用方法。通过具体的代码示例,帮助读者更好地理解和应用这些控制流结构。 ... [详细]
  • Bootstrap 插件使用指南
    本文详细介绍了如何在 Web 前端开发中使用 Bootstrap 插件,包括自动触发插件的方法、插件的引用方式以及具体的实例。 ... [详细]
  • 本文整理了一份基础的嵌入式Linux工程师笔试题,涵盖填空题、编程题和简答题,旨在帮助考生更好地准备考试。 ... [详细]
  • 本文介绍了如何在 Spring Boot 项目中使用 spring-boot-starter-quartz 组件实现定时任务,并将 cron 表达式存储在数据库中,以便动态调整任务执行频率。 ... [详细]
  • Leetcode学习成长记:天池leetcode基础训练营Task01数组
    前言这是本人第一次参加由Datawhale举办的组队学习活动,这个活动每月一次,之前也一直关注,但未亲身参与过,这次看到活动 ... [详细]
author-avatar
手机用户美佛_885
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有