2012-05-15 25 views
17

Como la inicialización del servicio del cliente WS y el puerto tardan años, me gusta inicializarlos una vez al inicio y volver a usar la misma instancia del puerto. Initalization sería algo como esto:¿Es seguro este subproceso de llamada de cliente JAX-WS?

private static RequestContext requestContext = null; 

static 
{ 
    MyService service = new MyService(); 
    MyPort myPort = service.getMyServicePort(); 

    Map<String, Object> requestContextMap = ((BindingProvider) myPort).getRequestContext(); 
    requestContextMap = ((BindingProvider)myPort).getRequestContext(); 
    requestContextMap.put(BindingProvider.USERNAME_PROPERTY, uName); 
    requestContextMap.put(BindingProvider.PASSWORD_PROPERTY, pWord); 

    rc = new RequestContext(); 
    rc.setApplication("test"); 
    rc.setUserId("test"); 
} 

La llamada en algún lugar de mi clase:

myPort.someFunctionCall(requestContext, "someValue"); 

Mi pregunta: ¿Será esta llamada ser flujos seguros?

Jonny

+3

ya ha sido contestada aquí: http://stackoverflow.com/questions/4385204/are-jax-ws-clients-thread-safe – kyiu

+0

Hola KHY , Gracias por su respuesta rápida. Vi este hilo. Mi problema es que no tengo ninguna declaración (oficial) que sea segura o no (service/port/etc.). Mi uso también difiere del otro hilo. Jonny – user871611

+1

Aquí una respuesta que encontré en el sitio web de CXF: https://cwiki.apache.org/CXF/faq.html#FAQ-AreJAXWSclientproxiesthreadsafe%253F – kyiu

Respuesta

19

De acuerdo con la CXF FAQ:

son sustitutos cliente JAX-WS hilo de seguridad?

Oficial JAX-WS respuesta: Nº De acuerdo con la especificación JAX-WS, los proxies cliente no es seguro para subprocesos. Para escribir código portátil, debe tratarlos como no seguro para subprocesos y sincronizar el acceso o usar un conjunto de instancias o similar.

CXF respuesta: Los proxies CXF son seguros para rosca para MUCHOS casos de uso. Las excepciones son :

  • El uso de ((BindingProvider)proxy).getRequestContext() - acuerdo con las especificaciones JAX-WS, el contexto de la petición es por instancia. Por lo tanto, cualquier cosa establecida allí afectará las solicitudes en otros hilos. Con CXF, que puede hacer:

    ((BindingProvider)proxy).getRequestContext().put("thread.local.request.context","true"); 
    

    y futuras llamadas a getRequestContext() utilizará un contexto de la petición local de hilo. Eso permite que el contexto de solicitud sea threadsafe. (Nota: el contexto de respuesta es siempre el hilo local en CXF)

  • Configuración en el conducto - si se utiliza código o configuración para manipular directamente el conducto (como para establecer la configuración de TLS o similar), los no se enrosque seguro. El conducto es por instancia y, por lo tanto, esas configuraciones se compartirían. Además, si usa FailoverFeature y LoadBalanceFeatures, el conducto se reemplaza sobre la marcha. Por lo tanto, las configuraciones establecidas en el conducto podrían perderse antes de ser utilizadas en el hilo de configuración .

  • Soporte de sesión: si activa el soporte de sesiones (consulte especificaciones jaxws), la cookie de sesión se almacena en el conducto. Por lo tanto, estaría dentro de las reglas anteriores en la configuración del conducto y, por lo tanto, se compartirá entre subprocesos.
  • Tokens de seguridad WS: si utiliza WS-TrustConversation o WS-Trust, el token recuperado se guarda en caché en el Endpoint/Proxy para evitar las llamadas adicionales (y costosas) al STS para obtener tokens . Por lo tanto, varios hilos compartirán el token. Si cada subproceso tiene diferentes credenciales de seguridad o requisitos, debe utilizar instancias de proxy independientes.

Para los problemas de conducción, PODRÍA instalar un nuevo ConduitSelector que utiliza un hilo local o similar. Eso es un poco complejo sin embargo.

Para la mayoría de los casos de uso "simple", puede usar proxies CXF en múltiples hilos . Lo anterior describe las soluciones para los demás.

3

En general, no.

Según el FAQ CXF http://cxf.apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe?

Oficial JAX-WS respuesta: No. De acuerdo con la especificación JAX-WS, el cliente proxies no es seguro para subprocesos. Para escribir código portátil, debe tratar como no seguro para subprocesos y sincronizar el acceso o utilizar un conjunto de instancias o similar.

CXF respuesta: Los proxies CXF son seguros para rosca para MUCHOS casos de uso.

Para obtener una lista de excepciones, consulte las preguntas frecuentes.

3

Como puede ver en las respuestas anteriores, los proxies de los clientes JAX-WS no son seguros para subprocesos, por lo que solo quería compartir mi implementación para que otros puedan almacenar en caché los proxies del cliente. En realidad, me enfrenté al mismo problema y decidí crear un bean de resorte que almacena en caché los proxies de JAX-WS Client. Se puede ver más detalles http://programtalk.com/java/using-spring-and-scheduler-to-store/

import java.util.Map; 
import java.util.concurrent.ConcurrentHashMap; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ScheduledExecutorService; 
import java.util.concurrent.TimeUnit; 

import javax.annotation.PostConstruct; 

import org.apache.commons.lang3.concurrent.BasicThreadFactory; 
import org.apache.logging.log4j.Logger; 
import org.springframework.stereotype.Component; 

/** 
* This keeps the cache of MAX_CUNCURRENT_THREADS number of 
* appConnections and tries to shares them equally amongst the threads. All the 
* connections are created right at the start and if an error occurs then the 
* cache is created again. 
* 
*/ 
/* 
* 
* Are JAX-WS client proxies thread safe? <br/> According to the JAX-WS spec, 
* the client proxies are NOT thread safe. To write portable code, you should 
* treat them as non-thread safe and synchronize access or use a pool of 
* instances or similar. 
* 
*/ 
@Component 
public class AppConnectionCache { 

private static final Logger logger = org.apache.logging.log4j.LogManager.getLogger(AppConnectionCache.class); 

private final Map<Integer, MyService> connectionCache = new ConcurrentHashMap<Integer, MyService>(); 

private int cachedConnectionId = 1; 

private static final int MAX_CUNCURRENT_THREADS = 20; 

private ScheduledExecutorService scheduler; 

private boolean forceRecaching = true; // first time cache 

@PostConstruct 
public void init() { 
    logger.info("starting appConnectionCache"); 
    logger.info("start caching connections"); ;; 
    BasicThreadFactory factory = new BasicThreadFactory.Builder() 
    .namingPattern("appconnectioncache-scheduler-thread-%d").build(); 
    scheduler = Executors.newScheduledThreadPool(1, factory); 

    scheduler.scheduleAtFixedRate(new Runnable() { 
    @Override 
    public void run() { 
    initializeCache(); 
    } 

    }, 0, 10, TimeUnit.MINUTES); 

} 

public void destroy() { 
    scheduler.shutdownNow(); 
} 

private void initializeCache() { 
    if (!forceRecaching) { 
    return; 
    } 
    try { 
    loadCache(); 
    forceRecaching = false; // this flag is used for initializing 
    logger.info("connections creation finished successfully!"); 
    } catch (MyAppException e) { 
    logger.error("error while initializing the cache"); 
    } 
} 

private void loadCache() throws MyAppException { 
    logger.info("create and cache appservice connections"); 
    for (int i = 0; i < MAX_CUNCURRENT_THREADS; i++) { 
    tryConnect(i, true); 
    } 
} 

public MyPort getMyPort() throws MyAppException { 
    if (cachedConnectionId++ == MAX_CUNCURRENT_THREADS) { 
    cachedConnectionId = 1; 
    } 
    return tryConnect(cachedConnectionId, forceRecaching); 
} 

private MyPort tryConnect(int threadNum, boolean forceConnect) throws MyAppException { 
    boolean connect = true; 
    int tryNum = 0; 
    MyPort app = null; 
    while (connect && !Thread.currentThread().isInterrupted()) { 
    try { 
    app = doConnect(threadNum, forceConnect); 
    connect = false; 
    } catch (Exception e) { 
    tryNum = tryReconnect(tryNum, e); 
    } 
    } 
    return app; 
} 

private int tryReconnect(int tryNum, Exception e) throws MyAppException { 
    logger.warn(Thread.currentThread().getName() + " appservice service not available! : " + e); 
    // try 10 times, if 
    if (tryNum++ < 10) { 
    try { 
    logger.warn(Thread.currentThread().getName() + " wait 1 second"); 
    Thread.sleep(1000); 
    } catch (InterruptedException f) { 
    // restore interrupt 
    Thread.currentThread().interrupt(); 
    } 
    } else { 
    logger.warn(" appservice could not connect, number of times tried: " + (tryNum - 1)); 
    this.forceRecaching = true; 
    throw new MyAppException(e); 
    } 
    logger.info(" try reconnect number: " + tryNum); 
    return tryNum; 
} 

private MyPort doConnect(int threadNum, boolean forceConnect) throws InterruptedException { 
    MyService service = connectionCache.get(threadNum); 
    if (service == null || forceConnect) { 
    logger.info("app service connects : " + (threadNum + 1)); 
    service = new MyService(); 
    connectionCache.put(threadNum, service); 
    logger.info("connect done for " + (threadNum + 1)); 
    } 
    return service.getAppPort(); 
} 
} 
0

Una solución general para esto es utilizar múltiples objetos de cliente en una piscina, a continuación, utilizar proxy que actúa como una fachada.

import org.apache.commons.pool2.BasePooledObjectFactory; 
import org.apache.commons.pool2.PooledObject; 
import org.apache.commons.pool2.impl.DefaultPooledObject; 
import org.apache.commons.pool2.impl.GenericObjectPool; 

import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 

class ServiceObjectPool<T> extends GenericObjectPool<T> { 
     public ServiceObjectPool(java.util.function.Supplier<T> factory) { 
      super(new BasePooledObjectFactory<T>() { 
       @Override 
       public T create() throws Exception { 
        return factory.get(); 
       } 
      @Override 
      public PooledObject<T> wrap(T obj) { 
       return new DefaultPooledObject<>(obj); 
      } 
     }); 
    } 

    public static class PooledServiceProxy<T> implements InvocationHandler { 
     private ServiceObjectPool<T> pool; 

     public PooledServiceProxy(ServiceObjectPool<T> pool) { 
      this.pool = pool; 
     } 


     @Override 
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
      T t = null; 
      try { 
       t = this.pool.borrowObject(); 
       return method.invoke(t, args); 
      } finally { 
       if (t != null) 
        this.pool.returnObject(t); 
      } 
     } 
    } 

    @SuppressWarnings("unchecked") 
    public T getProxy(Class<? super T> interfaceType) { 
     PooledServiceProxy<T> handler = new PooledServiceProxy<>(this); 
     return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(), 
              new Class<?>[]{interfaceType}, handler); 
    } 
} 

para utilizar el proxy:

ServiceObjectPool<SomeNonThreadSafeService> servicePool = new ServiceObjectPool<>(createSomeNonThreadSafeService); 
nowSafeService = servicePool .getProxy(SomeNonThreadSafeService.class); 
Cuestiones relacionadas