Search code examples
javainterfacestaticguava

How to use guava class to instance map in interface?


I have interface called RestClientInterface which is implemented by abstract RestClient class, those class is extended by SearchRestClient and IndexRestClient. Both of those classes are singletons. I would like to be able implement in the interface static method getInstance. I decided to use Guava library like suggested here: How can I implement abstract static methods in Java?.

I have implemented two statics methods in interface:

public interface RestClientInterface {

    ClassToInstanceMap<RestClientInterface> classToInstanceMap = MutableClassToInstanceMap.create();

    static <T extends RestClientInterface> T getInstance(Class<T> type) {
        return classToInstanceMap.getInstance(type);
    }

    static <T extends RestClientInterface> void registerInstance(Class<T> type, T identity) {
        classToInstanceMap.putIfAbsent(type, identity);
    }
}

And next registered instances in both extending classes:

public class IndexRestClient extends RestClient {

    static {
        RestClientInterface.registerInstance(IndexRestClient.class, getInstance());
    }

    /**
     * IndexRestClient singleton.
     */
    private static IndexRestClient instance = null;

    private IndexRestClient() {}

    /**
     * Return singleton variable of type IndexRestClient.
     *
     * @return unique instance of IndexRestClient
     */
    private static IndexRestClient getInstance() {
        if (Objects.equals(instance, null)) {
            synchronized (IndexRestClient.class) {
                if (Objects.equals(instance, null)) {
                    instance = new IndexRestClient();
                    instance.initiateClient();
                }
            }
        }
        return instance;
   } 
}

Next I call this like that:

IndexRestClient restClient = RestClientInterface.getInstance(IndexRestClient.class);

But every time I get null. Static registration of the instances doesn't work as the array of registered instances is empty. How can I correctly instantiate those both classes?


Solution

  • Following our discussion, the problem is probably related to the lazy loading of the class IndexRestClient if it is solely referenced in the statement RestClientInterface.getInstance(IndexRestClient.class) before further use, because I am not sure the reference to IndexRestClient.class is enough to trigger the load/initialization of the class.

    Assuming the problem here is indeed about the class IndexRestClient being lazily loaded, you would need to invert the control of the registration logic, i.e. instead of the IndexRestClient registering itself with the registry, have a "Registrar" in the middle which takes care of it.

    However it seems to me that the contract of your RestClientInterface should be changed since it is not agnostic of the concrete types of your Rest clients while I understand you want to hide the implementation of their creation.

    You may want to have a look at the Java service loader mechanism which seems close to what you want to do, e.g.:

    public final RestClients {
        private final ServiceLoader<RestClient> restClients = ServiceLoader.load(RestClient.class);
    
        public RestClient getClient(RestClientSpec spec) throws NoClientFoundForSpecException {
            for (RestClient client : restClients) {
                if (/* client matches your specification */) {
                    return client;
                }
            }
            throw new NoClientFoundForSpecException(spec);
        }
    }
    

    The Service Loader pattern may provide more encapsulation and hide more of the implementation than you actually want (since you are apparently using concrete types), yet I felt it was worth mentioning.