【问题标题】:@AroundInvoke interceptor is called twice on a @WebService class@AroundInvoke 拦截器在 @WebService 类上被调用两次
【发布时间】:2015-11-10 22:20:18
【问题描述】:

总结

@AroundInvoke 拦截器在@WebService 类上被调用两次, 如果拦截的方法是通过端点作为 SOAP Web 服务从应用程序的外部调用的。
如果非常相同的方法是从另一个 bean 内部调用的,那么它只会被调用 一次(正如我所料)。

被拦截的方法本身总是只调用一次!

问题一:我可以让拦截器只被调用一次吗?

问题 2:如果我不能,是否有一种可转移(独立于服务器)的方式来决定我在哪个拦截器中,所以我可以忽略多余的拦截器?

问题 3:这种行为是否常见(并在某些文档中进行了定义和描述), 还是取决于我的特定环境(JBoss EAP 6.4.0)?

观察:

  1. 这两个调用在同一个拦截器链中。
  2. 它不是拦截器类的同一个实例。
  3. InvocationContext 的实现类对于这两个调用是不同的。
  4. 有趣的是,contextData 之一,InvocationContext 沿拦截器链传递数据的字段不是HashMap 的实例,而是WrappedMessageContext,但它不包装另一个contextData 反正。

最少的可重现代码

(我删除了包名。)

MyEndpoint 接口

import javax.jws.WebService;

@WebService
public interface MyEndpoint {
    public static final String SERVICE_NAME = "MyEndpointService";
    public String getHello();
}

MyEndpointImpl 类

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 类

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();
    }
}

输出

Interceptor called
Interceptor called
MyEndpointImpl.getHello() called

更多详情

为了获取更多运行时信息,我添加了更多日志记录。

MyEndpointImpl 类

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;
    }
}

输出

    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 web-services jakarta-ee aop interceptor


    【解决方案1】:

    我无法直接回答您的问题,但也许对上下文进行一些澄清可能会对您有所帮助。

    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 实现。请参阅WarppedMessageContextApache CXF web service context。为 HTTP/SOAP 访问调用它。

    第二次调用是您在使用 RMI 时所期望的(可能在处理 SOAP 消息之后从 Apache CXF 触发)。

    为避免这种情况,您可以使用定义了拦截器的第三类进行逻辑实现。现有的 Web 服务实现类只会委托给它,不会再包含拦截器注解。

    示例代码可以看这里:OSCM Project

    【讨论】:

    • 合理的解释,我怀疑它可能是这样的。您认为这是实施中的错误吗?根据@AroundInvoke 的规范,我希望拦截器只被调用一次。
    • 我用 Glassfish 服务器测试过同样的场景,拦截器只被调用一次。但是,我的 Web 服务委托给另一个具有自己接口的 bean(不是作为 Web 服务端点接口公开的那个)。可能取决于 jax-ws 实现。我不能说它是否是一个错误,但可以搜索 JBoss 或 Apache CXF 问题并找到一些东西。
    【解决方案2】:

    我遇到了完全相同的问题并找到了解决方案。

    如果您不使用@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?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多