Search code examples
javavert.xvertx-verticle

In vert.x, why do static methods run before static code blocks?


I have a class ConfigFactory, it can give me some configuration from JSON files by vert.x conf modules.

public class ConfigFactory {
    private static JsonObject result = new JsonObject();
    static {
        ConfigStoreOptions fileStore = new ConfigStoreOptions()
                .setType("file")
                .setOptional(true)
                .setFormat("json")
                .setConfig(new JsonObject().put("path", "conf/config.json"));
        ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore);
        ConfigRetriever retriever = ConfigRetriever.create(VertxSingleton.VERTX, options);
        retriever.getConfig(ar -> {
            if (ar.failed()) {
                throw new RuntimeException("Config get error! Something went wring during getting the config");
            } else {
                result.mergeIn(ar.result());
            }
        });
    }

    public static JsonObject getHttpConfig() {
        BiFunction<Integer, String, JsonObject> httpConfigFile = (port, host) -> new JsonObject()
                .put("port", port).put("host", host);
        if (!result.isEmpty()) {
            JsonObject http = result.getJsonObject("http");
            return httpConfigFile.apply(http.getInteger("port"), http.getString("host"));
        } else {
            throw new RuntimeException("HTTP Config get error! Something went wring during getting the config");
        }

    }
}

But in Verticle,I use JsonObject httpConfig = ConfigFactory.getHttpConfig();,it will give me exception HTTP Config get error! Something went wring during getting the config.In this time, the result was empty.

I found the static methods getHttpConfig run before static code blocks. About one second, the static code blocks will run. At that time, the result was not empty.

Can you help me? Thanks!


Solution

  • It's not so much vertX related question - it is more a design issue. The static block is executed when the class is loaded by the class loader it will basically happen when the class is first referenced. If the first time you reference it is when you use ConfigFactory.getHttpConfig(); then the class is loaded by the class loader, the static block is executed and the retrieveConfig is called with a handler. Unfortunatelly it is not blocking the execution (the handler waits for the result) and you call the class right away. So in to make it work properly you should call getHttpConfig() after it has finished initializing.

    • A solution that will make the static block synchronous is to use the Future option.

    For example:

    Future<JsonObject> futureJson=ConfigRetriever.getConfigAsFuture(retriever);
    JsonObject obj=futureJson.get();
    

    when future.get() is called in the static block it will wait until the config is retrieved before continuing. It might seem like a easier to comprehend solution but I wouldn't use it. It's made asynchronous for a reason and we shouldn't change that behavior.

    • Another solution which might require a bit more coding would be to add a static field that shows if it is in working state.

    For example:

    private static JsonObject result = new JsonObject();
    private static boolean readyToGo;
        static {       
            retriever.getConfig(ar -> {
               ....
                if (ar.failed()) {
                 ...
                } else {
                    result.mergeIn(ar.result());
                    ConfigFactory.readyToGo=true; //Now we are good to go
                }
            });
        }
    

    Then

       public static JsonObject getHttpConfig() {
          if (!readyToGo) throw Exception
        }
    

    Add one isReady() method and when you are calling the getHttpConfig() method you will first call isReady(). Something like:

    while(!ConfigFactory.isReady()){
    //wait ;)
    }
    ConfigFactory.getHttpConfig(); //it will work here
    

    I guess you can write it better ;) But the general idea is to maintain a state if the object is ready to use or not. Having that static configuration executed doesn't guarantee the object is in ready state so you should manage that state by yourself.

    • And the easy solution would be to call the class once upon startup so it can get initialized (classloaded) and then call the getHttpConfig() later. It will not guarantee 100% result though if the retriever takes too much time or even if it never completes retrieving the configuration.