在@WebService类

时间:2015-11-10 22:20:19

标签: java web-services java-ee aop interceptor

摘要

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

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

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

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

问题3 :这种行为是否常见(并在某些文档中定义和描述), 或者它是否依赖于我的特定环境(JBoss EAP 6.4.0)?

观察:

  1. 这两个调用在同一个拦截链中不是
  2. 它与拦截器类的实例不同。
  3. InvocationContext的实现类对于这两个调用都是不同的。
  4. 有趣的是contextDataInvocationContext的字段之一用于沿拦截器链传递数据,它不是HashMap的实例,但WrappedMessageContext,但它无论如何也不会包装其他contextData
  5. 最小可重现代码

    (我删除了包名。)

    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
    

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

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

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