@AroundInvoke拦截器在@WebService类上被调用两次
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了@AroundInvoke拦截器在@WebService类上被调用两次相关的知识,希望对你有一定的参考价值。
Summary
如果截取的方法是通过端点作为SOAP Web服务从应用程序外部调用的,则@AroundInvoke
拦截器在@WebService
类上调用两次。
如果从另一个bean内部调用相同的方法,它只被调用一次(正如我所料)。
截获的方法本身总是只被调用一次!
问题1:我可以只拦截一次拦截器吗?
问题2:如果我不能,是否有可转移的(服务器独立的)方式来决定我在哪个拦截器中,那么我可以忽略多余的拦截器?
问题3:这种行为是否常见(并在某些文档中定义和描述),还是依赖于我的特定环境(JBoss EAP 6.4.0)?
观察:
- 这两个调用不在同一个拦截链中。
- 它与拦截器类的实例不同。
InvocationContext
的实现类对于这两个调用都是不同的。- 有趣的是,其中一个
contextData
,InvocationContext
的领域,用于沿着拦截链传递数据,不是HashMap
的一个实例,而是WrappedMessageContext
,但它无论如何都不包裹其他的contextData
。
Minimal reproducible code
(我删除了包名。)
MyEndpoint interface
import javax.jws.WebService;
@WebService
public interface MyEndpoint {
public static final String SERVICE_NAME = "MyEndpointService";
public String getHello();
}
MyEndpointImpl class
import javax.interceptor.Interceptors;
import javax.jws.WebService;
@WebService(endpointInterface = "MyEndpoint", serviceName = MyEndpoint.SERVICE_NAME)
@Interceptors({TestInterceptor.class})
public class MyEndpointImpl implements MyEndpoint {
@Override
public String getHello() {
System.out.println("MyEndpointImpl.getHello() called");
return "Hello";
}
}
TestInterceptor class
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class TestInterceptor {
@AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
System.out.println("Interceptor called");
return ic.proceed();
}
}
Output
Interceptor called
Interceptor called
MyEndpointImpl.getHello() called
More details
为了获得更多运行时信息,我添加了更多日志记录
MyEndpointImpl class
import java.lang.reflect.Method;
import java.util.Map;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestInterceptor {
private static Logger logger = LoggerFactory.getLogger(TestInterceptor.class);
private static int callCnt = 0;
@AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
final String interceptorClass = this.toString();
final String invocationContextClass = ic.getClass().getName();
final Method method = ic.getMethod();
final String calledClass = method.getDeclaringClass().getName();
final String calledName = method.getName();
final String message = String.format(
"%n INTERCEPTOR: %s%n InvocationContext: %s%n %s # %s()",
interceptorClass, invocationContextClass, calledClass, calledName);
logger.info(message);
final int call = ++callCnt;
final Map<String, Object> contextData = ic.getContextData();
contextData.put("whoami", call);
logger.info("BEFORE PROCEED {}, {}", call, contextData);
final Object ret = ic.proceed();
logger.info("AFTER PROCEED {}, {}", call, contextData);
return ret;
}
}
Output
INTERCEPTOR: TestInterceptor@74c90b72
InvocationContext: org.jboss.invocation.InterceptorContext$Invocation
MyEndpointImpl # getHello()
BEFORE PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext@2cfccb1d
INTERCEPTOR: TestInterceptor@5226f6d8
InvocationContext: org.jboss.weld.interceptor.proxy.InterceptorInvocationContext
MyEndpointImpl # getHello()
BEFORE PROCEED 2, {whoami=2}
MyEndpointImpl.getHello() called
AFTER PROCEED 2, {whoami=2}
AFTER PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext@2cfccb1d
我无法直接回答您的问题,但也许有关上下文的澄清可能会对您有所帮助。
Java EE JAX-WS实现因服务器而异。例如,Glassfish使用Metro,JBoss使用Apache CXF。
存在不同类型的拦截器链,其允许以编程方式控制请求/响应处理之前和之后的条件。
SOAP Web服务调用的拦截器是SOAP处理程序和逻辑处理程序(请参阅Oracle documentation)。两者都可以在不同级别(整个或仅有效负载)上访问SOAP消息。
我的假设是你的拦截器调用两次,一次用于通过HTTP / SOAP访问,一次用于通过RMI访问。
在第一个拦截器调用中,您看到的上下文是org.apache.cxf.jaxws.context.WrappedMessageContext,它是一个Map实现。见WarppedMessageContext,Apache CXF web service context。它被调用用于HTTP / SOAP访问。
第二次调用是您在使用RMI时所期望的(可能是在处理SOAP消息后从Apache CXF触发)。
为了避免这种情况,您可以使用第三类进行逻辑实现,并定义拦截器。现有的Web服务实现类只会委托给它,并且不再包含拦截器注释。
示例代码可以在这里看到:OSCM Project
我有完全相同的问题,并找到了解决方案。
如果您不使用@Interceptors样式绑定而使用@InterceptorBinding样式绑定,那么拦截器仅被实例化并调用一次(至少在我的情况下在WildFly 10.1.0.Final上)。
这是你的示例使用@InterceptorBinding样式的样子。
您的自定义拦截器绑定注释:
import javax.interceptor.InterceptorBinding;
...
@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface MyInterceptorBinding {
你的终点:
@WebService(endpointInterface = "MyEndpoint", serviceName =
MyEndpoint.SERVICE_NAME)
@MyInterceptorBinding
public class MyEndpointImpl implements MyEndpoint {
你的拦截器:
import javax.interceptor.Interceptor;
import javax.annotation.Priority;
...
@Interceptor
@MyInterceptorBinding
@Priority(Interceptor.Priority.APPLICATION) //we use @Priority to enable this interceptor application-wide, without having to use beans.xml in every module.
public class TestInterceptor {
@AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
System.out.println("Interceptor called");
return ic.proceed();
}
我从来没有弄清楚究竟是什么问题,但我怀疑@Interceptors样式绑定对多种类型的拦截器(EJB和CDI)有效,而@InterceptorBinding样式可能仅对CDI拦截器有效。也许JAX-WS @WebService既是EJB又是CDI bean?
以上是关于@AroundInvoke拦截器在@WebService类上被调用两次的主要内容,如果未能解决你的问题,请参考以下文章