Domanda

I'm trying to solve a common problem of logging each method that calls 3rd party operation and I don't understand how to scale it up. Current implementation:

public class ElasticsearchClient {

    RestHighLevelClient client = new RestHighLevelClient(
            RestClient.builder(
                new HttpHost("localhost", 9200, "http"),
                new HttpHost("localhost", 9201, "http")));

    public void createIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("twitter");
        client.indices().create(request, RequestOptions.DEFAULT);
    }
}

public class ElasticsearchProxy {

    ElasticsearchClient client = new ElasticsearchClient();
    Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());

    public void createIndex() throws IOException {
        logger.info("Calling to method " + this.getClass().getEnclosingMethod().getName() + " in ES");
        client.createIndex();
    }
}

As you can see now I only have 1 method - createIndex() but what if I have multiple methods - reIndex(), deleteIndex(), getIndex() and many more, for each of those methods I need to do:

    public void reIndex() throws IOException {
        logger.info("Calling to method " + this.getClass().getEnclosingMethod().getName() + " in ES");
        client.reIndex();
    }

    public void deleteIndex() throws IOException {
        logger.info("Calling to method " + this.getClass().getEnclosingMethod().getName() + " in ES");
        client.deleteIndex();
    }

    public void getIndex() throws IOException {
        logger.info("Calling to method " + this.getClass().getEnclosingMethod().getName() + " in ES");
        client.getIndex();
}

I find it to be a crazy code duplication and it feels that there must be another way to do it so:

  1. I will not repeat the logger.info(...) line
  2. I will not have to copy each method in the client class to be in the proxy class as well, I would like to have an option to add a method in the client class that will be automatically exposed in the proxy class but with the log message.

Thanks

È stato utile?

Soluzione

Use dynamic proxies.

In Java the first step is to create an interface which contains the methods you want to log and forward.

interface If {
    void originalMethod(String s);
}

Let the target class implement that interface.

class Original implements If {
    public void originalMethod(String s) {
        System.out.println(s);
    }
}

Then write an InvocationHandler which logs some text and calls the given Method on an instance of your choice (the business logic).

class Handler implements InvocationHandler {
    private final If original;

    public Handler(If original) {
        this.original = original;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        System.out.println("BEFORE");
        Object result = method.invoke(original, args);
        System.out.println("AFTER");
        return result;
    }
}

To get an object having the desired behavior you then use Proxy.newProxyInstance.

Original original = new Original();
Handler handler = new Handler(original);
If f = (If) Proxy.newProxyInstance(
                If.class.getClassLoader(),
                new Class[] { If.class },
                handler);
f.originalMethod("Hello");

Code in this example was taken (and slightly modified in order to return results from the handler which is not necessary here but in general it is) from here: https://dzone.com/articles/java-dynamic-proxy

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top