`
sharajava
  • 浏览: 66884 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

类的动态加载

    博客分类:
  • Java
 
阅读更多

问:调用 Class.forName() ClassLoader.loadClass() 的区别在什么地方?

答:这两方法都是通过一个给定的类名去定位和加载这个类名对应的 java.long.Class 类对象 . 尽管如此 , 它们的在行为方式上还是有区别的 .

Ø         用哪个 java.lang.ClassLoader 进行加载

Ø         返回的 Class 对象是否被初始化

Class.forName(String) 方法(只有一个参数), 使用调用者的类加载器来加载, 也就是用加载了调用forName方法的代码的那个类加载器. 相应的, ClassLoader.loadClass()方法是一个实例方法(非静态方法), 调用时需要自己指定类加载器, 那么这个类加载器就可能是也可能不是加载调用代码的类加载器. 如果用特定的类加载器来加载类在你的设计中占有比较重要的地位, 你就应该调用ClassLoader.loadClass(String)方法或Class.forName(String, boolean, ClassLoader)方法

    另外, Class.forName()方法对加载的类对象进行初始化. 可见的效果就是类中静态初始化段及字节码中对所有静态成员的初始工作的执行(这个过程在类的所有父类中递归地调用). 这点就与ClassLoader.loadClass()不同. ClassLoader.loadClass()加载的类对象是在第一次被调用时才进行初始化的

    你可以利用上述的差异. 比如,要加载一个静态初始化开销很大的类, 你就可以选择提前加载该类(以确保它在classpath), 但不进行初始化, 直到第一次使用该类的域或方法时才进行初始化

    最常用的是Class.forName(String, boolean, ClassLoader). 设置第二个参数为false即推迟初始化, 第三个参数指定要用来进行加载的类加载器. 我建议为了最大的灵活性使用这个方法

类初始化错误是难处理的

    成功地加载了类, 并不意味着就不会有其它问题. 静态初始化代码可以抛出异常, 异常被包装到java.long.ExceptionInInitializerError的实例中. 异常抛出后, 这个类将不可用. 这样, 如果你需要在代码中处理这些错误, 你就应该调用进行初始化的Class.forName()方法. <o:p></o:p>

    但进一步说, 如果你要处理ExceptionInInitializerError并试图从错误中恢复, 很可能不如你想象的那样正常工作. 请看下面的示例代码:<o:p></o:p>


public   class  Main
{
    
public   static   void  main (String [] args)  throws  Exception
    {
        
for  ( int  repeat = 0; repeat < 3; ++ repeat)
        {
            
try
            {
                
// "Real" name for X is outer class name+$+nested class name:
                Class.forName ("Main$X");
            }
            
catch  (Throwable t)
            {
                System.out.println ("load attempt #" + repeat + ":");
                t.printStackTrace (System.out);
            }
        }
    }

    
private   static   class  X
    {
        
static
        {
            
if  (++ s_count == 1)
                
throw   new  RuntimeException ("failing static initializer<v:shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype><v:shape id="_x0000_i1025" style="WIDTH: 11.25pt; HEIGHT: 15pt" alt="" type="#_x0000_t75"><v:imagedata o:href="/Images/dot.gif" src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif"></v:imagedata></v:shape>");
        }
        
    } 
// End of nested class

    
private   static   int  s_count;

// End of class

<o:p></o:p>

    上面的代码3次尝试加载一个内部类X, 即便是X的静态初始化只在每一次加载时失败, 3次加载都抛出了异常. <o:p></o:p>

>java Main
load attempt #0:
java.lang.ExceptionInInitializerError
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:140)
        at Main.main(Main.java:17)
Caused by: java.lang.RuntimeException: failing static initializer...
        at Main$X.<clinit>(Main.java:40)
        ... 3 more
load attempt #1:
java.lang.NoClassDefFoundError
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:140)
        at Main.main(Main.java:17)
load attempt #2:
java.lang.NoClassDefFoundError
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:140)
        at Main.main(Main.java:17)
<o:p></o:p>

    有点令人吃惊的时, 在第2, 3次进行类加载时, 抛出的异常竟然是java.lang.NoClassDefFoundError. 这里发生的事情是, 第一次加载后(在进行初始化之前), JVM发现X已经被加载, 而这个X的类实例在加载它的类加载器被垃圾回收之前是不会被卸载的. 所以这之后的对Class.forName()的调用时, JVM不会再尝试进行初始化的工作, 但是, 更令人不解的是, 抛出一个NoClassDefFoundError. <o:p></o:p>

    卸载这样的类的方法是丢弃原来加载该类的类加载器实例并重新创建一个. 当然, 这只能是在你使用了Class.forName(String, boolean, ClassLoader)这个3参数的方法的时候才能办到.<o:p></o:p>

隐藏的 Class.forName() 方法

    你一定用过JavaX.class的语法去获取一个在编译器就知道名字的类对象实例. 在字节码的层次上, 这一点是如何实现的就不被人熟知了. 不同的编译器有不同的实例细节, 但共同点是, 所有编译器所相应产生的代码都是调用的Class.forName(String)这一个参数的方法. 比如J2SE <st1:chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False">1.4.1</st1:chsdate>javac就把Class cls = X.class; 翻译成如下等价的形式:<o:p></o:p>

 <v:shape id="_x0000_i1026" style="WIDTH: 11.25pt; HEIGHT: 15pt" alt="" type="#_x0000_t75"><v:imagedata o:href="/Images/dot.gif" src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif"></v:imagedata></v:shape>
        
// This is how "Class cls = X.class" is transformed:
         if  ( class $Main$X ==  null )
        {
            
class $Main$X =  class $ ("Main$X");
        }
        Class cls = 
class $Main$X;

    <v:shape id="_x0000_i1027" style="WIDTH: 11.25pt; HEIGHT: 15pt" alt="" type="#_x0000_t75"><v:imagedata o:href="/Images/dot.gif" src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif"></v:imagedata></v:shape>

    
static  Class  class $ (String s)
    {
        
try
        {
            
return  Class.forName (s);
        }
        
catch  (ClassNotFoundException e)
        {
            
throw   new  NoClassDefFoundError (e.getMessage());
        }
    }

    
static  Class  class $Main$X;  // A synthetic field created by the compiler <o:p></o:p>

Sun javac 开个玩笑

从上面的例子你可以看到, 编译器调用Class.forName()方法加载类对象, 并将其缓存到一个包内可见的静态变量中. 这种令人费解的实现方式的可能是因为在早期版本的Java, 这种X.class的语法还未被支持, so the feature was added on top of the Java 1.0 byte-code instruction set.(???)<o:p></o:p>

利用这一点, 你可以在编译器的开销上做一些有趣的事情. J2SE <st1:chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False">1.3.1</st1:chsdate>编译下面的代码片段: <o:p></o:p>

public   class  <st1:place w:st="on">Main</st1:place>
{
    
public   static   void  main (String [] args)  throws  Exception
    {
        System.out.println ("String class: " + String.
class );
        
class $java$lang$String =  int . class ;
        System.out.println ("String class: " + String.
class );
    }
    
    
static  Class  class $java$lang$String;

// End of class <o:p></o:p>

运行它, 你会得到下面这个很荒谬的输出结果: <o:p></o:p>

>java <st1:place w:st="on">Main</st1:place><o:p></o:p>

String class: class java.lang.String<o:p></o:p>

String class: int<o:p></o:p>

J2SE <st1:chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False">1.4.1</st1:chsdate>, 上面的代码将不能被编译通过, 但你仍然可以用反射的方式戏弄它: <o:p></o:p>

public   static   void  main (String [] args)  throws  Exception
    {
        System.out.println ("String class: " + String.
class );
        Main.
class .getDeclaredField ("class$java$lang$String").set ( null int . class );
        System.out.println ("String class: " + String.
class );
    }
<o:p></o:p>

<o:p> </o:p>

    综上所述, 下次你再调用Class.forName()方法时, 你应该知道它的局限性可选的替代方案了. <o:p></o:p>

<o:p> </o:p>

 

分享到:
评论

相关推荐

    微信小程序三级分类动态加载数据

    在这个项目中,“微信小程序三级分类动态加载数据”指的是在小程序中实现一个具有三层结构的分类系统,并且能够根据用户的操作动态加载各级别的分类数据。 首先,我们来看看核心的文件结构: 1. `app.js`:这是小...

    Java类动态加载(一)——java源文件动态编译为class文件

    在Java编程中,类动态加载是一项重要的技术,它允许程序在运行时根据需要加载新的类,从而提高了灵活性和可扩展性。这篇博客“Java类动态加载(一)——java源文件动态编译为class文件”可能主要探讨了如何在运行时...

    Java代码执行漏洞中类动态加载的应用1

    在Java编程中,类动态加载是一项重要的特性,它允许程序在运行时加载未知或自定义的类。这个特性在某些场景下非常有用,如插件系统、热部署以及安全相关的利用。本文主要探讨了Java代码执行漏洞中类动态加载的应用,...

    Java类动态加载机制在铁路互联网售票中的设计与实现.pdf

    ### Java类动态加载机制在铁路互联网售票中的设计与实现 #### 概述 随着铁路互联网售票系统的广泛应用,其面临着越来越高的并发访问压力。为解决这一问题,系统采用分布式内存数据库集群来存储和处理如余票查询、...

    反射,动态加载指定类调用类中的方法

    在“反射,动态加载指定类调用类中的方法”这个主题中,我们将深入探讨如何使用反射来实现动态加载和执行类的方法。首先,我们需要理解几个关键的反射API: 1. `Class&lt;?&gt;`: 这是所有Java类的通用表示,通过`Class....

    动态加载jar包

    在Java中,动态加载jar包的核心在于使用`java.lang.ClassLoader`类或其子类。ClassLoader是Java虚拟机(JVM)的一部分,负责将类的字节码加载到JVM中并转换为Class对象。默认情况下,JVM会使用系统类加载器来查找和...

    Java 动态加载jar文件示例

    使用自定义类加载器后,我们可以在程序运行时动态加载jar文件中的类。例如,如果我们有一个名为`com.example.Plugin`的插件类存在于jar文件中,可以这样加载: ```java String jarPath = "path_to_your_jar.jar"; ...

    springboot+java类热加载

    例如,`MemoryClassLoader.java`可能就是一个自定义类加载器的实现,它可以在内存中动态加载或更新类。 **JarinJAR**是一种打包技术,它可以将多个JAR文件打包成一个大的JAR文件。在热加载场景下,JarinJAR使得在...

    CButtonST类动态加载bmp或ico图标的实例

    点击动态加载图片,将会弹出当前文件夹,用户可以选择pc某一目录下的图标,点击确认后按钮上的图标将会即时更新。代码当前支持是ico格式,但包含了bmp格式图片的支持(该部分代码用#if 0 #else宏包含)!

    动态加载类机制JAVA

    在Java编程语言中,动态加载类机制是一种强大的功能,它允许程序在运行时根据需要加载新的类或资源,而不是在编译时确定所有类。这种技术对于提高软件的灵活性、可扩展性和模块化至关重要,特别是在大型系统和插件式...

    JAVA类加载机制与动态代理

    ### JAVA类加载机制与动态代理 #### 一、类加载机制 ##### 1.1 类加载的时机 类加载机制负责将描述类的数据从`.class`文件加载到内存,并进行必要的校验、转换解析和初始化,使之成为可以被Java虚拟机直接使用的...

    动态加载dll,动态加载webservice

    动态加载DLL允许在不重新编译主程序的情况下添加、更新或移除功能,而动态加载WebService则使得应用程序能灵活地与远程服务进行交互,即使这些服务的接口可能在运行时发生变化。这两项技术在软件开发中都有广泛的...

    WPF 实现TabControl 中动态添加选项卡、动态添加Frame

    动态添加选项卡和`Frame`是提高用户界面灵活性的重要方法,允许应用程序根据需求在运行时创建新的视图。下面我们将详细探讨如何在WPF中实现这一功能。 首先,我们需要理解`TabControl`的基本结构。`TabControl`由一...

    apk动态加载研究(1)

    4. **插件化开发**:动态加载有助于实现插件化架构,使得新功能可以通过添加新的插件模块来实现,降低维护成本。 实现APK动态加载涉及的技术包括: 1. ** DexClassLoader**:Android系统提供的类加载器,可以加载...

    android动态加载类

    android服务端动态加载类。  在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势...

    Java反射动态加载实例类

    ### Java反射机制与动态加载实例类 在Java中,反射是一种强大的工具,允许程序在运行时检查和修改其结构和行为。通过反射,我们可以动态地加载类、创建对象、访问和修改字段、调用方法等。本文将深入探讨Java反射...

    动态编译、加载java类

    在动态加载类后,反射可用于创建类的实例,调用方法,访问和修改字段,甚至执行私有成员的操作。 5. **代理(Proxy)**: Java的动态代理机制可以在运行时创建实现一组指定接口的新类。这在实现回调、事件监听或者...

    实现动态加载布局和动态加载控件的两个demo例子

    首先,动态加载布局指的是在运行时根据需要创建和添加布局。这通常通过Java代码或者Kotlin代码来完成,而不是在XML布局文件中预先定义。这样做的好处是可以减少应用资源的占用,同时增加布局的动态性和适应性。例如...

    Android中的动态加载机制

    1. ClassLoader重写:Android系统的类加载是通过ClassLoader来完成的,因此,要实现动态加载,我们需要自定义ClassLoader。通过对默认的 DexClassLoader 或 PathClassLoader 进行扩展,我们可以控制何时以及如何加载...

Global site tag (gtag.js) - Google Analytics