Как правильно обрабатывать состояние в 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