Как работают сервлеты? Создание экземпляров, сессии, общие переменные и многопоточность

1144

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

Теперь, если 2 или более пользователей отправляют запрос на этот сервер, что происходит с переменными сеанса?
Будут ли они все общими для всех пользователей или будут разными для каждого пользователя?
Если они разные, то как сервер мог различать разных пользователей?

Еще один похожий вопрос: если есть nпользователи, обращающиеся к определенному сервлету, то этот сервлет создается только в первый раз, когда первый пользователь получил к нему доступ, или он создается для всех пользователей отдельно?
Другими словами, что происходит с переменными экземпляра?

Ку Джон
источник

Ответы:

1822

ServletContext

Когда контейнер сервлета (например, Apache Tomcat ) запускается, он развертывает и загружает все свои веб-приложения. Когда веб-приложение загружается, контейнер сервлета создает его ServletContextодин раз и сохраняет в памяти сервера. Веб - приложение web.xmlи все включены web-fragment.xmlфайлы анализируются, и каждое <servlet>, <filter>и <listener>нашел (или каждый класс с аннотацией @WebServlet, @WebFilterи @WebListenerсоответственно) конкретизируется раз и хранится в памяти сервера , а также. Для каждого экземпляра фильтра его init()метод вызывается с новым FilterConfig.

Когда a Servletимеет значение <servlet><load-on-startup>или @WebServlet(loadOnStartup)больше чем 0, тогда его init()метод также вызывается во время запуска с новым ServletConfig. Эти сервлеты инициализируются в том же порядке, который указан в этом значении ( 11-й, 22-й и т. Д.). Если же значение задано более одного сервлета, то каждый из этих сервлет загружаются в том же порядке , как они появляются в web.xml, web-fragment.xmlили @WebServletзагрузке классов. Если значение «load-on-startup» отсутствует, init()метод будет вызываться всякий раз, когда HTTP-запрос попадает в этот сервлет в первый раз.

Когда контейнер сервлета завершит все описанные выше шаги инициализации, ServletContextListener#contextInitialized()будет вызвано.

Когда сервлет контейнер закрывается вниз, он выгружает все веб - приложения, вызывает destroy()метод всех его инициализированными сервлетов и фильтров, а также все ServletContext, Servlet, Filterи Listenerэкземпляры громил. Наконец ServletContextListener#contextDestroyed()будет вызван.

HttpServletRequest и HttpServletResponse

Контейнер сервлета подключен к веб-серверу, который прослушивает HTTP-запросы на определенный номер порта (порт 8080 обычно используется во время разработки, а порт 80 - в работе). Когда клиент (например , пользователь с помощью веб - браузера, или программно с помощьюURLConnection ) посылает запрос HTTP, контейнер сервлета создает новый HttpServletRequestи HttpServletResponseобъекты , и передает их через любой определенные Filterв цепи и, в конечном счете, на Servletэкземпляр.

В случае фильтров , doFilter()метод вызывается. При вызове кода контейнера сервлета chain.doFilter(request, response)запрос и ответ переходят к следующему фильтру или попадают в сервлет, если нет оставшихся фильтров.

В случае сервлетов , service()метод вызывается. По умолчанию этот метод определяет, какой из doXxx()методов вызывать на основе request.getMethod(). Если определенный метод отсутствует в сервлете, в ответе возвращается ошибка HTTP 405.

Объект запроса обеспечивает доступ ко всей информации о HTTP-запросе, такой как его URL, заголовки, строка запроса и тело. Объект ответа предоставляет возможность контролировать и отправлять ответ HTTP, например, так, как вы хотите, например, позволяя вам устанавливать заголовки и тело (обычно с сгенерированным содержимым HTML из файла JSP). Когда HTTP-ответ фиксируется и завершается, объекты запроса и ответа перерабатываются и становятся доступными для повторного использования.

HttpSession

Когда клиент посещает веб-приложение в первый раз и / или HttpSessionполучает его впервые через request.getSession(), контейнер сервлета создает новый HttpSessionобъект, генерирует длинный и уникальный идентификатор (который вы можете получить session.getId()) и сохраняет его на сервере. Память. Контейнер сервлета также устанавливает Cookieв Set-Cookieзаголовке ответа HTTP a в JSESSIONIDкачестве имени и уникальный идентификатор сеанса в качестве значения.

В соответствии со спецификацией файлов cookie HTTP (контракт, которого должен придерживаться любой приличный веб-браузер и веб-сервер), клиент (веб-браузер) обязан отправлять этот куки-файл обратно в последующих запросах в Cookieзаголовке до тех пор, пока cookie-файл действителен ( т. е. уникальный идентификатор должен относиться к не истекшему сеансу, а домен и путь являются правильными). Используя встроенный в ваш браузер монитор HTTP-трафика, вы можете убедиться, что файл cookie действителен (нажмите F12 в Chrome / Firefox 23+ / IE9 + и откройте вкладку Сеть / Сеть ). Контейнер сервлета будет проверять Cookieзаголовок каждого входящего HTTP-запроса на наличие файла cookie с именем JSESSIONIDи использовать его значение (идентификатор сеанса), чтобы получить связанный HttpSessionиз памяти сервера.

В HttpSessionостается жив , пока он находится в режиме ожидания (т.е. не используется в запросе) больше , чем значение тайм - аута , указанных в <session-timeout>, настройки в web.xml. Значение тайм-аута по умолчанию составляет 30 минут. Таким образом, когда клиент не посещает веб-приложение дольше указанного времени, контейнер сервлета перехватывает сеанс. Каждый последующий запрос, даже с указанным файлом cookie, больше не будет иметь доступа к тому же сеансу; Контейнер сервлета создаст новый сеанс.

На стороне клиента cookie сеанса остается активным до тех пор, пока работает экземпляр браузера. Итак, если клиент закрывает экземпляр браузера (все вкладки / окна), то сеанс удаляется на стороне клиента. В новом экземпляре браузера cookie, связанный с сеансом, не существует, поэтому он больше не будет отправляться. Это приводит к HttpSessionсозданию совершенно нового файла с использованием совершенно нового файла cookie сеанса.

В двух словах

  • В ServletContextжизни так долго , как веб - приложение жизни. Он распределяется между всеми запросами во всех сеансах.
  • В HttpSessionжизни до тех пор , пока клиент взаимодействует с веб - приложение с тем же экземпляром браузера, и сеанс не истекло на стороне сервера. Он распределяется между всеми запросами в одном сеансе.
  • Операция HttpServletRequestи HttpServletResponseдействует с момента получения сервлетом HTTP-запроса от клиента до получения полного ответа (веб-страницы). Это не распространено в другом месте.
  • Все Servlet, Filterа Listenerэкземпляры живут так же долго, как и веб-приложение. Они распределяются между всеми запросами во всех сеансах.
  • Любое, attributeчто определено в ServletContext, HttpServletRequestи HttpSessionбудет жить до тех пор, пока живет рассматриваемый объект. Сам объект представляет «область видимости» в инфраструктурах управления bean-компонентами, таких как JSF, CDI, Spring и т. Д. Эти структуры хранят свои bean-объекты области видимости как attributeближайшую соответствующую область видимости.

Поток безопасности

Тем не менее, ваша главная проблема, возможно, безопасность потоков . Теперь вы должны знать, что сервлеты и фильтры являются общими для всех запросов. Это хорошая вещь в Java, она многопоточная, и разные потоки (читай: HTTP-запросы) могут использовать один и тот же экземпляр. В противном случае было бы слишком дорого, чтобы воссоздать, init()и destroy()их для каждого отдельного запроса.

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

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    } 
}

Смотрите также:

BalusC
источник
25
Поэтому, когда я каким-то образом могу узнать JSessionId, который отправляется клиенту, я могу украсть его сеанс?
Тоскан
54
@Toskan: это правильно. Это известно как взлом фиксации сессии . Обратите внимание, что это не относится к JSP / Servlet. Все другие языки на стороне сервера, которые поддерживают сеанс с помощью cookie, также чувствительны, как PHP с PHPSESSIDcookie, ASP.NET с ASP.NET_SessionIDcookie и так далее. Вот почему переопределение URL с помощью ;jsessionid=xxxнекоторых сред JSP / Servlet MVC автоматически не одобряется. Просто убедитесь, что идентификатор сеанса никогда не отображается в URL-адресе или другими способами на веб-страницах, чтобы не подвергнуться нападению неосведомленный конечный пользователь.
BalusC
11
@Toskan: Кроме того, убедитесь, что ваше веб-приложение не чувствительно к атакам XSS. Т.е. не отображать любой пользовательский ввод в неэкранированном виде. XSS открыл двери для способов сбора идентификаторов сеансов всех конечных пользователей. Смотрите также Какова общая концепция XSS?
BalusC
2
@BalusC, простите за мою глупость. Это означает, что все пользователи имеют доступ к одному и тому же экземпляру thisIsNOTThreadSafe, верно?
затмение
4
@TwoThumbSticks 404 возвращается, когда отсутствует весь сам сервлет. 405 возвращается, когда сервлет присутствует, но требуемый метод doXxx () не реализован.
BalusC
428

сессии

введите описание изображения здесь введите описание изображения здесь

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

Сервлет Инстанция

Если загрузка при запуске имеет значение false :

введите описание изображения здесь введите описание изображения здесь

Если загрузка при запуске - это правда :

введите описание изображения здесь введите описание изображения здесь

Как только он перейдет в сервисный режим и в паз, тот же сервлет будет работать с запросами всех других клиентов.

введите описание изображения здесь

Почему не стоит иметь один экземпляр на клиента? Подумайте об этом: вы будете нанимать по одному парню пиццы на каждый заказ? Сделайте это, и вы будете вне бизнеса в кратчайшие сроки.

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

JOPS
источник
26
Ваша картинка очень хороша для моего понимания. У меня один вопрос: что будет делать этот ресторан-пиццерия, когда пришло слишком много заказов на пиццу, просто подождать одного пиццерия или нанять еще пиццерию? Спасибо .
18
6
Он вернет сообщение сto many requests at this moment. try again later
Please_Dont_Bully_Me_SO_Lords
3
Сервлеты, в отличие от людей, доставляющих пиццу, могут выполнять более одной доставки одновременно. Им просто нужно позаботиться о том, чтобы записать адрес клиента, вкус пиццы ...
bruno
42

Сеанс в Java-сервлетах аналогичен сеансу в других языках, таких как PHP. Это уникально для пользователя. Сервер может отслеживать это различными способами, такими как файлы cookie, перезапись URL-адреса и т. Д. В этой статье, посвященной Java, объясняется это в контексте сервлетов Java и указывается, что именно то, как поддерживается сеанс, является подробностью реализации, оставленной разработчикам сервера. Спецификация только предусматривает, что она должна поддерживаться как уникальная для пользователя при нескольких подключениях к серверу. Проверьте эту статью от Oracle для получения дополнительной информации по обоим вашим вопросам.

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

Крис Томпсон
источник
Это поднимает для меня еще один вопрос: поскольку для всего приложения существует только один контекст сервлета, и мы получаем доступ к переменным сеанса через этот сервлет-текст, так как переменные сеанса могут быть уникальными для каждого пользователя? Спасибо ..
Ку Джон
1
как вы получаете доступ к сеансу из servletContext? Вы не имеете в виду servletContext.setAttribute (), не так ли?
Мэтт Б
4
@KuJon Каждое веб-приложение имеет один ServletContextобъект. Этот объект имеет ноль, один или несколько объектов сеанса - набор объектов сеанса. Каждый сеанс идентифицируется некоторой строкой идентификатора, как показано в мультфильмах на другом ответе. Этот идентификатор отслеживается на клиенте с помощью cookie или перезаписи URL. У каждого объекта сеанса есть свои переменные.
Василий Бурк
33

Когда сервлет-контейнер (например, Apache Tomcat) запускается, он будет читать из файла web.xml (только один на приложение), если что-то пойдет не так или обнаружит ошибку на консоли на стороне контейнера, в противном случае он развернет и загрузит весь веб-интерфейс. приложения с помощью web.xml (так называемый дескриптор развертывания).

На этапе создания экземпляра сервлета экземпляр сервлета готов, но не может обслуживать клиентский запрос, поскольку отсутствует с двумя частями информации:
1: контекстная информация
2: исходная информация о конфигурации

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

Q) Сколько раз в жизни сервлета происходит инициализация и инициализация ??

A) только один раз (для каждого клиентского запроса создается новый поток) только один экземпляр сервлета обслуживает любое количество клиентских запросов, т. Е. После обслуживания один клиентский сервер запросов не умирает. Он ожидает других клиентских запросов, т. Е. Какой CGI (для каждого клиентского запроса создается новый процесс) ограничение преодолевается с помощью сервлета (внутренний механизм сервлета создает поток).

Q) Как работает концепция сессии?

А) всякий раз, когда getSession () вызывается для объекта HttpServletRequest

Шаг 1 : объект запроса оценивается для входящего идентификатора сеанса.

Шаг 2 : если идентификатор недоступен, создается новый объект HttpSession и генерируется соответствующий ему идентификатор сеанса (т. Е. HashTable). Идентификатор сеанса сохраняется в объекте ответа httpservlet, а ссылка на объект HttpSession возвращается сервлету (doGet / doPost). ,

Шаг 3 : если идентификатор доступен, новый объект сеанса не создан. Идентификатор сеанса выбирается из запроса. Поиск объекта производится в коллекции сеансов с использованием идентификатора сеанса в качестве ключа.

После успешного поиска идентификатор сеанса сохраняется в HttpServletResponse, а ссылки на существующие объекты сеанса возвращаются в doGet () или doPost () UserDefineservlet.

Замечания:

1) когда управление уходит от кода сервлета к клиенту, не забывайте, что объект сеанса удерживается контейнером сервлета, т. Е. Механизмом сервлета

2) многопоточность оставлена ​​разработчикам сервлетов для реализации, т. Е. Обрабатывать множественные запросы клиента, не беспокоясь о многопоточном коде

Краткая форма:

Сервлет создается при запуске приложения (оно развертывается в контейнере сервлета) или при первом обращении к нему (в зависимости от настройки загрузки при запуске), когда создается сервлет, вызывается метод init () сервлета затем сервлет (его единственный экземпляр) обрабатывает все запросы (его метод service () вызывается несколькими потоками). Вот почему не рекомендуется иметь какую-либо синхронизацию, и вам следует избегать переменных экземпляра сервлета, когда приложение не развернуто (контейнер сервлета останавливается), вызывается метод destroy ().

Аджай Такур
источник
20

Сессии - что сказал Крис Томпсон.

Instantiation - сервлет создается, когда контейнер получает первый запрос, сопоставленный с сервлетом (если сервлет не настроен для загрузки при запуске с <load-on-startup>элементом in web.xml). Этот же экземпляр используется для обслуживания последующих запросов.

Лаури Лехтинен
источник
3
Правильный. Дополнительная мысль: каждый запрос получает новый (или переработанный) поток для запуска в этом отдельном экземпляре сервлета. Каждый сервлет имеет один экземпляр и, возможно, много потоков (если много одновременных запросов).
Василий Бурк
13

Спецификация сервлета JSR-315 четко определяет поведение веб-контейнера в методах службы (и doGet, doPost, doPut и т. Д.) (2.3.3.1 Проблемы многопоточности, Страница 9):

Контейнер сервлета может отправлять параллельные запросы через сервисный метод сервлета. Для обработки запросов разработчик сервлета должен предусмотреть адекватные условия для одновременной обработки с несколькими потоками в методе обслуживания.

Хотя это и не рекомендуется, альтернативой для разработчика является реализация интерфейса SingleThreadModel, который требует, чтобы контейнер гарантировал, что в методе службы одновременно присутствует только один поток запросов. Контейнер сервлета может удовлетворить это требование путем сериализации запросов к сервлету или путем поддержки пула экземпляров сервлета. Если сервлет является частью веб-приложения, помеченного как распространяемое, контейнер может поддерживать пул экземпляров сервлета в каждой JVM, по которой распространяется приложение.

Для сервлетов, не реализующих интерфейс SingleThreadModel, если метод службы (или методы, такие как doGet или doPost, которые отправляются методу службы абстрактного класса HttpServlet) был определен с ключевым словом synchronized, контейнер сервлета не может использовать подход пула экземпляров , но должен сериализовать запросы через него. Настоятельно рекомендуется, чтобы разработчики не синхронизировали метод службы (или методы, отправленные ему) в этих обстоятельствах из-за негативного влияния на производительность.

tharindu_DG
источник
2
К вашему сведению, текущая спецификация сервлета (2015-01) - 3.1, определенная JSR 340 .
Василий Бурк
1
Очень аккуратный ответ! @tharindu_DG
Том Тейлор,
0

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

1) Сериализация запросов (постановка в очередь) для одного экземпляра - это похоже на сервлет, НЕ реализующий SingleThreadModel, НО синхронизирующий методы service / doXXX; ИЛИ

2) Создание пула экземпляров - что является лучшим вариантом и компромисс между загрузкой / инициализацией / временем сервлета по сравнению с ограничительными параметрами (память / время ЦП) среды, в которой размещен сервлет.

Махеш Баласубраманян
источник
-1

Нет. Сервлеты не безопасны

Это позволяет получить доступ к более чем одному потоку одновременно

если вы хотите сделать его Servlet как потокобезопасным., вы можете пойти на

Implement SingleThreadInterface(i) пустой интерфейс нет

методы

или мы можем пойти на методы синхронизации

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

Ключевое слово перед методом

Пример::

public Synchronized class service(ServletRequest request,ServletResponse response)throws ServletException,IOException

или мы можем положить блок кода в синхронизированный блок

Пример::

Synchronized(Object)

{

----Instructions-----

}

Я чувствую, что синхронизированный блок лучше, чем весь метод

синхронизированный

Вед Пракаш
источник