I'm trying to determine the best way to create a new instance of a class based on which classes are available on the classpath at runtime.
For example, I have a library that requires a JSON response to be parsed in multiple classes. The library has the following interface:
JsonParser.java
:
public interface JsonParser {
<T> T fromJson(String json, Class<T> type);
<T> String toJson(T object);
}
This class has multiple implementations, i.e. GsonJsonParser
, JacksonJsonParser
, Jackson2JsonParser
, and currently, the user of the library is required to "pick" their implementation to be used based on which library they've included in their project. For example:
JsonParser parser = new GsonJsonParser();
SomeService service = new SomeService(parser);
What I'd like to do, is dynamically pick up which library is on the classpath, and create the proper instance, so that the user of the library doesn't have to think about it (or even have to know the internal implementation of another class parses JSON).
I'm considering something similar to the following:
try {
Class.forName("com.google.gson.Gson");
return new GsonJsonParser();
} catch (ClassNotFoundException e) {
// Gson isn't on classpath, try next implementation
}
try {
Class.forName("com.fasterxml.jackson.databind.ObjectMapper");
return new Jackson2JsonParser();
} catch (ClassNotFoundException e) {
// Jackson 2 was not found, try next implementation
}
// repeated for all implementations
throw new IllegalStateException("You must include either Gson or Jackson on your classpath to utilize this library");
Would this be an appropriate solution? It seems kind of like a hack, as well as uses exceptions to control the flow.
Is there a better way to do this?
Essentially you want to create your own JsonParserFactory
. We can see how it's implemented in the Spring Boot framework:
public static JsonParser getJsonParser() {
if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
return new JacksonJsonParser();
}
if (ClassUtils.isPresent("com.google.gson.Gson", null)) {
return new GsonJsonParser();
}
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
return new YamlJsonParser();
}
return new BasicJsonParser();
}
So your approach is nearly the same as this, except for the use of the ClassUtils.isPresent
method.
This sounds like a perfect case for the Service Provider Interface (SPI) pattern. Check out the java.util.ServiceLoader
documentation for an example of how to implement it.
If only one of the implementations (GsonJsonParser
, JacksonJsonParser
, Jackson2JsonParser
) would be present at runtime and there is no other option, then you'd have to use Class.forName()
.
Although you can handle it a smarter.
For example, you can put all the classes into a Set<String>
and then loop over them. If any one of them throws exception, you can just continue, and the one which does not, you can do your operations.
Yes, it is a hack, and your code would become library dependent. If there could be any chance that you can include all three implementations of your JsonParsers in your classpath and use a logic to define which implementation you have to use; that would be a much better approach.
If this is not possible, you can continue with above.
Also, instead of using plain Class.forName(String name)
, you can use a better option Class.forName(String name, boolean initialize, ClassLoader loader)
which will NOT run any static initializers (if present in your class).
Where initialize = false
and loader = [class].getClass().getClassLoader()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With