Reputation: 220797
In my library's code, I load class names from an XML file using JAXB, in order to instanciate them later on using Class.forName()
. A fictive example to illustrate the case:
public void libraryMethod() throws Exception {
List<String> classNames = loadClassNamesFromXML();
for (String className : classNames) {
Class.forName(className).newInstance().doThings();
}
}
Now, some users use OSGi to configure their applications, and they load my library with a different class loader than the classes they configure using my XML structure. This means that loading may fail, as the class cannot be found.
Is there any more reliable way to load such classes? Or is there any other way to configure these instances? I'm open to suggestions that result in this:
public void libraryMethod() throws Exception {
// Spring does things like this:
List<SomeType> instances = loadInstancesFromXML();
for (SomeType instance : instances) {
instance.doThings();
}
}
Some constraints:
Upvotes: 5
Views: 6321
Reputation: 4788
If you want your library to be flexible and work in both OSGi and non-OSGi environment, you should allow users provide their own ClassLoader's or let them tell your library which class names they have. Read Neil Bartlett blog post.
Original link returns a 404. You can access the article on the Wayback Machine.
Upvotes: 4
Reputation: 15372
In general, in OSGi you should use a service. The reason that Class.forName/XML configuration is so popular is that only a single class gets control. To configure the rest, it needs to know the classes to initialize/call.
In OSGi this problem does not exist since each module (bundle) (can) get control through declarative services (or in the old fashioned way through an activator). So in OSGi you have a peer to peer model. Anybody can register a service and depend on other services.
So instead of specifying class names and assuming they are globally unique (they are not in large systems) it is a lot easier to use services and not leave the Java compiler; these class names are very error prone. In general this means that you often just register your service and wait to be called since there is no need to initialize your clients. However, the whiteboard pattern address the situation when you want to find out about your clients (with bndtools and bnd annotations):
The "server"
@Component
public class MyLib {
@Reference(type='*')
void addSomeType(SomeType st ) {
st.doThings();
}
}
The client
@Component
public class MyClient implements SomeType {
public void doThings() { ... }
}
Hope this helps.
Upvotes: 2
Reputation: 19606
Thanks for the explanation. Spring would not make this any easier in OSGi. You can not simply inject an implementation class from a package you do not import. In OSGi you typically use OSGi services to inject implementations that originate outside your bundle and are unknown to you at compile time.
So your user would implement an interface you specify and publish his implementation as an OSGi service. You could then either pick up all such services or let the user specify an ldap filter for his service in the xml config.
The advantage of this aproach is that you do not have to load classes and care about classloaders. So this is the recommended way in OSGi. If you want the same solution for inside and outside OSGi Ivan's aproach with specifying a classloader + classname is an alternative.
Upvotes: 2
Reputation: 9150
JDBC4 drivers include META-INF/services/java.sql.Driver in the jar which use the ServiceProvider mechanism to register the Driver implementation with the JVM (see java.util.ServiceLoader javadocs). Having the driver on the class path will register the driver automatically, obviating the need to use Class.forName. Instead app code uses ServiceLoader.load to discover registered drivers. Same mechanism can be used for other config. Perhaps something like that could be used? As an aside, when registering one's own implementations with the Service Provider mechanism, using an annotation like the spi looks pretty convenient.
Upvotes: 1