Теория и практика Java: Все ли в порядке в Web-приложениях, которые хранят свое состояние в сеансе пользователя?

Класс HttpSession и связанные с ним классы сложнее, чем кажется

HttpSession - механизм управления состоянием сеанса пользователя, предоставляемый инфраструктурой Java Servlets, упрощает создание приложений, сохраняющих свое состояние, однако при использовании этого механизма очень легко допустить ошибку. Большинство Web-приложений, использующих HttpSession для меняющихся данных (например, классов JavaBeans), делают это без необходимой координации, что может привести возникновению множества проблем, связанных с параллельным выполнением кода.

Брайан Гетц, главный консультант, Quiotix

Брайан Гетц (Brian Goetz) - консультант по ПО и последние 15 лет работал профессиональными разработчиком ПО. Сейчас он является главным консультантом в фирме Quiotix, занимающейся разработкой ПО и консалтингом и находящейся в Лос-Альтос, Калифорния. Следите за публикациями Брайана в популярных промышленных изданиях. Вы можете связаться с Брайаном по адресу brian@quiotix.com



17.11.2010

Несмотря на то, что в мире Java имеется много Web-инфраструктур, все они, прямо или косвенно, основаны на инфраструктуре Java Servlets. Java Servlets API предоставляет набор полезных возможностей, включая управление состоянием с помощью объектов HttpSession и ServletContext, которые позволяют приложению сохранять данные на протяжении нескольких запросов от пользователя. Однако для Web-приложений существует ряд тонких (и по большей части недокументированных) правил, регулирующих работу с совместно используемыми объектами, из-за которых во многих приложениях появляются трудноуловимые ошибки. В результате во многих Web-приложениях, хранящих свое состояние в сеансе пользователя, возникают запутанные ситуации и проблемы.

Контейнеры с ограниченным сроком жизни

Экземпляры классов ServletContext, HttpSession и HttpRequest в спецификации Java Servlets называются контейнерами с ограниченным сроком жизни (scoped containers). У каждого из них есть методы getAttribute() и setAttribute(), которые позволяют приложению записывать и считывать используемые им данные. Различие между этими контейнерами заключается в продолжительности их жизненного цикла. Например, в случае HttpRequest данные хранятся только во время обработки запроса; в HttpSession данные сохраняются только пока существует сеанс между пользователем и приложением; в случае ServletContext данные существуют на протяжении всего жизненного цикла приложения.

Поскольку протокол HTTP не обеспечивает сохранение состояния между запросами пользователя (stateless), контейнеры с ограниченным сроком жизни незаменимы при создании Web-приложений, обеспечивающих хранение состояния; при этом сервлет-контейнер отвечает за управление состоянием приложения и жизненным циклом использующихся в нем данных. Хотя, в спецификации это четко не оговаривается, контейнеры, ограниченные длительностью сеанса пользователя или длительностью жизни всего приложения, должны быть потокобезопасными, так как методы getAttribute() и setAttribute() могут вызываться в любое время из различных потоков. (Спецификация напрямую не требует, чтобы реализации этих контейнеров были потокобезопасными, но этого требует сама природа служб, обеспечиваемых этими объектами.)

Контейнеры с ограниченным сроком жизни также предоставляют Web-приложениям еще одно потенциальное преимущество: контейнер может управлять репликацией и восстанавливать состояние приложения после сбоя прозрачно для приложения.

Сеанс

Сеанс (session) - это последовательность запросов, отправляемых конкретным клиентом серверу, и ответов, полученных от Web-сервера. Пользователи ожидают, что Web-сайты запомнят их аутентифицирующие данные, содержимое их покупательской корзины и информацию, введенную в различные формы во время прошлых запросов, но сам по себе протокол HTTP не поддерживает хранение состояния, так что вся информация о запросе должна содержаться в нем самом. Поэтому чтобы организовать продуктивное взаимодействие с пользователем, которое бы длилось дольше, чем время жизни разового запроса, состояние сеанса должно храниться в каком-нибудь другом месте. Инфраструктура Java Servlets позволяет ассоциировать каждый запрос с сеансом и предоставляет интерфейс HttpSession для хранения данных, относящихся к этому сеансу (данные хранятся в виде пар ключ/значение). В листинге 1 приведен стандартный фрагмент исходного кода, сохраняющий корзину покупок покупателя в объект HttpSession:

Листинг 1. Использование объекта HttpSession для сохранения информации о покупках покупателя
HttpSession session = request.getSession(true);
ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart");
if (cart == null) {
    cart = new ShoppingCart(...);
    session.setAttribute("shoppingCart");
}        
doSomethingWith(cart);

Код из листинга 1 очень часто применяется в сервлетах; приложение проверяет, был ли уже объект помещен в сеанс, и, если нет, создает новый экземпляр этого объекта, который будет использоваться в последующих запросах в этом сеансе. Web-инфраструктуры, построенные на основе Java Servlets (такие как JSP, JSF, SpringMVC и т.д.), скрывают подробности реализации, но по существу выполняют подобные же действия над данными, которые помечены как хранимые на протяжении сеанса. К несчастью, код из листинга 1 является неправильным.

Вопросы потокобезопасности при выполнении в многопоточной среде

Когда в сервлет-контейнер поступает HTTP-запрос, создаются экземпляры объектов HttpRequest и HttpResponse; далее эти экземпляры передаются в метод service() сервлета в контексте потока, управляемого сервлет-контейнером. Сервлет отвечает за подготовку ответа для клиента; также сервлет управляет текущим потоком до тех пор, пока не будет сформирован ответ; после этого поток возвращается в пул рабочих потоков, доступных для дальнейшего использования. Сервлет не поддерживает связь между потоком и сеансом; следующий запрос, который придет в рамках этого сеанса, скорее всего будет обслужен другим потоком, а не потоком, обслужившим текущий запрос. На практике возможно, что в рамках одного сеанса одновременно придут несколько запросов (такая ситуация может возникнуть в приложениях, которые используют фреймы или технологию AJAX для загрузки данных с сервера, не лишая при этом пользователя возможности работать со страницей). В этом случае от одного пользователя может поступить несколько запросов, одновременно выполняющихся в различных потоках.

В большинстве случаев вопросы потокобезопасности при работе приложения в многопоточной среде не являются существенными для разработчика Web-приложений. Сама сущность протокола HTTP, не поддерживающего хранение состояния, предполагает, что ответ является функцией данных, хранящихся в запросе (которые недоступны для параллельно выполняющихся запросов) и данных, хранящихся в репозитариях (таких как базы данных), в которых уже реализовано управление одновременным доступом. Однако если Web-приложение хранит данные в общем контейнере типа HttpSession или ServletContext, то оно получает возможность одновременной работы над несколькими запросами, и теперь необходимо подумать о потокобезопасности.

Обычно, когда говорят о потокобезопасности, имеют в виду программный код, хотя на самом деле речь идет о данных. Точнее, потокобезопасность заключается в корректной координации доступа к изменяемым данным, которые доступны сразу нескольким потокам. Java Servlet-приложения являются потокобезопасными благодаря тому, что в них фактически отсутствуют данные, общие для нескольких потоков, и, следовательно, не требуется дополнительная синхронизация. Однако существует много способов добавить общие данные в Web-приложение - не только через контейнеры с ограниченным сроком жизни, такие как HttpSession и ServletContext, но и через статические переменные и поля объекта HttpServlet. Как только Web-приложению потребуется сохранить данные между запросами, разработчик приложения должен уделить внимание тому, где будут храниться совместно используемые данные, и убедиться, что потоки, обращающиеся к данным, синхронизированы в достаточной степени, чтобы избежать проблем, связанных с потокобезопасностью.

Риски для Web-приложений, связанные с потоками

Когда Web-приложение хранит изменяемые данные сеанса, такие как корзина покупок, в объекте HttpSession, может возникнуть ситуация, когда два запроса одновременно попытаются обратиться к корзине покупок. В этом случае возможны следующие ошибки:

  • Ошибка атомарности (atomicity failure), в которой один поток обновляет несколько элементов данных, а другой поток считывает данные в тот момент, когда они находятся в противоречивом состоянии.
  • Ошибка видимости (visibility failure) возникает между читающим и пишущим потоками, когда один поток модифицирует корзину покупателя, а другой видит устаревшее или противоречивое состояние содержимого корзины.

Ошибки атомарности

В листинге 2 показана неправильная реализация методов для установки и считывания максимального числа очков в игровом приложении. Для представления максимального числа очков используется объект PlayerScore, являющийся обычным JavaBean-классом со свойствами name и score и хранящийся в контейнере типа ServletContext (срок жизни этого контейнера совпадает со сроком жизни всего приложения). (Это значит, что при запуске приложения исходное значение максимального количества очков сохраняется в атрибуте highScore контейнера ServletContext, поэтому вызов метода getAttribute() не приведет к возникновению ошибки в приложении.)

Листинг 2. Неправильный алгоритм для хранения данных в контейнере с ограниченным сроком жизни
public PlayerScore getHighScore() {
    ServletContext ctx = getServletConfig().getServletContext();
    PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");
    PlayerScore result = new PlayerScore();
    result.setName(hs.getName());
    result.setScore(hs.getScore());
    return result;
}

public void updateHighScore(PlayerScore newScore) {
    ServletContext ctx = getServletConfig().getServletContext();
    PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");
    if (newScore.getScore() > hs.getScore()) {
        hs.setName(newScore.getName());
        hs.setScore(newScore.getScore());
    }
}

Что конкретно неправильно в коде из листинга 2? Подход, используемый здесь, состоит в сохранении переменной для имени игрока с максимальным количеством очков и самого количества очков в контейнере типа ServletContext. Когда появляется новый лучший результат, имя и количество очков необходимо изменить.

Предположим, что лучшего игрока зовут Боб (Bob) и у него 1000 очков, но результат Боба побил Джо (Joe), который набрал 1100 очков. В момент, когда регистрируется результат Джо, другой игрок запрашивает максимальное количество очков. Метод getHighScore() извлечет объект PlayerScore из контейнера типа ServletContext, а уже из него получит имя и количество очков. При неудачном стечении обстоятельств возможна ситуация, когда будет возвращено имя Боба, но результат Джо, т.е. будет ошибочно показано, что Боб достиг 1100 очков. (Такая ошибка не будет критичной на сайте бесплатных игр, но если заменить "количество очков" на "банковский баланс", то ситуация будет совсем не безобидной). Эта ошибка является ошибкой атомарности, так как две операции, которые должны быть атомарными по отношению друг к другу, - извлечение имени/количества очков и обновление имени/количества очков - на самом деле выполняются не атомарно по отношению одна к другой, и у одного из потоков получилось считать общие данные тогда, когда они находились в несогласованном состоянии.

Далее, поскольку механизм обновления очков следует шаблону "сначала проверь - затем действуй" (check-then-act), возможна ситуация, когда несколько потоков одновременно попытаются обновить наилучшие показатели, что приведет к непредсказуемым результатам. Предположим, что текущее наибольшее количество очков 1000, и два игрока одновременно регистрируют новые результаты в 1100 и 1200 очков. Из-за синхронизации оба потока пройдут проверку, что представленный результат больше уже имеющегося наибольшего количества очков, и оба попадут в блок кода, где выполняется обновление значения максимального количества очков. И снова из-за синхронизации результирующее значение может быть противоречивым (имя одного игрока и наилучший результат другого), или просто неправильным (данные игрока, набравшего 1100 очков, перезапишут данные игрока, достигшего 1200 очков).

Ошибки видимости

Ошибки видимости сложнее, чем ошибки атомарности. Из-за отсутствия синхронизации, если один поток записывает значение в переменную, а другой поток считывает значение этой же переменной, читающий поток может считать устаревшие данные. Еще хуже ситуация, если читающий поток увидит обновленные данные для переменной x и устаревшие данные для переменной y, даже если значение y было записано до x. Ошибки видимости плохи тем, что они непредсказуемы и возникают не очень часто, но вызывают необычные сбои, которые трудно отлаживать. Ошибки видимости создаются при конкуренции за данные - сбое в синхронизации при доступе к общим переменным. Любая программа, в которой присутствует конкуренция или "гонка" за данными, является некорректной, так как ее поведение невозможно предсказать.

Модель памяти Java (Java Memory Model, JMM) определяет условия, согласно которым поток, считывающий переменную, гарантированно увидит результаты записи переменной в другом потоке. (Подробное объяснение механизмов работы JMM выходит за рамки данной статьи; см. раздел Ресурсы.) JMM определяет для программы порядок выполнения операций, который также называется алгоритмом happens-before (случилось-до). Алгоритм упорядочивания happens-before применяется для потоков только при синхронизации на общем объекте-семафоре (lock) или при доступе к общей volatile переменной (еще один тип синхронизации, встроенной в Java). Если же алгоритм happens-before не применяется, платформа Java имеет широкие права откладывать или менять порядок, при котором изменения в переменной, внесенные в одном потоке, становятся видимыми при чтении из другого потока.

В листинге 2 присутствуют и ошибки видимости, и ошибки атомарности. Метод updateHighScore() извлекает объект HighScore из объекта ServletContext, а затем меняет состояние объекта HighScore. Задача этих модификаций - сделать новое значение переменной видимым для других потоков, вызывающих метод getHighScore(), но из-за отсутствия упорядочивания по алгоритму happens-before при записи переменных с именем победителя и его результатов в методе updateHighScore() и чтением этих свойств в других потоках, вызывающих метод getHighScore(), можно лишь надеяться, что потоки прочитают корректные значения.

Возможные решения

Несмотря на то, что спецификация Java Servlets не содержат формального описания алгоритма happens-before, который должен реализовываться сервлет-контейнером, задача программиста - обеспечить, чтобы запись атрибута в общий контейнер (HttpSession или ServletContext) происходила до того, как другой поток извлечет этот атрибут. (Подтверждения этого заключения приведены в JCiP 4.5.1. Во всех спецификациях говорится "Несколько сервлетов, обрабатывающих потоки с запросами, могут одновременно иметь доступ к одному и тому объекту сеанса. Разработчик несет ответственность за необходимую синхронизацию доступа к ресурсам сеанса.")

Методика set-after-write

Наилучший выход из проблемы, затронутой выше, состоит в том, чтобы снова вызвать метод setAttribute() после обновления данных (английское название этой методики - set-after-write). В листинге 3 приведен пример метода updateHighScore(), переписанного для реализации этой методики. (Одно из преимуществ этой методики заключается в том, что контейнер уведомляется об изменении значения, и сеанс или состояние приложения могут быть синхронизированы с другими экземплярами этого Web-приложения (если используется кластер)).

Листинг 3. Использование методики set-after-write для уведомления сервлет-контейнера об изменении значения
public void updateHighScore(PlayerScore newScore) {
    ServletContext ctx = getServletConfig().getServletContext();
    PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");
    if (newScore.getScore() > hs.getScore()) {
        hs.setName(newScore.getName());
        hs.setScore(newScore.getScore());
        ctx.setAttribute("highScore", hs);
    }
}

К несчастью, хотя эта методика позволяет решать проблему эффективного дублирования сеансов и состояний приложения в кластерных приложениях, ее недостаточно для решения базовых проблем с многопоточностью в представленном примере. Данный подход может устранить ошибки видимости (так что другой игрок может никогда не увидеть значений, измененных в методе updateHighScore()), но его недостаточно для решения множества потенциальных проблем атомарности.

Двусторонняя синхронизация

Методика set-after-write может устранить проблемы видимости, поскольку порядок happens-before (случилось-до) является транзитивным и как бы ставит границу между вызовом метода setAttribute() в updateHighScore() и getAttribute() в методе getHighScore(). Обновление состояния объекта HighScore происходит до вызова метода setAttribute(), который, в свою очередь, выполняется до возврата значения методом getAttribute(). Следовательно, можно сделать вывод, что значения, полученные при вызове метода getHighScore(), будут настолько актуальными, насколько актуален был последний вызов метода setAttribute(). Этот прием называется двусторонней синхронизацией (piggybacking on synchronization), поскольку методы getHighScore() и updateHighScore() могут использовать свои знания о состоянии синхронизации из методов getAttribute() и setAttribute() для предоставления минимальных гарантий видимости. Однако, как упоминалось ранее, для рассматриваемого примера этого недостаточно. Методика set-after-может быть полезной при репликации состояния сеанса, но она не может обеспечить потокобезопасность.

Использование неизменяемых данных

Наиболее полезная методика для создания потокобезопасных приложений заключается в том, чтобы как можно больше использовать неизменяемые данные (immutable-подход). В листинге 4 приведен пример, переписанный для использования immutable-реализации объекта HighScore, при которой больше не могут возникнуть ошибки атомарности, позволяющие пользователю увидеть несуществующую пару игрок/очки, и свободную от ошибок видимости, благодаря чему пользователь, вызывающий getHighScore(), не сможет увидеть самые последние значения, записанные вызовом updateHighScore():

Листинг 4. Использование неизменяемого объекта HighScore для устранения большинства ошибок атомарности и видимости
Public class HighScore {
    public final String name;
    public final int score;

    public HighScore(String name, int score) {
        this.name = name;
        this.score = score;
    }
}

public PlayerScore getHighScore() {
    ServletContext ctx = getServletConfig().getServletContext();
    return (PlayerScore) ctx.getAttribute("highScore");
}

public void updateHighScore(PlayerScore newScore) {
    ServletContext ctx = getServletConfig().getServletContext();
    PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");
    if (newScore.score > hs.score) 
        ctx.setAttribute("highScore", newScore);
}

В коде из листинга 4 гораздо меньше возможностей для возникновения ошибок. Двусторонняя синхронизация методами setAttribute() и getAttribute() гарантирует видимость. Сам факт того, что хранится только одно неизменяемое (в течение максимально возможного времени) значение, устраняет возможные ошибки атомарности, когда при вызове getHighScore() были бы получены несовместимые изменения в паре имя/значение.

Размещение неизменяемых объектов в контейнерах позволяет избежать большинства ошибок атомарности и видимости; также в контейнерах полезно хранить фактически неизменяемые объекты (effectively immutable objects). Фактически неизменяемые объекты - это такие объекты, которые теоретически могут быть изменены, но на практике никогда не меняются после того, как стали доступны, например, JavaBeans-объекты, чьи методы для установки свойств никогда не вызываются после сохранения JavaBeans-объекта в объект HttpSession.

Доступ к данным, помещенным в объект HttpSession, может осуществляться не только из запросов в рамках данного сеанса; это может делать и сам контейнер, если он выполняет репликацию состояния сеанса или приложения.

Все данные, помещаемые в объекты HttpSession или ServletContext, должны быть либо потокобезопасными, либо фактически неизменяемыми.

Выполнение атомарных операций с состоянием

В коде из листинга 4 все-таки есть одна проблема, связанная с использованием методики check-then-act (проверь-затем-действуй) в методе updateHighScore(), так как это не исключает возникновения возможной конкуренции между двумя потоками, пытающимися обновить значение максимального количества очков. При некоторой неудачной синхронизации выполняемое обновление вполне может потеряться. Два потока могут одновременно пройти проверку "если новое максимальное количество очков больше, чем старое" и вызвать метод setAttribute(). В зависимости от синхронизации нет гарантии, что действительно будет записан наивысший из двух результатов. Чтобы закрыть эту последнюю уязвимость, потребуется решение для атомарного обновления данных по максимальному числу очков, причем в процесс его работы ничего не должно вмешиваться. Для реализации этой идеи существует несколько подходов.

В листинге 5 в метод updateHighScore() добавляется синхронизация, гарантирующая, что проверка check-then-act, проводящаяся при обновлении, не сможет быть выполнена одновременно с обновлением, происходящим в другом потоке. Этот подход будет работать в том случае, если вся программная логика, связанная с изменением объекта HighScore, будет использовать такой же объект-семафор, как и метод updateHighScore().

Листинг 5. Использование синхронизации для закрытия последней ошибки атомарности
public void updateHighScore(PlayerScore newScore) {
    ServletContext ctx = getServletConfig().getServletContext();
    PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");
    synchronized (lock) {
        if (newScore.score > hs.score) 
            ctx.setAttribute("highScore", newScore);
    }
}

Хотя прием, показанный в листинге 5, отлично работает, существует еще более совершенное решение: использование класса AtomicReference из пакета java.util.concurrent. Этот класс разработан для выполнения атомарного обновления с учетом определенного условия посредством вызова compareAndSet(). В листинге 6 показано, как использовать класс AtomicReference для исправления последней ошибки атомарности. Этот подход предпочтительнее, чем описанный в листинге 5, поскольку при его использовании труднее случайно нарушить установленные правила обновления максимального количества очков.

Листинг 6. Исправление последней ошибки атомарности при помощи класса AtomicReference
public PlayerScore getHighScore() {
    ServletContext ctx = getServletConfig().getServletContext();
    AtomicReference<PlayerScore> holder 
        = (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
    return holder.get();
}

public void updateHighScore(PlayerScore newScore) {
    ServletContext ctx = getServletConfig().getServletContext();
    AtomicReference<PlayerScore> holder 
        = (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
    while (true) {
        HighScore old = holder.get();
        if (old.score >= newScore.score)
            break;
        else if (holder.compareAndSet(old, newScore))
            break;
    } 
}
Обновление состояния изменяемых объектов должно выполняться атомарно либо через синхронизацию, либо через атомарные переменные в классе java.util.concurrent.

Сериализация доступа к объекту HttpSession

В примерах, приведенных ранее, мы пытались избежать различных проблем, связанных с доступом к данным при использовании контейнера типа ServletContext (который обеспечивает доступ к данным на уровне всего приложения). Очевидно, что при обращении к объекту ServletContext требуется тщательная координация, поскольку к нему можно обратиться из любого запроса. Однако большинство Web-приложений чаще используют контейнер типа HttpSession (который обеспечивает доступ к данным на уровне одного сеанса). Кажется странным, как в одном сеансе может возникнуть несколько одновременных запросов; в конце концов, сеанс привязан к конкретному пользователю и сеансу браузера, а пользователи редко запрашивают сразу несколько разных страниц. Однако запросы в сеансе могут перекрываться в приложениях, которые генерируют запросы автоматически, таких как AJAX-приложения.

Запросы в рамках одного сеанса могут пересечься друг с другом, и это будет досадным событием. Если бы запросы в рамках одного сеанса могли бы быть легко сериализованы, все угрозы, описанные выше и возникающие при обращении к общим объектам в контейнере типа HttpSession были бы устранены; сериализация (serialization) помогла бы предотвратить ошибки атомарности, а двусторонняя синхронизация, неявно реализованная в HttpSession, могла бы предотвратить ошибки видимости. Кроме того, сериализация запросов, относящихся к определенному сеансу, вряд ли приведет к значительной нагрузке на пропускную способность; вообще запросы в сеансе перекрываются очень редко, и еще реже встречается большое количество таких перекрывающих друг друга запросов.

К несчастью, в спецификации Java Servlet нет параграфа, "принуждающего сериализовать запросы в одном и том же сеансе". Однако инфраструктура SpringMVC предлагает способ достичь этого, и этот подход может быть с легкостью воспроизведен в других инфраструктурах. Базовый класс для контроллеров в инфраструктуре SpringMVC, AbstractController, предоставляет булеву переменную synchronizeOnSession; когда эта переменная установлена в true, SpringMVC будет использовать блокировку, чтобы гарантировать, что в каждый момент в рамках сеанса выполняется только один запрос.

Сериализация запросов вокруг объекта HttpSession позволяет избежать проблем с одновременным выполнением запросов таким же образом, как помещение объектов в поток диспетчеризации событий (Event Dispatch Thread, EDT) уменьшает потребность Swing-приложений в синхронизации.

Заключение

Многие Web-приложения, сохраняющие состояние, обладают серьезными уязвимостями с точки зрения одновременного выполнения из-за недостаточной синхронизации доступа к изменяемым данным, хранящимся в контейнерах типа HttpSession и ServletContext. Можно ошибочно предположить, что достаточно синхронизации, реализованной в методах getAttribute() и setAttribute() - но такое решение подходит лишь при определенных обстоятельствах, например, при неизменяемых атрибутах, фактически неизменяемых или потокобезопасных объектах, либо если все запросы, обращающиеся к контейнеру, сериализованы.

В общем случае все данные, которые хранятся в контейнерах с ограниченным сроком жизни, должны быть либо фактически неизменяемыми либо потокобезопасными. Реализация контейнеров, определяемая в спецификации Java Servlet, никогда не предназначалась для управления изменяемыми объектами, которые не могут самостоятельно синхронизироваться. Самым большим преступлением будет хранение обычных JavaBeans-классов в контейнере типа HttpSession. Такой подход будет работать только в случае, если JavaBeans-объект не изменяется после того, как сохраняется в данных сеанса.

Ресурсы

Научиться

  • Java theory and practice: Are all stateful Web applications broken?: оригинал статьи (EN).
  • Java Concurrency in Practice (Brian Goetz, Addison-Wesley Professional, май 2006 г.): в разделе 4.5.1 объясняется алгоритм анализа, который используется для реализации подхода happens-before в общих контейнерах. В разделе 3.5.4 объясняется принцип фактической неизменяемости данных. В разделе 16 приведено подробное описание модели памяти Java (Java Memory Model) и схемы упорядочивания операций happens-before.(EN)
  • Спецификация Servlet 2.5: (EN): описывает механизмы HttpSession и ServletContext.
  • Spring Framework: дополнительная информация о SpringMVC.(EN)
  • State Replication in the Web Tier (EN): в этой статье рассматривается репликация состояния, выполняемая сервлет-контейнером.
  • developerWorks на Твиттер.(EN)
  • Информация и презентации о технологиях IBM с открытым кодом и бесплатных продуктах в разделе developerWorks On demand demos.(EN)
  • Safarit technology bookstore: Интернет-магазин технической литературы.(EN)

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=587770
ArticleTitle=Теория и практика Java: Все ли в порядке в Web-приложениях, которые хранят свое состояние в сеансе пользователя?
publish-date=11172010