人们使用类加载的是什么?

时间:2010-02-01 06:46:30

标签: java classloader

因此,每本Java教科书都讨论了Java的灵活性,因为它可以在运行时加载类。只需拼凑一个字符串并将其交给Class.forName(),然后抓住ClassNotFoundException并处理它。这个理论非常重要。

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

编辑:当然,VM会根据需要懒惰地加载类。只要我确信我所需要的所有课程都在那里,这就是幕后的事情。我该如何处理ClassNotFoundException?假设我写了十页文本,找不到PrinterDriver类。

18 个答案:

答案 0 :(得分:10)

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

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

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

答案 1 :(得分:5)

“PLUGIN”,这是个大词。

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

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

数据库驱动程序,Eclipse的插件,脚本语言,加密方法都是以这种方式完成的,原始作者不知道(在某些情况下,不知道)实际使用哪个类。

希望这有帮助。

答案 2 :(得分:5)

应用服务器也非常依赖ClassLoaders来隔离不同的已部署模块。 E.g。

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

感谢类加载器的魔力......

答案 3 :(得分:3)

好吧,我用它将JDBC驱动程序动态加载到J2EE应用程序中。这可能是一个更好的方式,我不知道。

当时更容易进行forName()来电。

答案 4 :(得分:2)

我非常确定Java中的插件加载很大程度上依赖于它。

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

Eclipse actually uses that for plugins

关键的想法是执行未在开发时计划的代码。

答案 5 :(得分:2)

在您使用API​​并且API设计人员实际上将某些类从一个版本弃用到下一个版本(例如Android中的Contacts)时,它非常有用。

如果没有基于字符串名称的反射和动态类加载,在这种情况下不可能在平台的两个版本上运行相同的程序而不会在运行时获得类未找到的异常。但有了它,相同的程序被调整了一点,然后可以在两个平台上运行。

答案 6 :(得分:2)

ClassLoader也用于非类资源。想到配置文件。由于存在明确定义的搜索顺序,因此很容易插入自己的“log4j.xml”或“hibernate.properties”,应用程序将查找并使用它。

答案 7 :(得分:2)

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

通过自定义类加载器,您可以在加载类时对其进行转换。一些ORM框架以及一些AOP框架使用它。

答案 8 :(得分:2)

JDBC API就是一个很好的例子。这样,您可以在外部配置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");
    }

}

由于DriverManager 大致看起来像这样(它实际上使用了旧式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配置文件和/或注释的类。这一切只是使代码更加便携和可插拔。

答案 9 :(得分:1)

我认为JUnit也可能使用了很多反射功能来使测试框架通用。

答案 10 :(得分:1)

Java类加载器机制非常强大,因为它在完全加载代码的位置提供了一个抽象点,这使您可以执行以下操作:

  • 在类路径(db,远程url,文件系统等)之外的某个位置查找类位
  • 加载您刚创建并自行编译的源代码(使用javac api)
  • 加载您自己生成的字节代码(比如ASM)
  • 在使用代码之前加载代码并对其进行修改(使用ASM,Java代理等)
  • 即时重新加载代码
  • 链式加载器一起在树(正常委托)或网(基于兄弟的OSGi样式)或任何你想要的

在加载过程中修改代码时,你可以做一些有趣的事情来重新混合你的代码--AOP,分析,跟踪,行为修改等。在Terracotta我们依靠类加载器抽象来动态加载class,然后拦截对字段的所有访问,并动态添加从稍后在集群中的远程节点上的同一对象加载状态的功能。很酷的东西。

答案 11 :(得分:0)

例如,请参阅Spring Framework中对Oracle LOB handling的支持。只是因为框架为Oracle提供了特定的支持,您可能不希望将Oracle数据源部署为例如Oracle的数据源。 MySQL项目。因此,您可以在LOB处理程序的实例范围内反射性地加载Oracle驱动程序。

答案 12 :(得分:0)

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

答案 13 :(得分:0)

使用动态类加载对于加载配置文件也非常有用,如Thilo所提到的。更一般地说,动态类加载可以在许多情况下创建一个很好的文件系统抽象层,简化了编写首选项和配置敏感代码。只需确保您需要的资源位于类路径上并将其作为InputStream加载。

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

答案 14 :(得分:0)

真实示例(根据您的问题提出要求),专有应用程序(由您的问题明确允许)......

启动时,客户端软件联系我们的服务器并说“我有的接口栏的默认实现是Foo(因为实际上每个版本1.03,例如,正在使用Foo),你有更好的一个吗? ?”如果同时我们写了一个更好的实现,我们回答“是的,Bar是旧的,使用Buz,它更好”。

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

它过于简单,但它是一个真实世界的例子。它与提到的JRL示例并不完全不同:已弃用的类会被新的类自动替换。

答案 15 :(得分:0)

像Tomcat这样的Servlet容器从WEB-INF / web.xml读取war / webapp配置文件并加载Servlet / Filter / etc。基于您放入XML文件的String值的子类。对于数据库连接,他们从类配置中提取类名称,例如MySQL的“com.mysql.jdbc.Driver”。

答案 16 :(得分:0)

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

答案 17 :(得分:-2)

如果类在类路径中,则可以使用Class :: forName方法。但是,如果需要提供路径以及类名,即c:\ document \ xyz.class,则必须使用URLClassLoader类。