5

It seems like everybody has had an unpleasant brush with the Java Service Provider, that thing you can do with a file named like META-INF/services/com.example.Interface, but that nobody uses except for trying to load the right XML parser. I'm trying to work with a library that uses the Service Provider API, and trick it so that I can provide some runtime-extended classes (using cglib) that don't actually implement the interface but can be made to do so easily.

Basically, I think the steps I need to perform are:

  1. Create a custom class loader that will respond to getResources(...) and return an "extra" URL
  2. Also have that class loader hook getResourceAsStream(...) to return a list of the classes I am going to manipulate with cglib, when asked for the "extra" resource
  3. Finally, have that class loader load those classes when requested

But here's where I get lost. For example, when the library tries to determine what implementers are out there, it calls getResources(...) which returns a bunch of URLs. But getResourceAsStream(...) doesn't take URLs, it takes "names". Names which seem to be classpath-relative, and therefore the same everywhere. So META-INF/services/com.example.Interface in has the same "name" as META-INF/services/com.example.Interface in their JAR, right? Except somehow this works with those blasted XML parsers...

Of course, all of this assumes they were smart/kind enough to call ClassLoader.getSystemClassLoader() rather than using ClassLoader.getSystemResources(...), ClassLoader.getSystemResourceAsStream(...), etc., as in the latter case there's no way to hook the ClassLoader and provide the faked file.

I guess in that case I could use BCEL to manipulate the class files when my code is being packaged by Maven, rather than waiting until runtime to do it with cglib?

jonathan-stafford
  • 11,647
  • 1
  • 17
  • 11
  • Could you compile some stubs and register them via META-INF/services in the usual way, but then proxy them with cglib once they're loaded? – OrangeDog Dec 21 '10 at 14:30
  • What's the actual question/problem? Your approach of using a custom class loader (and setting it as the context class loader around the call to the library that calls getResources...) should work. – Brett Kail Dec 25 '10 at 04:41
  • bkail - The ultimate problem is that I've got classes (`Type1`, `Type2`, ...) that can be run by another class (`Runner`), which are themselves run inside a GUI. For various reasons, this is ugly in my application. What I'd like to do is dynamically "fuse" them together and have `Runner$Type1`, `Runner$Type2`, etc. presented to the GUI, so that users are aware of what classes are available. – jonathan-stafford Dec 27 '10 at 11:28

1 Answers1

3

The idea I described was along the right track. The mistake I made was in thinking that using ClassLoader.getResourceAsStream(..) to access the contents of the URLs. Instead, you should just URL.openStream().

Had I found it before posting, java.util.ServiceLoader (@since 1.6) provides some insight into how to do things correctly.

jonathan-stafford
  • 11,647
  • 1
  • 17
  • 11