Ответ 1
Ваша реализация не является потокобезопасной. Когда два потока обращаются к someMethod
, в то же время они используют один и тот же Client
, и один попытается сделать второй запрос, пока первый не будет завершен.
У вас есть два варианта:
Согласно документации,
"Клиенты - это тяжелые объекты, которые управляют клиентской стороной коммуникационной инфраструктуры. Инициализация, а также Экземпляр клиента может быть довольно дорогостоящей операцией. Поэтому рекомендуется создавать только небольшое количество экземпляров клиента в выражение."
Хорошо, я пытаюсь кэшировать сам клиент и экземпляры WebTarget в статической переменной, someMethod() вызывается в многопоточной среде:
private static Client client = ClientBuilder.newClient();
private static WebTarget webTarget = client.target("someBaseUrl");
...
public static String someMethod(String arg1, String arg2)
{
WebTarget target = entrTarget.queryParam("arg1", arg1).queryParam("arg2", arg2);
Response response = target.request().get();
final String result = response.readEntity(String.class);
response.close();
return result;
}
Но иногда (не всегда) я получаю исключение:
Недопустимое использование BasicClientConnManager: соединение все еще выделено. Обязательно отпустите соединение, прежде чем выделять другой.
Как можно корректно использовать/кэшировать Client/WebTarget? Возможно ли это с JAX RS Client API? Или я должен использовать некоторые функции, связанные с каркасом (resteasy/jersey). Не могли бы вы привести пример или документацию?
Ваша реализация не является потокобезопасной. Когда два потока обращаются к someMethod
, в то же время они используют один и тот же Client
, и один попытается сделать второй запрос, пока первый не будет завершен.
У вас есть два варианта:
Так как эта проблема все еще открыта на момент написания (версия 3.0.X) RESTEASY: устаревшая очистка классов Apache
Вы можете глубже использовать новые, не устаревшие классы, чтобы создать для вас клиента resteasy. У вас также будет больше контроля над тем, как вы хотите, чтобы пул был и т.д.
Вот что я сделал:
// This will create a threadsafe JAX-RS client using pooled connections.
// Per default this implementation will create no more than than 2
// concurrent connections per given route and no more 20 connections in
// total. (see javadoc of PoolingHttpClientConnectionManager)
PoolingHttpClientConnectionManager cm =
new PoolingHttpClientConnectionManager();
CloseableHttpClient closeableHttpClient =
HttpClientBuilder.create().setConnectionManager(cm).build();
ApacheHttpClient4Engine engine =
new ApacheHttpClient4Engine(closeableHttpClient);
return new ResteasyClientBuilder().httpEngine(engine).build();
Также убедитесь, что вы освободили соединение после совершения вызова. Вызов response.close() сделает это для вас, поэтому, вероятно, поместите это в блок finally.
Во-первых, не используйте повторно WebTarget. Для простоты вы всегда можете создать новый WebTarget.
Во-вторых, если вы используете Resteasy, вы можете добавить предоставленную зависимость для клиента Resteasy в свой проект. Пример в Gradle:
provided 'org.jboss.resteasy:resteasy-client:3.0.14.Final'
Затем вы можете создать свое соединение следующим образом:
ResteasyClientBuilder builder = new ResteasyClientBuilder();
builder.connectionPoolSize(200);
Нет необходимости устанавливать maxPooledPerRoute, это автоматически устанавливается RestEasy (может быть найдено в исходном коде класса RestEasyClientBuilder).
Когда вы устанавливаете connectionPoolSize, вы больше не будете получать ошибку при повторном использовании Клиента, и вы можете с радостью повторно использовать их во всем приложении. Я пробовал это решение во многих проектах, и он действительно работает хорошо. Но когда вы развертываете приложение в контейнер non-resteasy (например, Glassfish), ваш код не будет работать, и вам придется снова использовать класс ClientBuilder.