有没有用 Class.forName() 加载类的替代方法?

Posted

技术标签:

【中文标题】有没有用 Class.forName() 加载类的替代方法?【英文标题】:Is there an alternative to loading a class with Class.forName()? 【发布时间】:2012-07-14 02:26:12 【问题描述】:

在我的库代码中,我使用 JAXB 从 XML 文件加载类名,以便稍后使用 Class.forName() 实例化它们。一个虚构的例子来说明这个案例:

public void libraryMethod() throws Exception 
  List<String> classNames = loadClassNamesFromXML();

  for (String className : classNames) 
    Class.forName(className).newInstance().doThings();
  

现在,一些用户使用 OSGi 来配置他们的应用程序,他们使用与使用我的 XML 结构配置的类不同的类加载器加载我的库。这意味着加载可能会失败,因为找不到类。

有没有更可靠的方法来加载这些类?或者有没有其他方法来配置这些实例?我愿意接受导致这种情况的建议:

public void libraryMethod() throws Exception 
  // Spring does things like this:
  List<SomeType> instances = loadInstancesFromXML();

  for (SomeType instance : instances) 
    instance.doThings();
  

一些限制:

从库的角度来看,这些实例的生命周期并不重要。如果他们有(用户)状态,我的图书馆不会注意到。 我想在这个库中保持简单,所以我想避免在配置框架(例如 spring)上创建外部依赖项。所以我只对标准 JDK 6+ 发行版可以实现的解决方案感兴趣。 我真的很想保留我的简单 XML 配置文件(对 XML 结构稍作调整是可以的)。

【问题讨论】:

您已经深入研究了一个特殊的解决方案,而没有展示您试图解决的真正问题。你能解释一下你的 xml 配置做什么以及为什么你必须从其他包中加载类而不导入它们的包吗? @ChristianSchneider:我写了一个库(jOOQ)。我无法导入任意用户包,但我希望库用户能够将SomeType 的实现注入我的库中。使用 spring,很容易注入这样的实现,但这意味着我的库将对 spring 本身产生“严重”依赖。 @PreethiJain:我怀疑你是否仔细阅读了我的问题,它比你想象的更具体...... 【参考方案1】:

如果您希望您的库灵活并在 OSGi 和非 OSGi 环境中工作,您应该允许用户提供他们自己的 ClassLoader,或者让他们告诉您的库他们有哪些类名。阅读Neil Bartlett blog post。

原始链接返回404。您可以访问Wayback Machine.上的文章

【讨论】:

尼尔是最终权威,没有理由进一步讨论这个问题 :-) 正如他总结的那样,停止猜测。 链接到 404 的博文。 它在 Wayback Machine 上可用:web.archive.org/web/20150217082541/http://njbartlett.name/2010/…【参考方案2】:

感谢您的解释。在 OSGi 中,Spring 不会让这变得更容易。您不能简单地从未导入的包中注入实现类。在 OSGi 中,您通常使用 OSGi 服务来注入源自您的包之外并且在编译时您不知道的实现。

因此,您的用户将实现您指定的接口并将其实现发布为 OSGi 服务。然后,您可以选择所有此类服务,或者让用户在 xml 配置中为他的服务指定一个 ldap 过滤器。

这种方法的优点是您不必加载类并关心类加载器。所以这是 OSGi 中推荐的方式。如果您想在 OSGi 内部和外部使用相同的解决方案,那么指定类加载器 + 类名的方法是另一种选择。

【讨论】:

感谢您的解释。我认为 spring 可能是一个解决方案的原因是因为那些对 jOOQ/OSGi 有问题的特定用户自己构建了jOOQ 并可能对其进行了修补。但很高兴知道这通常不起作用。 LDAP 听起来有点矫枉过正,但服务可能是个好主意。到目前为止,允许指定自定义类加载器似乎是最好的选择,尽管 @MarkoTopolnik 的上下文类加载器似乎可以作为一种解决方法。 当您在 OSGi 中过滤服务实现时,您可以使用 LDAP 语法中的过滤器。这并不是什么特别的东西,它在所有 OSGi 框架中都可用。发布服务的人可以为其提供属性,而搜索服务的人使用属性上的这些过滤器进行搜索。如果您使用服务方法,则不需要类加载器,但您依赖于 OSGi。【参考方案3】:

一般来说,在 OSGi 中您应该使用服务。 Class.forName/XML 配置如此流行的原因是只有一个类获得控制权。要配置其余部分,它需要知道要初始化/调用的类。

在 OSGi 中不存在这个问题,因为每个模块(捆绑包)(可以)通过声明式服务(或以老式方式通过激活器)获得控制。所以在 OSGi 中你有一个对等模型。任何人都可以注册服务并依赖其他服务。

因此,与其指定类名并假设它们是全局唯一的(它们不在大型系统中),不如使用服务更容易,而且不用离开 Java 编译器;这些类名很容易出错。一般来说,这意味着您通常只需注册您的服务并等待被调用,因为不需要初始化您的客户端。但是,当您想了解您的客户时,白板模式解决了这种情况(使用 bndtools 和 bnd 注释):

“服务器”

@Component
public class MyLib 
   @Reference(type='*')
   void addSomeType(SomeType st ) 
      st.doThings();
   

客户

@Component
public class MyClient implements SomeType 
  public void doThings()  ... 

希望这会有所帮助。

【讨论】:

这很有趣。但是,我不想在我的库中引入这么多的 OSGi,因为不使用 OSGi 的库用户也应该能够从这个功能中受益...... 实际上,看看jooq,您可能希望让生活更加模块化并支持extender模式。在 OSGi 中,跟踪变得活跃的包是微不足道的【参考方案4】:

JDBC4 驱动程序在 jar 中包含 META-INF/services/java.sql.Driver,它使用 ServiceProvider 机制向 JVM 注册驱动程序实现(请参阅java.util.ServiceLoader javadocs)。将驱动程序放在类路径上将自动注册驱动程序,无需使用 Class.forName。相反,应用程序代码使用 ServiceLoader.load 来发现已注册的驱动程序。相同的机制可用于其他配置。也许可以使用类似的东西?顺便说一句,在使用 Service Provider 机制注册自己的实现时,使用像 spi 这样的注解看起来很方便。

【讨论】:

是的。 ServiceProvider 是对 Service Registry 的另一个破坏性模仿。但是,如果 100% OSGi 不是一个选项,ServiceProvider 还是聊胜于无。不过,它对类加载器没有帮助。 @IvanDubrov:很有趣。如果我使用 ServiceProvider,您能否解释一下“类加载问题”会是什么? 它要么使用 ThreadContext 类加载器(在 OSGi 中被破坏),要么要求您显式提供单个类加载器,但在 OSGi 中可能有多个提供服务的包,因此您有多个类加载器。

以上是关于有没有用 Class.forName() 加载类的替代方法?的主要内容,如果未能解决你的问题,请参考以下文章

java反射中,Class.forName 和 ClassLoader 加载类的区别

Class.forName()概述

Class.forName与动态加载

Class.forName()用法详解

Class.forName()用法详解

Class.forName()用法详解