代码之家  ›  专栏  ›  技术社区  ›  doppelfish

人们使用类加载是为了什么?

  •  16
  • doppelfish  · 技术社区  · 16 年前

    因此,每一个Java文本书都谈到Java是如何灵活的,因为它可以在运行时加载类。把一根绳子拼凑起来交给 Class.forName() 然后抓住 ClassNotFoundException 处理它。这一理论就到此为止。

    你能举例说明你如何使用Java类加载来实现一个原本不可能或容易的特性吗?注意我是 问“什么伟大的事情” 能够 是吗?”-我正在寻找现实世界的例子,无论是一个开源的应用程序,还是一个专有的应用程序(如果你可以在不给出太多细节的情况下描述它的话)。

    编辑: 当然,虚拟机可以根据需要延迟加载类。只要我确信我需要的所有课程都在那里,那就是一个幕后的事情。我怎么处理 ClassNotFoundException类 是吗?假设我已经写了十页的文字, PrinterDriver 找不到类。

    18 回复  |  直到 14 年前
        1
  •  10
  •   Gnurou    16 年前

    插件是首先想到的。Java类的加载使得它与C++语言相比非常容易。

    您可能不知道的一点是,任何Java虚拟机都严重依赖于内部的类加载。每当字节码解释器看到对某个方法的引用时,它都会检查该方法所属的类是否已经加载,如果没有,则使用后面相同的机制加载它。 Class.forName() 在解决方法之前。这种MeCeScript是非常强大的,因为任何Java应用程序真正充当一组可替换的组件,它们都是动态加载的。如果虚拟机写得很好,那么它可以通过一个自定义类加载器来加载类,该加载器从网络而不是常规文件中提取类。

    类加载时间取决于虚拟机实现,但大多数情况下依赖于这种后期绑定机制,后者在VM第一次遇到类时加载该类。

        2
  •  5
  •   ewernli    16 年前

    应用服务器也非常依赖 ClassLoaders 隔离不同部署的模块。例如。

    • 您可以在不同路径下部署同一个Web应用程序两次
    • 两个应用程序可以依赖同一库的两个不同版本,而不会发生冲突。

    多亏了类装入器的魔力…

        3
  •  5
  •   NawaMan    16 年前

    “plugin”这个词很重要。

    基本上,当您编写和编译程序时,您可以加载一个您不知道何时存在或不存在的类。

    例如,如果希望程序进行拼写检查,可以编写一个接口 SpellChecker 然后从实现 拼写检查器 接口。之后,您可以编写任何拼写检查程序,并在配置文件中设置实际文件名。这样,您的程序就不需要知道哪个类将执行拼写检查。

    DB驱动程序、Eclipse的插件、脚本语言、密码学方法都是这样做的,因为原始作者不知道(在某些情况下,不知道)实际将使用什么类。

    希望这有帮助。

        4
  •  3
  •   paxdiablo    16 年前

    好吧,我使用它动态地将JDBC驱动程序加载到J2EE应用程序中。我不知道这是什么时候能做得更好。

    在当时做这个比较容易 forName() 打电话。

        5
  •  2
  •   Eric    16 年前

    我确信Java中的插件加载很大程度上依赖于此。

    应用程序检查命名函数并执行它们

    Eclipse actually uses that for plugins

    关键思想是执行开发时没有计划的代码。

        6
  •  2
  •   JRL    16 年前

    在使用API的情况下,它可能非常有用,而API设计器实际上不赞成从一个版本到下一个版本的某些类(例如 Contacts 在Android中)。

    如果没有基于字符串名称的反射和动态类加载,那么在这个实例中,在两个版本的平台上运行相同的程序,就不可能在运行时获得未找到类的异常。但有了它,同一个程序被稍作调整,然后可以在两个平台上运行。

        7
  •  2
  •   Thilo    16 年前

    类加载器也用于非类资源。配置文件浮现在脑海中。由于有一个定义良好的搜索顺序,很容易放入您自己的“log4j.xml”或“hibernate.properties”,应用程序将找到并使用它。

        8
  •  2
  •   Michael Wiles    16 年前

    我记得创建了一个类加载器来远程加载类。应用程序在一个节点上运行,而类存储在另一个节点上。

    而且,通过定制类加载器,您可以在加载类时对它们进行转换。这被一些ORM框架和一些AOP框架使用。

        9
  •  2
  •   BalusC    16 年前

    JDBCAPI就是一个很好的例子。这样,您可以在外部配置JDBC驱动程序,例如属性文件:

    driver = com.dbvendor.jdbc.Driver
    url = jdbc:dbvendor://localhost/dbname
    username = stackoverflow
    password = youneverguess
    

    …您可以使用它作为:

    Properties properties = new Properties();
    properties.load(Thread.currentThread().getResourceAsStream("jdbc.properties"));
    
    String driver = properties.getProperty("driver");
    String url = properties.getProperty("url");
    String username = properties.getProperty("username");
    String password = properties.getProperty("password");
    
    Class.forName(driver);
    Connection connection = DriverManager.getConnection(url, username, password);
    

    每个JDBC驱动程序实现基本上都在 DriverManager 里面 static 初始值设定项块。也就是在 Class#forName() .

    package com.dbvendor.jdbc;
    
    public class Driver implements java.sql.Driver {
    
        static {
             java.sql.DriverManager.registerDriver(new Driver());
        }
    
        private Driver() {
            // ...
        }
    
        public boolean acceptsURL(String url) {
            return url.startsWith("jdbc:dbvendor");
        }
    
    }
    

    自从 驱动管理器 粗略地 像这样(它实际上使用了老式的 Vector )

    private static final Set<Driver> drivers = new HashSet<Driver>();
    
    public static void registerDriver(Driver driver) {
        drivers.add(driver);
    }
    
    public static Connection getConnection(String url, String username, String password) throws SQLException {
        for (Driver driver : drivers) {
            if (driver.acceptsURL(url)) {
                return driver.connect(url, username, password);
            }
        }
        throw new SQLException("No suitable driver");
    }
    

    …您可以从中获取连接,而无需实例化驱动程序本身!

    通过这种方式,JDBC代码是高度可移植的。您可以更改数据库或在具有不同数据库的用户之间分发代码,而无需更改/黑客/重建代码本身。

    不仅JDBC使用这种方法,其他API(如servlet API)、ORM(如hibernate/jpa)、依赖注入框架等也使用反射来加载基于外部可配置属性文件、XML配置文件和/或注释的类。这一切只会使代码更易于移植和插入。

        10
  •  1
  •   Dyno Fu    16 年前

    我认为JUnit还可以使用很多反射特性来使测试框架成为通用的。

        11
  •  1
  •   Alex Miller    16 年前

    Java类加载器机制是强大的,因为它提供了一个抽象点,恰好在代码加载的地方,它可以让你做一些事情,比如:

    • 查找类位的位置不是类路径(db、远程url、文件系统等)
    • 加载您刚刚创建和编译的源代码(使用javacpi)
    • 加载刚刚自己生成的字节代码(比如asm)
    • 加载代码并在使用之前修改它(使用ASM、Java代理等)
    • 在运行中重新加载代码
    • 在树(普通委托)或Web(基于兄弟体的OSGi样式)或任何您想要的方式中将加载程序链接在一起

    关于在加载期间修改代码的问题,您可以做一些有趣的事情来重新混合您的代码-AOP、分析、跟踪、行为修改等。在Terracotta,我们依赖类加载器抽象来动态加载类,然后截获对字段的所有访问,并动态添加从SAM加载状态的能力。稍后群集中远程节点上的e对象。很酷的东西。

        12
  •  0
  •   Stephen C    16 年前

    我在构建一个现成的应用程序时使用它,该应用程序需要由我自己或客户定制以满足客户的特定需求。

        13
  •  0
  •   yawn    16 年前

    查看对Oracle的支持 LOB handling 例如,在Spring框架中。仅因为框架为Oracle提供了特定的支持,您可能不想将Oracle数据源作为您的依赖项部署,例如mysql项目。因此,您可以在LOB处理程序实例的范围内反射地加载Oracle驱动程序。

        14
  •  0
  •   Harold L    16 年前

    像Tomcat这样的servlet容器从WEB-INF/WEB.xml读取您的war/webapp配置文件,并根据您放入XML文件中的字符串值加载servlet/filter/etc.子类。对于数据库连接,它们从配置中提取要加载的类名,例如mysql的“com.mysql.jdbc.driver”。

        15
  •  0
  •   SyntaxT3rr0r    16 年前

    真实世界的例子(如您的问题所要求的),专有应用程序(如您的问题明确允许的)。

    启动后,客户端软件会联系我们的服务器并说“我拥有的接口栏的默认实现是foo(因为实际上每个版本1.03,例如,都使用foo),您有更好的版本吗?”如果同时我们写了一个更好的实现,我们会回答“是的,酒吧是老的,使用buz,它是更好的”。

    然后在客户端,使用类装入器来加载最新的实现。

    它过于简单化了,但它是一个现实世界的例子。它与上面提到的JRL示例并不完全不同:不推荐使用的类自动替换为新类。

        16
  •  0
  •   dietrich    16 年前

    正如thilo提到的,使用动态类加载对于加载配置文件也非常有用。更一般地说,动态类加载可以在许多情况下成为一个很好的文件系统抽象层,从而简化编写偏好和配置敏感代码。只要确保您需要的资源在类路径上,并将其作为输入流加载即可。

    此外,使用Java中的自定义协议处理程序,可以通过URL访问类路径上的项。这并不是动态类加载特有的优势,但它确实演示了如何通过与其他资源(甚至远程资源)相同的API访问类路径资源。 http://java.sun.com/developer/onlineTraining/protocolhandlers/

        17
  •  0
  •   fastcodejava    16 年前

    任何基于配置的框架(struts、jsf、spring、hibernate等)都使用这种机制。任何基于插件架构的产品都会使用此功能。

        18
  •  -2
  •   dev ray    16 年前

    如果类位于类路径中,则可以使用class::forname方法。但是,如果需要提供一个路径和类名,即C:\document\xyz.class,则必须使用urlclassloader类。