【发布时间】:2020-06-16 21:33:03
【问题描述】:
在org.jboss.ejb.client.EJBClientInterceptor中处理状态的正确方法是什么?
在 Wildfly 12 中,我想为我们的通用拦截器链创建一个适配器。
让我们简化问题以创建简单的持续时间日志记录 EJB 客户端拦截器。
不幸的是,EJBClientInterceptor 2-method 的设计很奇怪:
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 地图
另一种方式是通过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 演示项目来玩
我在 docker-compose 中创建了一个包含 2 个 Wildfly 实例的演示项目。一个实例充当 ejb 客户端,另一个实例充当 ejb 服务器。有多个带有拦截器的 EJB 调用场景演示。
【问题讨论】:
-
您能否澄清
handleInvocation和handleInvocationResult是否确实会在同一个线程中?我认为他们需要这样才能使包装 EJB 调用的逻辑成功,但还没有找到表明一种或另一种方式的文档。你能确定它们是同一个线程吗?
标签: java wildfly ejb-3.0 interceptor