how to override a service provider in java
Asked Answered
M

5

14

This is more a general question by example: I'm using xstream and woodstox, woodstox comes with a service provider for javax.xml.stream.XMLOutputFactory in woodstox jar registering com.ctc.wstx.stax.WstxOutputFactory. I want to provide my own javax.xml.stream.XMLOutputFactory and still have woodstox jar in the classpath. I know I can provide my own with the system property javax.xml.stream.XMLOutputFactory , but I'm trying to take off the hassle from our dev ops team and do it with a service file in my jar or maybe in my war's META-INF/services folder. looking the code of javax.xml.stream.FactoryFinder how can I make sure that my META-INF/services/javax.xml.stream.XMLOutputFactory file will be the one used by FactoryFinder?

we use xstream with camel and could not find a way to inject the factory to XStreamDataFormat

Microcircuit answered 23/7, 2013 at 10:5 Comment(0)
H
10

First: instead of relying on JDK SPI interface, I strongly recommend simplifying your life and NOT using it. It really adds no value over injecting XMLInputFactory and/or XMLOutputFactory yourself. For injection you can use Guice (or Spring); or just pass it manually. Since these factories do not have dependencies of their own, this is easy.

But if choose to (or have to) use XMLInputFactory.newInstance(), you can define a System property for "javax.xml.stream.XMLOutputFactory" and "javax.xml.stream.XMLInputFactory".

So why not use JDK approach? Multiple reasons:

  1. It adds overhead: if you are not specifying System property, it will have to scan the whole classpath, and with big app servers this takes 10x-100x as long as most parsing
  2. Precedence of implementations is undefined: if you multiple in classpath, which one will you get? Who knows... (and note: it might even change when you add new jars in classpath)
  3. You are very likely to get multiple impl via transitive dependencies

Unfortunately, Oracle still seems to insist on adding this known-faulty method for registering service providers. Why? Probably because they do not have a DI lib/framework of their own (Guice is by google, Spring by Springsource), and they tend to be pretty control hungry.

Herringbone answered 23/7, 2013 at 18:8 Comment(3)
We use xstream with camel, and I did not find a way to inject my factory to XStreamDataFormat. I added the code to the original post. but I think if I put the META-INF/services in my WEB-INF/classes if will be scanned first in the classpath.Microcircuit
Ah yes. Transitive dependencies are bit nasty, esp. through two levels. But setting System properties should work fine -- and it will be faster (no class path scanning) than adding SPI metadata.Herringbone
For sake of completeness, here's what Javadocs (docs.oracle.com/javase/7/docs/api/javax/xml/stream/…) say: "Use the javax.xml.stream.XMLInputFactory system property. Use the properties file "lib/stax.properties" in the JRE directory"Herringbone
S
9

You can just do like this to specify the XMLOutputFactory implementation You want to use:

System.setProperty("javax.xml.stream.XMLOutputFactory", ... full classname You want to use ...);

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

Deriving from JAXP, the XMLInputFactory.newInstance() method determines the specific XMLInputFactory implementation class to load by using the following lookup procedure:

  1. Use the javax.xml.stream.XMLInputFactory system property.
  2. Use the lib/xml.stream.properties file in the JRE directory.
  3. Use the Services API, if available, to determine the classname by looking in the META-INF/services/javax.xml.stream.XMLInputFactory files in jars available to the JRE.
  4. Use the platform default XMLInputFactory instance.
Shalne answered 28/2, 2014 at 9:19 Comment(0)
L
6

We had similar issue where parsing would run in local but fail on server. After debugging found server is using reader com.ctc.wstx.evt.WstxEventReader

Whereas on local reader was com.sun.xml.internal.stream.XMLEventReaderImpl

We set following property to resolve it.

System.setProperty("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl");
Labaw answered 10/5, 2017 at 14:59 Comment(0)
M
5

I discovered that if I put the service file under WEB-INF/classes/services/javax.xml.stream.XMLOutputFactory then it will be first in classpath and before jars in WEB-INF/lib. and that's my solution.

Microcircuit answered 23/7, 2013 at 11:34 Comment(3)
I was solving similar problem - I needed to override Jersey SpringComponentProvider. This advice didn't work for me because the path to META-INF/services is hardcoded in Jersey internals. I don't want to downvote since OP is about XMLOutputFactory, however this advice cannot be treat as universal.Donnell
What was the contents of the WEB-INF/classes/services/javax.xml.stream.XMLOutputFactory file ? Is it just a class name, or something else?Platen
@Platen it's a file name and should contain the complete class name of your own implementation.Trifid
O
1

If your implementation is in a jar then make sure it is before woodstox.jar on the class path, then FactoryFinder will use your implementation.

Obala answered 23/7, 2013 at 10:32 Comment(1)
The problem is how can you guarantee it's before Woodstox on the classpath, particularly when you're using an app container (e.g. Tomcat).Platen

© 2022 - 2024 — McMap. All rights reserved.