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

因此,每本Java教科书都讨论了Java的灵活性,因为它可以在运行时加载类。 只需拼凑一个字符串并将其提供给Class.forName() ,并捕获ClassNotFoundException并处理它。 这个理论太多了。

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

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

插件是第一个浮现在脑海中的东西。 与C ++之类的语言相比,Java类加载使其变得非常容易。

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

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

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

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

感谢类加载器的魔力……

“PLUGIN”,这就是大字。

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

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

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

希望这可以帮助。

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

当时执行forName()调用更容易。

我非常确定Java中的插件加载依赖于此。

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

Eclipse实际上将它用于插件

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

它在您使用API​​的情况下非常有用,并且API设计者实际上将某些类从一个版本弃用到下一个版本(例如Android中的“ 联系人” )。

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

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

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

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

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

 driver = com.dbvendor.jdbc.Driver
 url = jdbc:dbvendor:// localhost / dbname
 username = stackoverflow
密码= 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驱动程序实现基本上都在static初始化程序块内的DriverManager注册。 它就是在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 drivers = new HashSet(); 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,dependency injection框架,等等,使用reflection来加载基于外部可配置的属性文件,XML配置文件和/或注释的类。 这一切只是使代码更加便携和可插拔。

我认为JUnit也可能使用了很多reflectionfunction来使测试框架具有通用性。

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

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

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

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

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

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

真实世界的例子(根据你的问题的要求),专有的应用程序(你的问题明确允许)……

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

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

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

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

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

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

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