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");
}
私のクラスのどこかに電話:
myPort.someFunctionCall(requestContext, "someValue");
私の質問:この呼び出しはスレッドセーフですか?
CXF FAQ によると:
JAX-WSクライアントプロキシはスレッドセーフですか?
公式のJAX-WS回答:いいえ。JAX-WS仕様によると、クライアントプロキシはスレッドセーフではありません。移植可能なコードを作成するには、それらを非スレッドセーフとして扱い、アクセスを同期するか、インスタンスのプールなどを使用する必要があります。
CXF回答:CXFプロキシは、多くのユースケースでスレッドセーフです。例外は次のとおりです。
((BindingProvider)proxy).getRequestContext()
の使用-JAX-WS仕様に従って、リクエストコンテキストはPER INSTANCEです。したがって、そこに設定されたものはすべて、他のスレッドの要求に影響します。 CXFを使用すると、次のことができます。((BindingProvider)proxy).getRequestContext().put("thread.local.request.context","true");
getRequestContext()への今後の呼び出しでは、スレッドローカル要求コンテキストが使用されます。これにより、要求コンテキストをスレッドセーフにすることができます。 (注:応答コンテキストは常にCXFのスレッドローカルです)
コンジットの設定-コードまたは構成を使用してコンジットを直接操作する場合(TLS設定の設定など)、それらはスレッドセーフではありません。コンジットはインスタンスごとなので、これらの設定は共有されます。また、FailoverFeatureおよびLoadBalanceFeaturesを使用する場合、コンジットはその場で置き換えられます。したがって、コンジットに設定された設定は、設定スレッドで使用される前に失われる可能性があります。
- セッションサポート-セッションサポートをオンにすると(jaxws仕様を参照)、セッションCookieはコンジットに格納されます。したがって、コンジット設定に関する上記のルールに該当し、スレッド間で共有されます。
- WS-Securityトークン-WS-SecureConversationまたはWS-Trustを使用する場合、取得したトークンはエンドポイント/プロキシにキャッシュされ、トークンを取得するためのSTSへの余分な(そして高価な)呼び出しを回避します。したがって、複数のスレッドがトークンを共有します。各スレッドに異なるセキュリティ資格情報または要件がある場合は、個別のプロキシインスタンスを使用する必要があります。
コンジットの問題については、スレッドローカルまたは類似のものを使用する新しいConduitSelectorをインストールすることができます。しかし、それは少し複雑です。
ほとんどの「単純な」使用例では、複数のスレッドでCXFプロキシを使用できます。上記は、他の回避策の概要です。
上記の回答からわかるように、JAX-WSクライアントプロキシはスレッドセーフではないため、他のクライアントプロキシをキャッシュする実装を共有したいだけです。私は実際に同じ問題に直面し、JAX-WSクライアントプロキシのキャッシュを実行するSpring Beanを作成することにしました。詳細を見ることができます 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();
}
}
一般的には違います。
CXFによるとFAQ http://cxf.Apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe?
公式のJAX-WS回答:いいえ。JAX-WS仕様によると、クライアントプロキシはスレッドセーフではありません。移植可能なコードを作成するには、それらを非スレッドセーフとして扱い、アクセスを同期するか、インスタンスのプールなどを使用する必要があります。
CXF回答:CXFプロキシは、多くのユースケースでスレッドセーフです。
例外のリストについては、FAQを参照してください。
これに対する一般的な解決策は、プールで複数のクライアントオブジェクトを使用してから、ファサードとして機能するプロキシを使用することです。
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);
}
}
プロキシを使用するには:
ServiceObjectPool<SomeNonThreadSafeService> servicePool = new ServiceObjectPool<>(createSomeNonThreadSafeService);
nowSafeService = servicePool .getProxy(SomeNonThreadSafeService.class);