类加载器介绍在介绍双亲委派模型之前,先介绍一下类加载器。类加载器将类的完全限定名称转换为描述该类的二进制字节流。对于任何一个类,被同一个类加载器加载后都是唯一的,但是如果被不同的加载器加载就不是唯一的了。即使源自同一个Class文件,被同一个JVM加载,只要加载类的加载器不同,类就会不同。如何判断类是否相同可以通过Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果来判断,也可以通过instanceof关键字来判断类的关系物体。让我们写一个由不同类加载器加载的类,看看它如何影响instanceof关键字操作:>loadClass(Stringname)throwsClassNotFoundException{try{StringfileName=name.substring(name.lastIndexOf(".")+1)+".class";InputStreaminputStream=getClass().getResourceAsStream(文件名);if(inputStream==null){returnsuper.loadClass(name);}byte[]array=newbyte[inputStream.available()];inputStream.read(数组);returndefineClass(name,array,0,array.length);}catch(IOExceptione){thrownewClassNotFoundException(name);}}};Objectobject=myLoader.loadClass("OneMoreStudy").newInstance();System.out.println("类名:"+object.getClass().getName());System.out.println("instanceof:"+(objectinstanceofOneMoreStudy));}}运行结果:classname:OneMoreStudyinstanceof:false在运行结果中,第一行显示对象确实是从OneMoreStudy类实例化而来,但是第二行的instanceof运行结果为False,说明有两个OneMoreStudy类在JVM中,一个是系统应用类加载器加载的,一个是我们自定义的类加载器加载的,虽然都是来自同一个Class文件,在同一个JVM中,但是被不同的类加载器加载后,它们是还是两个独立的班级。类加载器的划分除了上面示例代码中我们实现的自定义类加载器外,系统还提供了3种类加载器:BootstrapClassLoader:负责存放在%JAVA_HOME%\lib目录下,或者在-Xbootclasspath参数指定的路径下,将JVM识别的类库加载到JVM内存中。只是根据文件名来识别,比如rt.jar,名字不匹配的类库即使放在lib目录下也不会被加载。它由C++语言实现,不能被Java程序直接引用。ExtensionClassLoader:负责加载%JAVA_HOME%\lib\ext目录下,或者java.ext.dirs系统变量指定路径下的所有类库。它由sun.misc.Launcher.ExtClassLoader实现,开发者可以直接使用扩展类加载器。应用类加载器(ApplicationClassLoader):负责加载用户类路径(ClassPath)上指定的类库。由于是ClassLoader中getSystemClassLoader()方法的返回值,所以一般称为系统类加载器。它由sun.misc.Launcher.AppClassLoader实现。开发者可以直接使用这个类加载器。如果应用程序没有自定义自己的类加载器,一般这是程序中默认的类加载器。前面提到的双亲委托模型,对于任何一个类,加载它的类加载器和类本身都需要建立它在JVM中的唯一性。但是类加载器种类繁多,如何保证一个类在JVM中的唯一性呢?为了解决这个问题,双亲委派模型(ParentsDelegationModel)应运而生,也就是下图所示的类加载器之间的层级关系:除了最顶层的启动类加载器,其他所有的类加载器都必须有自己的父类加载器。类加载器之间的父子关系一般不会实现为继承关系,而是通过组合关系来复用父类加载器。类加载器收到类加载请求后,不会先尝试加载该类,而是将请求委托给父类加载器尝试加载。对于每个类加载器都是如此,因此所有加载请求最终都应该路由到顶级启动类加载器。只有当父类加载器报告它无法完成加载请求时(它在它的搜索范围内没有找到需要的类),子加载器才会尝试自己加载它。这样既保证了类在JVM中的唯一性,也保证了Java程序的稳定运行。实现双亲委托模型的代码集中在java.lang.ClassLoader的loadClass()方法中,如下://首先,检查类是否已经加载Class>c=findLoadedClass(name);//如果没有加载,则调用父类加载器的loadClass()方法if(c==null){longt0=System.nanoTime();try{if(parent!=null){c=parent.loadClass(name,false);}else{//如果父类加载器为空,则使用启动类加载器c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){//如果在父类加载器中找不到类,会抛出ClassNotFoundException}if(c==null){//如果找不到父类,调用findClass()找到班级。长t1=System.nanoTime();c=findClass(名字);//记录统计sun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}如果(解决){resolveClass(c);}返回c;}}破坏双亲委托模型双亲委托模型不是强制约束模型,而是Java设计者推荐给开发者的类加载器实现。大多数类加载器都遵循这个模型,但是也有例外,比如下面三种情况:,导致非唯一类。因此,不鼓励开发者重写loadClass()方法,而应该将自己的类加载逻辑写到findClass()方法中。在loadClass()方法的逻辑上,如果父类加载失败,会调用自己的findClass()方法完成加载,这样可以保证新写的类加载器符合双亲委托模型。SPI(ServiceProviderInterface)Java提供了很多SPI(ServiceProviderInterface,服务提供者接口),允许第三方提供这些接口的实现。常见的SPI包括JDBC、JNDI、JCE、JAXB和JBI。SPI接口由Java核心库提供,这些SPI实现代码作为Java应用所依赖的jar包包含在类路径(ClassPath)中。SPI接口中的代码往往需要加载具体的实现类。那么问题来了,SPI接口是Java核心库的一部分,由启动类加载器加载;SPI实现类由系统类加载器加载。引导类加载器找不到SPI的实现类,因为根据双亲委托模型,引导类加载器不能委托系统类加载器加载类。这时候就会用到线程上下文类加载器(ThreadContextClassLoader)。在JVM中,当前线程的类加载器无法加载的类,会交给线程上下文类加载器去加载,直接使用Thread.currentThread()。getContextClassLoader(),默认返回的是应用类加载器,也可以通过java.lang.Thread类的setContextClassLoader()方法设置。线程上下文类加载器破坏了双亲委托模型,即父类加载器请求子类加载器完成类加载动作,但为了实现功能,这也是一种巧妙的实现方式。OSGi(OpenServiceGatewayProtocol)OSGi(OpenServiceGatewayInitiative,开放服务网关协议)技术是Java的动态模块化系统模型。无需重启即可远程安装、启动、升级和卸载程序模块(称为Bundle)。.实现程序模块热部署的关键是其自定义类加载器机制的实现。在OSGi中,类加载器不再是双亲委托模型中的树状结构,而是更复杂的网络结构。类加载的规则简单介绍如下:如果类属于java.*包,加载请求将委托给父加载器如果类定义在启动委托列表(org.osgi.framework.bootdelegation),加载请求委托给父加载器如果类属于Import-Package中定义的包,框架通过ClassLoader依赖图找到导出这个包的Bundle的ClassLoader,将加载请求委托给这个类加载器。如果类资源属于Require-Bundle中定义的Bundle,则框架通过ClassLoader依赖图找到这个Bundle的ClassLoader,并将加载请求委托给这个ClassLoaderBundle搜索自己的类资源(包括定义在Bundle中的类路径)Bundle-Classpath和属于Bundle的Fragment的类资源)。如果类定义在DynamicImport-Package中,则开始尝试在运行环境中寻找符合条件的Bundle。如果经过上面的一系列步骤后,如果仍然没有正确加载类资源,则会抛出类未找到异常。总结类加载器将类的全限定名转换成描述类的二进制字节流,分为启动类加载器、扩展类加载器、应用类加载器和自定义类加载器。在双亲委托模型中,上述各种类加载器形成了一系列的父子关系。子类加载器首先将类加载请求委托给父类加载器尝试加载。当父类加载器加载失败时,子类加载器只尝试自己加载,这样就保证了类在JVM中的唯一性。但是,它并没有遵循双亲委派模型,例如:重写ClassLoader的loadClass()方法,SPI(服务提供者接口),OSGi(开放服务网关协议)。
