Перехватчик @AroundInvoke вызывается дважды в классе @WebService

Резюме

@AroundInvoke перехватчик вызывается дважды в классе @WebService, если перехваченный метод вызывается вне приложения через конечную точку в качестве веб-службы SOAP.
Если тот же метод вызывается внутренне из другого компонента, он вызывается только один раз (как я и ожидал).

Сам перехватываемый метод всегда вызывается только один раз!

Вопрос 1: можно ли сделать так, чтобы перехватчик вызывался только один раз?

Вопрос 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

person Honza Zidek    schedule 10.11.2015    source источник


Ответы (2)


Я не могу ответить на ваши вопросы напрямую, но, возможно, вам могут помочь некоторые пояснения по поводу контекстов.

Реализация Java EE JAX-WS варьируется от сервера к серверу. Например, Glassfish использует Metro, а JBoss использует Apache CXF.

Существуют разные виды цепочек перехватчиков, которые позволяют программно управлять условиями до и после обработки запроса / ответа.

Перехватчиками для вызовов веб-службы SOAP являются обработчики SOAP и логические обработчики (см. Документация Oracle). Оба могут получить доступ к сообщению SOAP на разных уровнях (целиком или только к полезной нагрузке).

Я предполагаю, что ваш перехватчик вызывался дважды: один раз для доступа через HTTP / SOAP и один раз для доступа через RMI.

При первом вызове перехватчика в качестве контекста вы видите org.apache.cxf.jaxws.context.WrappedMessageContext, который является реализацией карты. См. WarppedMessageContext, контекст веб-службы Apache CXF. Он вызывается для доступа по протоколу HTTP / SOAP.

Второй вызов - это то, что вы ожидаете при использовании RMI (вероятно, запускается из Apache CXF после обработки сообщения SOAP).

Чтобы избежать этого, вы можете использовать третий класс для логической реализации с определенным перехватчиком. Существующий класс реализации веб-службы будет только делегировать ему и больше не будет содержать аннотацию перехватчика.

Пример кода можно увидеть здесь: Проект OSCM

person S.Stavreva    schedule 11.04.2016
comment
Правдоподобное объяснение, я подозревал, что это могло быть что-то в этом роде. Как вы думаете, это ошибка в реализации? Исходя из спецификации @AroundInvoke, я ожидал, что перехватчик будет вызываться только один раз. - person Honza Zidek; 12.04.2016
comment
Я тестировал тот же сценарий с сервером Glassfish, перехватчик вызывается только один раз. Однако мой веб-сервис делегирует другому компоненту, который имеет собственный интерфейс (не тот, который отображается как интерфейс конечной точки веб-службы). Вероятно, зависит от реализации jax-ws. Я не могу сказать, является ли это ошибкой, но могу поискать проблемы JBoss или Apache CXF и что-то найти. - person S.Stavreva; 12.04.2016

У меня была такая же проблема, и я нашел решение.

Если вместо использования привязки стиля @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?

person TueCN    schedule 22.12.2017