Est-ce que le client JAX-WS Call thread est en sécurité?

<!-Depuis que l'initialisation du service client et du port WS prend des âges, j'aime les initialiser une fois au démarrage et réutiliser la même instance du port. Initalization ressemblerait à quelque chose comme ceci:

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

L'appel, quelque part dans ma classe:

myPort.someFunctionCall(requestContext, "someValue");

ma question: cet appel sera-t-il sans fil?

21
demandé sur user871611 2012-05-15 15:42:09

4 réponses

Selon CXF FAQ:

réponse officielle JAX-WS: Non. Selon la spécification JAX-WS, les mandataires du client ne sont pas sécurisés. Pour écrire du code portable, vous devez les traiter comme non-thread sûr et synchroniser l'accès ou utiliser un pool d'instances ou similaire.

CXF answer: les procurations CXF sont sécuritaires pour de nombreux cas d'utilisation. Le les exceptions sont les suivantes:

  • ((BindingProvider)proxy).getRequestContext() - par JAX-WS spec, le contexte de la requête est par exemple. Ainsi, tout ce jeu il y aura affecter les requêtes sur d'autres threads. Avec CXF, vous pouvez faire:

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

    et les futurs appels à getRequestContext () utiliseront un thread contexte de la demande locale. Qui permet au contexte de la demande d'être des threads. (Note: le contexte de réponse est toujours thread local dans CXF)

  • Paramètres le conduit - si vous utilisez le code ou la configuration pour manipuler le conduit (comme pour définir les paramètres TLS ou similaire), ceux ne sont pas thread-safe. Le conduit est per-instance et donc ceux les paramètres seraient partagés. Aussi, si vous utilisez la FailoverFeature et LoadBalanceFeatures, le conduit est remplacé à la volée. Ainsi, les paramètres sur la conduite pourrait se perdre avant d'être utilisé sur le réglage du fil.

  • support de Session - si vous activez le support de session (voir jaxws spec), le cookie de session est stocké dans le conduit. Ainsi, il serait tomber dans les règles ci-dessus sur les paramètres de conduite et ainsi être partagées dans les threads.
  • WS-jetons de Sécurité - Si l'utilisation de WS-SecureConversation ou WS-Trust, le token récupéré est caché dans le Endpoint / Proxy pour éviter les appels supplémentaires (et coûteux) aux STS pour obtenir des jetons. Ainsi, plusieurs threads partageront le token. Si chaque fil a différent informations d'identification de sécurité ou des exigences, vous devez pour utiliser un mandataire distinct instance.

pour les problèmes de conduit, vous pouvez installer un nouveau Conductselector qui utilise un thread local ou similaire. C'est un peu complexe.

pour la plupart des cas d'utilisation "simple" , vous pouvez utiliser CXF proxies sur plusieurs threads. Ce qui précède décrit les solutions de rechange pour les autres.

22
répondu kyiu 2015-10-15 20:09:14

En général, non.

selon la FAQ CXF http://cxf.apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe?

réponse officielle JAX-WS: Non. Selon la spécification JAX-WS, le client les mandataires ne sont pas sûrs. Pour écrire du code portable, vous devriez traiter les comme non-thread sûr et synchroniser l'accès ou utiliser une piscine de les instances ou similaire.

CXF answer: les mandataires CXF sont des fils sans danger pour de nombreux cas d'utilisation.

pour une liste d'exceptions, voir la FAQ.

3
répondu CupawnTae 2014-06-11 12:54:36

comme vous le voyez à partir des réponses ci-dessus que JAX-WS proxies client ne sont pas thread safe, donc je voulais juste partager mon implémentation will autres pour mettre en cache les proxies client. J'ai fait face au même problème et j'ai décidé de créer un haricot à ressort qui fait la mise en cache des mandataires des clients JAX-WS. Vous pouvez voir plus de détails 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();
 }
}
3
répondu awsome 2016-12-09 09:13:23

Une solution générale pour cela est d'utiliser plusieurs objets dans une piscine, puis d'utiliser de proxy qui agit comme une façade.

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

Pour utiliser le proxy:

ServiceObjectPool<SomeNonThreadSafeService> servicePool = new ServiceObjectPool<>(createSomeNonThreadSafeService);
nowSafeService = servicePool .getProxy(SomeNonThreadSafeService.class);
0
répondu Jether 2018-02-15 14:13:17