如何在Java中覆盖服务提供者

14
这更像是一个一般性的问题,以例子为基础: 我正在使用xstream和woodstox,woodstox jar中自带javax.xml.stream.XMLOutputFactory服务提供程序,并注册com.ctc.wstx.stax.WstxOutputFactory。 我想提供自己的javax.xml.stream.XMLOutputFactory并仍然有woodstox jar在类路径中。我知道可以通过系统属性javax.xml.stream.XMLOutputFactory来提供自己的工厂,但我正试图减轻我们的dev ops团队的麻烦,并使用我的jar或者也许是我的war的META-INF/services文件夹中的服务文件来实现。通过查看javax.xml.stream.FactoryFinder的代码,我如何确保我的META-INF/services/javax.xml.stream.XMLOutputFactory文件将被FactoryFinder使用?
我们正在使用camel与xstream,无法找到一种将工厂注入XStreamDataFormat的方法。
5个回答

10
首先,我强烈建议您简化生活并避免使用JDK SPI接口。与手动注入XMLInputFactory和/或XMLOutputFactory相比,它确实没有任何价值。对于注入,您可以使用Guice(或Spring);或者手动传递。由于这些工厂本身没有依赖关系,因此这很容易。
但是如果您选择(或必须)使用XMLInputFactory.newInstance(),则可以为“javax.xml.stream.XMLOutputFactory”和“javax.xml.stream.XMLInputFactory”定义系统属性。
那么为什么不使用JDK方法呢?多种原因:
  1. 它增加了开销:如果您没有指定系统属性,则必须扫描整个类路径,在大型应用服务器上,这需要比大多数解析长10倍到100倍
  2. 实现的优先级未定义:如果在类路径中有多个实现,哪一个将被选择?谁知道...(请注意:当您添加新的jar时,甚至可能会更改)
  3. 您很可能通过传递依赖项获得多个impl
不幸的是,Oracle似乎仍然坚持添加这种已知故障的方法来注册服务提供程序。为什么?可能是因为他们没有自己的DI库/框架(Guice由google提供,Spring由Springsource提供),并且他们倾向于控制力强。

我们在camel中使用xstream,但我没有找到一种将我的工厂注入到XStreamDataFormat的方法。我已经将代码添加到了原始帖子中。但我认为如果我将META-INF/services放在WEB-INF/classes中,它将首先在类路径中被扫描到。 - Shalom938
啊,是的。传递依赖关系有点棘手,特别是通过两个级别。但设置系统属性应该可以正常工作——而且比添加SPI元数据更快(无需类路径扫描)。 - StaxMan
1
为了完整起见,这里是Javadocs(http://docs.oracle.com/javase/7/docs/api/javax/xml/stream/XMLInputFactory.html#newFactory%28%29)的内容:“使用javax.xml.stream.XMLInputFactory系统属性。在JRE目录中使用属性文件“lib/stax.properties”。” - StaxMan

8
您可以这样指定您想要使用的XMLOutputFactory实现:
System.setProperty("javax.xml.stream.XMLOutputFactory", ... full classname You want to use ...);

来源: http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.6/tutorial/doc/SJSXP4.html

XMLInputFactory.newInstance() 方法是从JAXP继承而来的。它通过以下查找过程确定要加载的特定XMLInputFactory实现类:

  1. 使用 javax.xml.stream.XMLInputFactory 系统属性。
  2. 在JRE目录中的lib/xml.stream.properties文件中查找。
  3. 使用可用的 Services API 来查找 META-INF/services/javax.xml.stream.XMLInputFactory 文件中的类名,这些文件在可供JRE使用的jar包中。
  4. 使用平台默认的 XMLInputFactory 实例。

5
我发现,如果我将服务文件放在WEB-INF/classes/services/javax.xml.stream.XMLOutputFactory下面,那么它将首先出现在类路径中,并且在WEB-INF/lib中的jar包之前。这就是我的解决方案。

我曾经解决过类似的问题 - 我需要覆盖Jersey SpringComponentProvider。然而,这个建议对我没有用,因为META-INF/services的路径在Jersey内部是硬编码的。虽然这个建议不能被视为通用的,但我不想投反对票,因为OP是关于XMLOutputFactory的。 - Tomáš Záluský
WEB-INF/classes/services/javax.xml.stream.XMLOutputFactory 文件的内容是什么?它只是一个类名,还是其他东西? - vikingsteve
@vikingsteve 这是一个文件名,应该包含你自己实现的完整类名。 - cristianoms

5

我们遇到了类似的问题,本地解析可以运行但是在服务器上失败。通过调试发现服务器使用的阅读器是com.ctc.wstx.evt.WstxEventReader

而本地使用的阅读器是com.sun.xml.internal.stream.XMLEventReaderImpl

我们设置了以下属性来解决这个问题。

System.setProperty("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl");

1
如果你的实现是在一个jar包中,那么请确保它在类路径上的woodstox.jar之前,这样FactoryFinder就会使用你的实现。

1
问题在于如何确保它在类路径上位于Woodstox之前,特别是当您使用应用程序容器(例如Tomcat)时。 - vikingsteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接