由于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");
我的问题:这个电话会是线程安全的吗?
答案 0 :(得分:27)
根据CXF FAQ:
JAX-WS客户端代理线程是否安全?
官方JAX-WS答案:不会。 根据JAX-WS规范,客户端代理不是线程安全的。 要编写可移植代码,您应该将它们视为非线程安全的 同步访问或使用实例池或类似实例。
CXF回答:对于许多用例,CXF代理是线程安全的。该 例外是:
根据JAX-WS规范使用
((BindingProvider)proxy).getRequestContext()
, 请求上下文是PER INSTANCE。因此,任何设置都会 影响其他线程上的请求。使用CXF,您可以:((BindingProvider)proxy).getRequestContext().put("thread.local.request.context","true");
以及对getRequestContext()的未来调用将使用一个线程 本地请求上下文。这允许请求上下文 线程安全的。 (注意:响应上下文在CXF中始终是线程本地的)
管道上的设置 - 如果您直接使用代码或配置 操纵管道(如设置TLS设置或类似设置),那些 不是线程安全的。导管是每个实例,因此是那些 设置将被共享。此外,如果您使用FailoverFeature和 LoadBalanceFeatures,管道被动态替换。从而, 导管上设置的设置可能会在使用之前丢失 设置线程。
- 会话支持 - 如果您启用会话支持(请参阅 jaxws spec),会话cookie存储在管道中。因此,它 将在管道设置中落入上述规则,从而被共享 跨线程。
- WS-Security令牌 - 如果使用WS-SecureConversation或 WS-Trust,检索到的令牌缓存在端点/代理中以避免 额外(和昂贵)调用STS获取令牌。从而, 多个线程将共享令牌。如果每个线程有不同 安全凭证或要求,您需要使用单独的代理 实例
对于管道问题,您可以安装新的 使用本地或类似线程的ConduitSelector。那是一点点 虽然复杂。
对于大多数“简单”用例,您可以在多个上使用CXF代理 线程。以上概述了其他人的解决方法。
答案 1 :(得分:3)
一般来说,没有。
根据CXF常见问题http://cxf.apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe?
官方JAX-WS回答:否。根据JAX-WS规范,客户端 代理不是线程安全的。要编写可移植代码,您应该对待 它们作为非线程安全并同步访问或使用池 实例或类似。
CXF回答:对于许多用例,CXF代理是线程安全的。
有关例外情况,请参阅常见问题解答。
答案 2 :(得分:3)
正如您从上面的答案中看到的那样,JAX-WS客户端代理不是线程安全的,所以我只想分享我的实现,其他人将缓存客户端代理。 我实际上遇到了同样的问题,并决定创建一个执行JAX-WS客户端代理缓存的spring bean。您可以查看更多详细信息docs
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 :(得分:0)
一般的解决方案是在池中使用多个客户端对象,然后使用充当外观的代理。
apiVersion: apps/v1
kind: Deployment
metadata:
name: customer-ratings
labels:
app: product-ratings-vue
spec:
replicas: 1
selector:
matchLabels:
app: product-ratings-vue
template:
metadata:
labels:
app: product-ratings-vue
spec:
containers:
- name: api-service
image: api-service
ports:
- containerPort: 8080
- containerPort: 8000
resources: {}
volumeMounts:
- mountPath: /usr/local/tomcat/logs
name: api-service-claim
# ekomi-import
- name: ekomi-import
image: ekomi-import
resources: {}
# cache
- name: cache
image: cache
resources:
limits:
memory: "536870912"
# storage
- name: storage
image: storage
ports:
- containerPort: 7000
- containerPort: 7001
- containerPort: 7199
- containerPort: 9042
- containerPort: 9160
resources: {}
# view
- name: view
image: view
ports:
- containerPort: 3000
resources: {}
# tomcat
- name: tomcat
image: tomcat
# node
- name: node
image: node
resources: {}
# openJdk
- name: node
image: node
resources: {}
volumes:
- name: api-service-claim
persistentVolumeClaim:
claimName: api-service-claim
使用代理:
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);
}
}