问题
I have two Java modules, A and B. A provides a core model with JAXB annotations and helper classes for creating doing JAXB stuff (creating the context, marshalling, unmarshalling, etc.) B provides additional classes that are included in the model via @XmlAnyElement(lax = true) and must therefore be added to the JAXB context.
This works fine in plain Java - B's classloader sees all the relevant classes and can instantiate the JAXB context with:
JAXBContext.newInstance(RootFromA.class, RootFromB.class)
Now I'm trying the same with OSGI (B is an Eclipse plug-in and A is the core library that will also be used by a plain Java command line module C). After much trial and error, I've managed to get A and B to see both the JAXB API and implementation via OSGI package imports. The problem is that calling newInstance as above seems to use the JAXB API's classloader, not the one of RootFromA, and certainly not the one of RootFromB. As such, it fails to even see the JAXB implementation and complains that it can't find the ContextFactory class.
I've managed to resolve this by calling a different version of newInstance:
JAXBContext.newInstance(
RootFromA.class.getPackageName()
+ ":" + RootFromB.class.getPackageName(),
RootFromB.class.getClassLoader())
I don't like this, for two reasons:
- My "client" code (B being the client of the JAXB helper stuff in A) has to manually provide a fitting classloader.
- I have to provide jaxb.index files in all referenced packages that list the context classes, even though my code is perfectly aware of them and actually gets the package name from the classes.
There probably isn't any way around (1), because only B knows the full set of classes and can decide whose classloader is able to see all of them. I'm worried though that I might run into more trouble once I add extension modules C and D that hook into B via Eclipse extension points and provide additional classes for the JAXB context - will B's classloader be able to see those?
But I'd really find a way to get rid of the static index files required for (2). The full set of context classes is dynamic and decided in plain Java by a ServiceLoader and in Eclipse by an extension point. In both cases I have direct access to the full set of classes that should belong to the context, and thus consider having to manually add jaxb.index files to each package redundant and therefore a potential and unnecessary source of error.
Am I missing something? Is there a "nicer" way to do this? And why isn't there a newInstance method that takes a set of context classes and a classloader?
回答1:
It seems I found a solution. My problem was having to deal with classloaders for two different purposes:
- The classloader used by JAXB to instantiate the context
- The classloaders of my various model classes
As described above, (1) can be specified as a parameter in JAXBContext.newInstance(), but only when specifying the model as package names instead of individual model classes. This means JAXB has to look up the classes on its own, and it the only classloader it can use to do that is (1) - which can't see all the model classes if they are spread across multiple bundles.
Another thread (Why can't JAXB find my jaxb.index when running inside Apache Felix?) taught me that JAXB defaults to using the thread context classloader to locate the context implementation. Setting that to a classloader that sees the implementation (e.g. that of my own bundle) is sufficient to make JAXB find its own implementation, which leaves me free to call my preferred version of newInstance() with an array of classes. And since these classes have already been loaded, JAXB can use them as is and doesn't have to care about their different classloaders.
In short:
Class[] myClasses = getModelClasses(); // gather all model classes, which may have different classloaders
Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(getClass().getClassLoader()); // my own bundle's classloader
JAXBContext context = JAXBContext.newInstance(myClasses);
thread.setContextClassLoader(originalClassLoader); // reset context classloader
The context classloader manipulation stuff can be wrapped in an AutoCloseable, allowing me to simply wrap the JAXBContext instantiation in a try-with-resources block.
来源:https://stackoverflow.com/questions/64979229/eclipse-osgi-java-11-jaxb-and-the-classloader