Servlet 3 spec и ThreadLocal - PullRequest
       81

Servlet 3 spec и ThreadLocal

27 голосов
/ 21 февраля 2011

Насколько я знаю, в спецификации Servlet 3 появилась функция асинхронной обработки.Помимо прочего, это будет означать, что один и тот же поток может и будет использоваться повторно для обработки другого параллельного HTTP-запроса (запросов).Это не революционно, по крайней мере для людей, которые раньше работали с NIO.

В любом случае, это приводит к еще одной важной вещи: нет ThreadLocal переменных как временное хранилище для данных запроса,Поскольку, если один и тот же поток внезапно становится потоком-носителем для другого HTTP-запроса, локальные данные запроса будут переданы другому запросу.

Все это - мое чистое предположение, основанное на чтении статей, я не получилвремя играть с любыми реализациями Servlet 3 (Tomcat 7, GlassFish 3.0.X и т. д.).

Итак, вопросы:

  • Правильно ли предположить, что ThreadLocalперестанет быть удобным взломом для хранения данных запроса?
  • Кто-нибудь играл с какой-либо из реализаций Servlet 3 и пытался использовать ThreadLocal s, чтобы доказать вышеизложенное?
  • Помимо хранения данныхВ HTTP-сеансе есть ли другие подобные легкодоступные хаки, которые вы могли бы посоветовать?

РЕДАКТИРОВАТЬ: не поймите меня неправильно.Я полностью понимаю опасности и хакерство ThreadLocal.На самом деле, я всегда советую не использовать его в аналогичном контексте.Однако, хотите верьте, хотите нет, контекст потока использовался гораздо чаще, чем вы, возможно, представляете.Хорошим примером может служить Spring OpenSessionInViewFilter, который, согласно его Javadoc:

Этот фильтр делает сеансы Hibernate доступными через текущий поток, который будет автоматически определяться менеджерами транзакций.

Это не строго ThreadLocal (еще не проверил источник), но уже звучит тревожно.Я могу думать о более похожих сценариях, и обилие веб-фреймворков делает это гораздо более вероятным.

Короче говоря, многие люди строили свои песочные замки на вершине этого хака, с осознанием или без него.Поэтому ответ Стивена понятен, но не совсем то, что я ищу.Я хотел бы получить подтверждение того, действительно ли кто-либо пытался и смог воспроизвести некорректное поведение, поэтому этот вопрос можно использовать в качестве ориентира для других людей, попавших в ловушку той же проблемы.

Ответы [ 6 ]

9 голосов
/ 21 февраля 2011

Асинхронная обработка не должна вас беспокоить, если вы не попросите об этом.

Например, запрос не может быть выполнен асинхронным, если сервлет или какой-либо из фильтров в цепочке фильтров запроса не помечен <async-supported>true</async-supported>. Поэтому вы все еще можете использовать обычные практики для обычных запросов.

Конечно, если вам действительно нужна асинхронная обработка, вам нужно использовать соответствующие методы. В основном, когда запрос обрабатывается асинхронно, его обработка разбивается на части. Эти части не разделяют локальное состояние потока, однако вы можете использовать локальное состояние потока внутри каждой из этих частей, хотя вам придется управлять состоянием вручную между частями.

6 голосов
/ 21 февраля 2011

(Предостережение: я не читал спецификацию Servlet 3 подробно, поэтому я не могу с уверенностью сказать, что спецификация говорит о том, что вы думаете, что она делает. Я просто предполагаю, что она делает ...)

Правильно ли предположить, что ThreadLocal перестанет быть удобным хаком для хранения данных запроса?

Использование ThreadLocal всегда было плохим подходом, потому что вы всегда подвергались риску утечки информации, когда рабочий поток завершил один запрос и запустил другой. Хранить вещи как атрибуты в объекте ServletRequest всегда было лучше.

Теперь у вас просто есть еще одна причина сделать это "правильным" способом.

Кто-нибудь играл с какой-либо из реализаций Servlet 3 и пытался использовать ThreadLocals для доказательства вышесказанного?

Это не правильный подход. Он только говорит вам о конкретном поведении конкретной реализации при определенных обстоятельствах вашего теста. Вы не можете обобщать.

Правильный подход состоит в том, чтобы предположить, что это иногда случится , если в спецификации сказано, что это возможно ... и спроектировать ваше веб-приложение с учетом этого.

(Не бойтесь! Очевидно, в этом случае это не происходит по умолчанию. Ваше веб-приложение должно явно включить функцию асинхронной обработки. Если ваш код заполнен локальными потоками, вам будет рекомендовано не делать этого .. .)

Помимо хранения данных внутри HTTP-сессии, есть ли другие подобные легкодоступные хаки, которые вы могли бы посоветовать.

Неа. Единственный правильный ответ - сохранение данных, специфичных для запроса, в объекте ServletRequest или ServletResponse. Даже сохранение его в HTTP-сеансе может быть неправильным, поскольку для данного сеанса одновременно может быть активным несколько запросов.

3 голосов
/ 02 марта 2011

ПРИМЕЧАНИЕ: хаки следуют.Используйте с осторожностью или просто не используйте.

Пока вы продолжаете понимать, в каком потоке выполняется ваш код, нет никаких причин, по которым вы не можете безопасно использовать ThreadLocal.

try {
    tl.set(value);
    doStuffUsingThreadLocal();
} finally {
    tl.remove();
}

Это не так, как будто ваш стек вызовов переключается случайным образом.Черт возьми, если есть значения ThreadLocal, которые вы хотите установить глубоко в стеке вызовов, а затем использовать их дальше, вы можете взломать это тоже:

public class Nasty {

    static ThreadLocal<Set<ThreadLocal<?>>> cleanMe = 
        new ThreadLocal<Set<ThreadLocal<?>>>() {
            protected Set<ThreadLocal<?>> initialValue() {
                return new HashSet<ThreadLocal<?>>();
            }
        };

    static void register(ThreadLocal<?> toClean) {
       cleanMe.get().add(toClean);
    }

    static void cleanup()  {
        for(ThreadLocal<?> tl : toClean)
            tl.remove();
        toClean.clear();
    }
}

Затем вы регистрируете свои ThreadLocals по мере их установки и выполняете очисткунаконец пункт где-то.Это все постыдное занудство, которое вы, вероятно, не должны делать.Извини, что написал, но уже поздно: /

1 голос
/ 02 мая 2013

Вы экстрасенс! (+1 за это)

Моя цель состоит в том, чтобы ... получить подтверждение того, что оно перестало работать в контейнере Servlet 3.0

Здесь является доказательством того, что вы просили.

Между прочим, он использует тот же фильтр OEMIV, который вы упомянули в своем вопросе, и, угадайте, что он нарушает обработку сервлетов Async!

Редактировать: Вот еще одно доказательство.

1 голос
/ 02 марта 2011

Мне все еще интересно, почему люди используют гнилой API javax.servlet для реализации своих сервлетов. Что я делаю:

  • У меня есть базовый класс HttpRequestHandler, который имеет закрытые поля для запроса, ответа и метод handle(), который может выдавать исключение, а также некоторые служебные методы для получения / установки параметров, атрибутов и т. Д. чем 5-10% API сервлета, так что это не так много работы, как кажется.

  • В обработчике сервлета я создаю экземпляр этого класса, а затем забываю об API сервлета.

  • Я могу расширить этот класс обработчика и добавить все поля и данные, которые мне нужны для работы. Нет огромных списков параметров, нет локального взлома потока, нет проблем с параллелизмом.

  • У меня есть служебный класс для модульных тестов, который создает HttpRequestHandler с фиктивными реализациями запроса и ответа. Таким образом, мне не нужна среда сервлетов для тестирования моего кода.

Это решает все мои проблемы, потому что я могу получить сеанс БД и другие вещи в методе init() или я могу вставить фабрику между сервлетом и реальным обработчиком, чтобы делать более сложные вещи.

0 голосов
/ 02 мая 2018

Одно из решений состоит в том, чтобы не использовать ThreadLocal, а использовать одноэлементный массив, содержащий статический массив объектов, которые вы хотите сделать глобальными. Этот объект будет содержать заданное вами поле «threadName». Сначала вы устанавливаете имя текущего потока (в doGet, doPost) на случайное уникальное значение (например, UUID), а затем сохраняете его как часть объекта, содержащего данные, которые вы хотите сохранить в синглтоне. Затем, когда какой-то части вашего кода требуется доступ к данным, он просто проходит через массив и проверяет объект с запущенным в данный момент threadName и извлекает объект. Вам нужно будет добавить некоторый код очистки, чтобы удалить объект из массива после завершения http-запроса.

...