站长工具ping,中国建设手机银行下载,廊坊市安次区建设局网站,成都网站设计学校双亲委派模型的第一次“被破坏”是重写自定义加载器的loadClass(),jdk不推荐。一般都只是重写findClass()#xff0c;这样可以保持双亲委派机制.而loadClass方法加载规则由自己定义#xff0c;就可以随心所欲的加载类#xff0c;典型的打破双亲委派模型的框架和中间件有tomc… 双亲委派模型的第一次“被破坏”是重写自定义加载器的loadClass(),jdk不推荐。一般都只是重写findClass()这样可以保持双亲委派机制.而loadClass方法加载规则由自己定义就可以随心所欲的加载类典型的打破双亲委派模型的框架和中间件有tomcat与osgi 双亲委派模型的第二次“被破坏”是ServiceLoader和Thread.setContextClassLoader()。即线程上下文类加载器contextClassLoader。双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载)基础类之所以被称为“基础”是因为它们总是作为被调用代码调用的API。但是如果基础类又要调用用户的代码那该怎么办呢线程上下文类加载器就出现了。 SPI。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置如果创建线程时还未设置它将会从父线程中继承一个如果在应用程序的全局范围内都没有设置过那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码也就是父类加载器请求子类加载器去完成类加载动作这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器已经违背了双亲委派模型但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式例如JNDI,JDBC,JCE,JAXB和JBI等。 线程上下文类加载器默认情况下就是AppClassLoader那为什么不直接通过getSystemClassLoader()获取类加载器来加载classpath路径下的类的呢其实是可行的但这种直接使用getSystemClassLoader()方法获取AppClassLoader加载类有一个缺点那就是代码部署到不同服务时会出现问题如把代码部署到Java Web应用服务或者EJB之类的服务将会出问题因为这些服务使用的线程上下文类加载器并非AppClassLoader而是Java Web应用服自家的类加载器类加载器不同。所以我们应用该少用getSystemClassLoader()。总之不同的服务使用的可能默认ClassLoader是不同的但使用线程上下文类加载器总能获取到与当前程序执行相同的ClassLoader从而避免不必要的问题 双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的这里所说的“动态性”指的是当前一些非常“热门”的名词代码热替换、模块热部署等简答的说就是机器不用重启只要部署上就能用。
前言
比较两个类是否“相等”前提是这两个类由同一个类加载器加载
否则即使这两个类来源于同一个Class 文件被同一个虚拟机加载
只要加载它们的类加载器不同那么这两个类就必定不相等。
打破双亲委派 如下是一个自定义的类加载器TestClassLoader并重写了findClass和loadClass
public class TestClassLoader extends ClassLoader {public TestClassLoader(ClassLoader parent) {super(parent);}Overrideprotected Class? findClass(String name) throws ClassNotFoundException {// 1、获取class文件二进制字节数组byte[] data null;try {System.out.println(name);String namePath name.replaceAll(\\., \\\\);String classFile C:\\study\\myStudy\\ZooKeeperLearning\\zkops\\target\\classes\\ namePath .class;ByteArrayOutputStream baos new ByteArrayOutputStream();FileInputStream fis new FileInputStream(new File(classFile));byte[] bytes new byte[1024];int len 0;while ((len fis.read(bytes)) ! -1) {baos.write(bytes, 0, len);}data baos.toByteArray();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}// 2、字节码加载到 JVM 的方法区// 并在 JVM 的堆区建立一个java.lang.Class对象的实例// 用来封装 Java 类相关的数据和方法return this.defineClass(name, data, 0, data.length);}Overridepublic Class? loadClass(String name) throws ClassNotFoundException{Class? clazz null;// 直接自己加载clazz this.findClass(name);if (clazz ! null) {return clazz;}// 自己加载不了再调用父类loadClass保持双亲委托模式return super.loadClass(name);}
}测试初始化自定义的类加载器需要传入一个parent指定其父类加载器那就先指定为加载TestClassLoader的类加载器为TestClassLoader的父类加载器吧
public static void main(String[] args) throws Exception {// 初始化TestClassLoader被将加载TestClassLoader类的类加载器设置为TestClassLoader的parentTestClassLoader testClassLoader new TestClassLoader(TestClassLoader.class.getClassLoader());System.out.println(TestClassLoader的父类加载器 testClassLoader.getParent());// 加载 DemoClass clazz testClassLoader.loadClass(study.stefan.classLoader.Demo);System.out.println(Demo的类加载器 clazz.getClassLoader());
}运行如下测试代码发现报错了 找不到java\lang\Object.class我加载study.stefan.classLoader.Demo类和Object有什么关系呢
转瞬想到java中所有的类都隐含继承了超类Object加载study.stefan.classLoader.Demo也会加载父类Object。Object和study.stefan.classLoader.Demo并不在同个目录那就找到Object.class的目录将jre/lib/rt.jar解压修改TestClassLoader#findClass如下 遇到前缀为java.的就去找官方的class文件。
运行测试代码 还是报错了 报错信息为Prohibited package name: java.lang 看意思是java禁止用户用自定义的类加载器加载java.开头的官方类也就是说只有启动类加载器BootstrapClassLoader才能加载java.开头的官方类。
得出结论因为java中所有类都继承了Object而加载自定义类study.stefan.classLoader.Demo之后还会加载其父类而最顶级的父类Object是java官方的类只能由BootstrapClassLoader加载
跳过AppClassLoader和ExtClassLoader 既然如此先将study.stefan.classLoader.Demo交由BootstrapClassLoader加载即可。 由于java中无法直接引用BootstrapClassLoader所以在初始化TestClassLoader时传入parent为null也就是TestClassLoader的父类加载器设置为BootstrapClassLoader
package com.stefan.DailyTest.classLoader;public class Test {public static void main(String[] args) throws Exception {// 初始化TestClassLoader并将加载TestClassLoader类的类加载器// 设置为TestClassLoader的parentTestClassLoader testClassLoader new TestClassLoader(null);System.out.println(TestClassLoader的父类加载器 testClassLoader.getParent());// 加载 DemoClass clazz testClassLoader.loadClass(com.stefan.DailyTest.classLoader.Demo);System.out.println(Demo的类加载器 clazz.getClassLoader());}
}双亲委派的逻辑在 loadClass由于现在的类加载器的关系为TestClassLoader —BootstrapClassLoader所以TestClassLoader中无需重写loadClass。 运行测试代码 成功了Demo类由自定义的类加载器TestClassLoader加载的双亲委派模型被破坏了。
如果不破坏双亲委派那么Demo类处于classpath下就应该是AppClassLoader加载的所以真正破坏的是AppClassLoader这一层的双亲委派 一个比较完整的自定义类加载器
一般情况下自定义类加载器都是继承URLClassLoader具有如下类关系图 tomcat是如何打破双亲委派的 Tomcat中可以部署多个web项目为了保证每个web项目互相独立所以不能都由AppClassLoader加载所以自定义了类加载器WebappClassLoaderWebappClassLoader继承自URLClassLoader重写了findClass和loadClass并且WebappClassLoader的父类加载器设置为AppClassLoader。 WebappClassLoader.loadClass中会先在缓存中查看类是否加载过没有加载就交给ExtClassLoaderExtClassLoader再交给BootstrapClassLoader加载都加载不了才自己加载自己也加载不了就遵循原始的双亲委派交由AppClassLoader递归加载。
Web应用默认的类加载顺序是打破了双亲委派规则
先从JVM的BootStrapClassLoader中加载。 加载Web应用下/WEB-INF/classes中的类。 加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。 加载上面定义的System路径下面的类。 加载上面定义的Common路径下面的类。 如果在配置文件中配置了那么就是遵循双亲委派规则加载顺序如下
先从JVM的BootStrapClassLoader中加载。 加载上面定义的System路径下面的类。 加载上面定义的Common路径下面的类。 加载Web应用下/WEB-INF/classes中的类。 加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。
1 Tomcat对用户类库与类加载器的规划 在其目录结构下有三组目录“/common/”、“/server/”、“/shared/”可以存放Java类库,另外还可以加上Web应用程序本身的目录“/WEB-INF/”一共4组把Java类库放置在这些目录中的含义分别如下
放置在/commom目录中类库可被Tomcat和所有的Web应用程序共同使用 放置在/server目录中类库可被Tomcat使用对所有的Web应用程序都不可见 放置在/shared目录中类库可被所有的Web应用程序所共同使用但对Tomcat自己不可见 放置在/WebApp/WEB-INF目录中类库仅仅可以被此Web应用程序使用对Tomcat和其他Web应用程序都不可见 为了支持这套目录结构并对目录里面的类库进行加载和隔离Tomcat自定义了多个类加载器这些类加载器按照经典的双亲委派模型来实现所下图 最上面的三个类加载器是JDK默认提供的类加载器这三个加载器的的作用之前也说过这里不再赘述了而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebAppClassLoader则是Tomcat自己定义的类加载器他们分别加载/common/、/server/、/shared/和/WebApp/WEB-INF/中的Java类库。其中WebApp类加载器和jsp类加载器通常会存在多个实例每一个Web应用程序对应一个WebApp类加载器每一个jsp文件对应一个Jsp类加载器
从上图的委派关系可以看出CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类但各个WebAppClassLoader实例之间相互隔离。而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的哪一个Class它出现的目的就是为了被丢弃当服务器检测到JSP文件被修改时会替换掉目前的JasperLoader的实例并通过在建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能
tomcat对于不同应用需要有不同的隔离环境。 tomcat给每个应用都创建了一个WebApp ClassLoader类加载器 重写了load方法不再向上查找而是在本类查找不到后再向上。对于其他的需要共享的例如Redis可以在上层Share ClassLoader中共享。
OSGI是如何打破双亲委派的 既然说到OSGI就要来解释一下OSGi是什么以及它的作用
OSGiOpen Service Gateway Initiative是OSGi联盟指定的一个基于Java语言的动态模块化规范这个规范最初是由Sun、IBM、爱立信等公司联合发起目的是使服务提供商通过住宅网管为各种家用智能设备提供各种服务后来这个规范在Java的其他技术领域也有不错的发展现在已经成为Java世界中的“事实上”的模块化标准并且已经有了Equinox、Felix等成熟的实现。OSGi在Java程序员中最著名的应用案例就是Eclipse IDE
OSGi中的每一个模块称为Bundle与普通的Java类库区别并不大两者一般都以JAR格式进行封装并且内部存储的都是Java Package和Class。但是一个Bundle可以声明它所依赖的Java Package通过Import-Package描述也可以声明他允许导出发布的Java Package通过Export-Package描述。在OSGi里面Bundle之间的依赖关系从传统的上层模块依赖底层模块转变为平级模块之间的依赖至少外观上如此而且类库的可见性能得到精确的控制一个模块里只有被Export过的Package才可能由外界访问其他的Package和Class将会隐藏起来。除了更精确的模块划分和可见性控制外引入OSGi的另外一个重要理由是基于OSGi的程序很可能可以实现模块级的热插拔功能当程序升级更新或调试除错时可以只停用、重新安装然后启动程序的其中一部分这对企业级程序开发来说是一个非常有诱惑性的特性
OSGi之所以能有上述“诱人”的特点要归功于它灵活的类加载器架构。OSGi的Bundle类加载器之间只有规则没有固定的委派关系。例如某个Bundle声明了一个它依赖的Package如果有其他的Bundle声明发布了这个Package那么所有对这个Package的类加载动作都会为派给发布他的Bundle类加载器去完成。不涉及某个具体的Package时各个Bundle加载器是平级关系只有具体使用某个Package和Class的时候才会根据Package导入导出定义来构造Bundle间的委派和依赖
另外一个Bundle类加载器为其他Bundle提供服务时会根据Export-Package列表严格控制访问范围。如果一个类存在于Bundle的类库中但是没有被Export那么这个Bundle的类加载器能找到这个类但不会提供给其他Bundle使用而且OSGi平台也不会把其他Bundle的类加载请求分配给这个Bundle来处理
一个例子假设存在BundleA、BundleB、BundleC三个模块并且这三个Bundle定义的依赖关系如下
BundleA声明发布了packageA依赖了java.*的包 BundleB声明依赖了packageA和packageC同时也依赖了Java.*的包 BundleC声明发布了packageC依赖了packageA 那么这三个Bundle之间的类加载器及父类加载器之间的关系如下图
由于没有涉及到具体的OSGi实现所以上图中的类加载器没有指明具体的加载器实现只是一个体现了加载器之间关系的概念模型并且只是体现了OSGi中最简单的加载器委派关系。一般来说在OSGi中加载一个类可能发生的查找行为和委派关系会比上图中显示的复杂类加载时的查找规则如下
以java.*开头的类委派给父类加载器加载 否则委派列表名单内的类委派给父类加载器加载 否则Import列表中的类委派给Export这个类的Bundle的类加载器加载 否则查找当前Bundle的ClassPath使用自己的类加载器加载 否则查找是否在自己的Fragment Bundle中如果是则委派给Fragment bundle的类加载器加载 否则查找Dynamic Import列表的Bundle委派给对应Bundle的类加载器加载 否则查找失败 从之前的图可以看出在OSGi里面加载器的关系不再是双亲委派模型的树形架构而是已经进一步发展成了一种更复杂的、运行时才能确定的网状结构 相关面试题一个类的静态块是否可能被执行两次
一个自于网易面试官的一个问题一个类的静态块是否可能被执行两次。
答案如果一个类被两个 osgi的bundle加载 然后又有实例被初始化其静态块会被执行两次 什么是SPI 机制
Spi 机制加载第三方扩展的jar包类初始化。 mysql, dubbo rpc SPi机制的原理 java SPI全称Service Provider Interface 。是java 提供的一套用来被第三方实现的API他可以用来启用框架扩展和替换组件。实际上是基于接口编程策略模式配置文件 组合实现的动态加载机制。
JDBC
原本的JDBC Class.forName(“DriverName”) 是通过调用Driver中静态代码块中的将Driver注册
public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException(Cant register driver!);}}
}使用SPI的JDBC 在mysql的jar包中的META-INF/services/java.sql.Driver 文件中指明当前使用的Driver然后可以直接调用
Connection conn DriverManager.getConnection(jdbc:mysql://localhost:3306/mydb?characterEncodingGBK, root, );
问题是 一个类的加载器和调用他的加载器相同 这里调用的是 bootstrap类加载器无法加载到子类厂商中的类 方法使用线程上下文加载器
public class DriverManager {static {loadInitialDrivers();println(JDBC DriverManager initialized);}private static void loadInitialDrivers() {//省略代码//这里就是查找各个sql厂商在自己的jar包中通过spi注册的驱动ServiceLoaderDriver loadedDrivers ServiceLoader.load(Driver.class);IteratorDriver driversIterator loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}//省略代码}
}使用Thread类的 getContextClassLoader public static S ServiceLoaderS load(ClassS service) {ClassLoader cl Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}public static S ServiceLoaderS load(ClassS service,ClassLoader loader){return new ServiceLoader(service, loader);}
整个mysql的驱动加载过程:
第一获取线程上下文类加载器从而也就获得了应用程序类加载器也可能是自定义的类加载器 第二从META-INF/services/java.sql.Driver文件中获取具体的实现类名“com.mysql.jdbc.Driver” 第三通过线程上下文类加载器去加载这个Driver类从而避开了双亲委派模型的弊端 SPI参考39 如何破坏双亲委派机制原则 - 简书 知识来源
JVM问题(一) -- 如何打破双亲委派模型_如何打破双亲委派机制_leo_messi94的博客-CSDN博客
打破双亲委派的几种办法_破坏双亲委派_hhpub的博客-CSDN博客