打破双亲委派机制有什么用_类加载器如何打破双亲委派加载机制(SPI原理)

1.类加载器命名空间可见性

子类加载器可以见到父类加载器加载的类,而父类加载器看不见子类加载器加载的类

2.打破双亲委派加载机制

1.双亲委派模型的第一次“被破坏”是重写自定义加载器的loadClass(),jdk不推荐。一般都只是重写findClass(),这样可以保持双亲委派机制.而loadClass方法加载规则由自己定义,就可以随心所欲的加载类了

2.双亲委派模型的第二次“被破坏”是ServiceLoader和Thread.setContextClassLoader()

双亲委派模型的这个模型存在一些缺陷,双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被调用代码调用的API。但是,如果基础类又要调用用户的代码,那该怎么办呢。

这并非是不可能的事情,一个典型的例子便是JNDI服务,它的代码由启动类加载器去加载(在JDK1.3时放进rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识”之些代码,该怎么办?

为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等。

以JDBC为例:

//传统加载方式 1

Class.forName("com.mysql.jdbc.Driver");

Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:33061/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull", "root", "123456");

//传统加载方式 2

System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");

Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:33061/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull", "root", "123456");

//SPI加载方式

Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:33061/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull", "root", "123456");

**SPI加载机制中,不需要手动设置驱动为com.mysql.jdbc.Driver。DriverManager有一个静态代码块,这会在执行getConnection前运行,即loadInitialDrivers(),此方法会调用ServiceLoader.load(Class service, ClassLoader loader)寻找ClassPath:META-INF/services文件夹下面java.sql.Driver的内容,即实现类。load(

由于ServiceLoader位于rt.jar包下面(并没有继承ClassLoader)是启动类加载器加载的,所以是无法看见厂商的实现类jdbc.Driver,由父类加载器加载的类是看不见子类加载器加载的类的。所以此时采用自己传入的loader结合Thread.setContextLoader(),将jdbc.Driver偷偷加载到内存,并通过以下详细基本原理,进而实现SPI机制**

3.双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换、模块热部署等,简答的说就是机器不用重启,只要部署上就能用。

OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi幻境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当受到类加载请求时,OSGi将按照下面的顺序进行类搜索:

1)将java.*开头的类委派给父类加载器加载。

2)否则,将委派列表名单内的类委派给父类加载器加载。

3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。

4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。

5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。

6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。

7)否则,类加载器失败。

3.基本原理

当前项目中定义好接口,实现类不在当前类路径下。实现类实现当前项目提供的接口。在当前项目中调用自定义classLoder.load().根据双亲委托机制,会先尝试使用父类加载器加载,加载不成功则使用子类加载器。子类加载器加载当前Student1类,需要用到Student接口,而Student接口是使用父类加载器加载的(在类路径下面),由于父类加载器加载的类对于子类可见,则不会报错).拿到反射实例的class后调用反射(此处不能直接new ,直接new或者直接使用Student1都会造成主动使用,从而造成appClassLoder来加载这个类,由于AppclassLoder无法加载这个类,父类加载器无法访问子类加载器加载的类,此时就会报错)。根据预先定义好的接口Student,就可以使用这个具体实现类的某些方法了

/**

@author: logan

@Date: 2019/10/30 13:15

@Description:

*/

public class Test01 {

public static void main(String[] args)

throws ClassNotFoundException, IllegalAccessException, InstantiationException {

//这个类class的路径

String classPath = "C:\\Users\\Administrator\\Desktop\\test\\com\\student\\";

MyClassLoader myClassLoader = new MyClassLoader(classPath, "my");

String packageNamePath = "Student1";

Class> a = myClassLoader.loadClass(packageNamePath);

System.out.println("类加载器是:" + a.getClassLoader());

Student student = (Student) a.newInstance();

student.doSth();

}

}

应用场景

SPI原理:如JDBC、JNDI等

tomaCat:....

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值