Как управлять состоянием в EJBClientInterceptor - PullRequest
2 голосов
/ 04 марта 2020

Как правильно обрабатывать состояние в org.jboss.ejb.client.EJBClientInterceptor?

В Wildfly 12 я хотел бы создать адаптер для наших общих цепочек перехватчиков. Давайте упростим задачу для создания простой регистрации продолжительности EJB-клиента-перехватчика. К сожалению, дизайн EJBClientInterceptor 2-метода странный:

public interface EJBClientInterceptor {

    /**
     * Handle the invocation.  Implementations may short-circuit the invocation by throwing an exception.  This method
     * should process any per-interceptor state and call {@link EJBClientInvocationContext#sendRequest()}.
     *
     * @param context the invocation context
     * @throws Exception if an invocation error occurs
     */
    void handleInvocation(EJBClientInvocationContext context) throws Exception;

    /**
     * Handle the invocation result.  The implementation should generally call {@link EJBClientInvocationContext#getResult()}
     * immediately and perform any post-invocation cleanup task in a finally block.
     *
     * @param context the invocation context
     * @return the invocation result, if any
     * @throws Exception if an invocation error occurred
     */
    Object handleInvocationResult(EJBClientInvocationContext context) throws Exception;
}

Проблема в том, что, поскольку перехватчик разделен на 2 метода, вы должны перенести состояние из части «до» (handleInvocation) в часть "после" (handleInvocationResult).

Обзор потока

    @Override
    public void handleInvocation(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        System.out.println("handleInvocation - before sendRequest");
        ejbClientInvocationContext.sendRequest();
        System.out.println("handleInvocation - after sendRequest");
    }

    @Override
    public Object handleInvocationResult(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        System.out.println("handleInvocationResult - before getResult");
        try {
            return ejbClientInvocationContext.getResult();
        } finally {
            System.out.println("handleInvocationResult - after getResult");
        }
    }

Результаты в этом выводе

client_1  | 16:50:24,575 INFO  [stdout] (default task-1) handleInvocation - before sendRequest
client_1  | 16:50:24,718 INFO  [stdout] (default task-1) handleInvocation - after sendRequest
server_1  | 16:50:25,737 INFO  [stdout] (default task-2) Doing work at EJB server
client_1  | 16:50:25,771 INFO  [stdout] (default task-1) handleInvocationResult - before getResult
client_1  | 16:50:25,795 INFO  [stdout] (default task-1) handleInvocationResult - after getResult

Плохое решение # 1 - поля экземпляра

Другая проблема заключается в том, что экземпляр перехватчика является одноэлементным и используется повторно для всех вызовов. Таким образом, вы не можете использовать поля в перехватчике, как это

public class DurationLogging1ClientInterceptor implements EJBClientInterceptor {

    private long startTime;

    @Override
    public void handleInvocation(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        startTime = System.currentTimeMillis();
        ejbClientInvocationContext.sendRequest();
    }

    @Override
    public Object handleInvocationResult(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        try {
            return ejbClientInvocationContext.getResult();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            System.out.println("Call took " + duration + "ms");
        }
    }
}

Плохое решение # 2 - ThreadLocal

Другой способ - использовать ThreadLocal:

public class DurationLogging2ClientInterceptor implements EJBClientInterceptor {

    private final ThreadLocal<Long> startTimeTL = new ThreadLocal<>();

    @Override
    public void handleInvocation(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        startTimeTL.set(System.currentTimeMillis());
        ejbClientInvocationContext.sendRequest();
    }

    @Override
    public Object handleInvocationResult(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        try {
            return ejbClientInvocationContext.getResult();
        } finally {
            long startTime = startTimeTL.get();
            long duration = System.currentTimeMillis() - startTime;
            System.out.println("Call took " + duration + "ms");
        }
    }
}

Но я не уверен, гарантированно ли будет вызван метод handleInvocationResult в том же потоке, что и handleInvocation. И даже если это так, мне не нравится использование ThreadLocal.

Плохое решение № 3 - Использование contextData map

Другой способ - передать состояние через EJBClientInvocationContext параметр, возможно, используя свойство getContextData():

public class DurationLogging3ClientInterceptor implements EJBClientInterceptor {

    @Override
    public void handleInvocation(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        ejbClientInvocationContext.getContextData().put("startTime", System.currentTimeMillis());
        ejbClientInvocationContext.sendRequest();
    }

    @Override
    public Object handleInvocationResult(EJBClientInvocationContext ejbClientInvocationContext) throws Exception {
        try {
            return ejbClientInvocationContext.getResult();
        } finally {
            long startTime = (Long) ejbClientInvocationContext.getContextData().get("startTime");
            long duration = System.currentTimeMillis() - startTime;
            System.out.println("Call took " + duration + "ms");
        }
    }
}

Но эта карта contextData сериализуется и отправляется на EJB-сервер, чего я не хочу.

Предыдущий Jboss Дизайн перехватчика

В предыдущих версиях Jboss был намного более простой дизайн org.jboss.aop.advice.Interceptor, который решал все эти проблемы:

public interface Interceptor {
    String getName();

    Object invoke(Invocation invocation) throws Throwable;
}

Перехватчик регистрации продолжительности можно записать так:

public class DurationLogging4ClientInterceptor implements Interceptor {

    @Override
    public String getName() {
        return getClass().getName();
    }

    @Override
    public Object invoke(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();

        try {
            return invocation.invokeNext();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            System.out.println("Call took " + duration + "ms");
        }
    }
}

Dockerized демонстрационный проект для игры с

Я создал демонстрационный проект с 2 экземплярами Wildfly внутри docker-compose. Один экземпляр действует как ejb-клиент, а другой - как ejb-сервер. Существует несколько сценариев вызова EJB с перехватчиками.

https://github.com/petr-ujezdsky/w2w

...