Pergunta

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

Foi útil?

Solução

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

Licenciado em: CC-BY-SA com atribuição
scroll top