Casey Casey - 6 months ago 23
Java Question

Create implementation of Java class dynamically based on provided dependencies at runtime

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?

Answer

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.