Как работают сервлеты? Инстанцирование, сессии, общие переменные и многопоточность
У меня есть веб-сервер, который содержит множество сервлетов. Для передачи информации между этими сервлетами я устанавливаю переменные сессии и экземпляров.
Теперь, если 2 или более пользователей отправляют запросы на этот сервер, что происходит с переменными сессии? Будут ли они общими для всех пользователей или же они будут разными для каждого пользователя? Если они разные, то как сервер может различать разных пользователей?
Также у меня есть еще один подобный вопрос: если имеется n
пользователей, обращающихся к определенному сервлету, создается ли этот сервлет только в первый раз для первого пользователя или он создается отдельно для всех пользователей?
Другими словами, что происходит с переменными экземпляров?
5 ответ(ов)
Когда контейнер сервлетов (например, Apache Tomcat) запускается, он считывает файл web.xml (только один на приложение). Если что-то идет не так или появляется ошибка в консоли контейнера, это будет показано, иначе все веб-приложения развертываются и загружаются, используя веб.xml (так называемый дескриптор развертывания).
На этапе инстанцирования сервлета экземпляр сервлета готов, но не может обработать запрос клиента, поскольку ему не хватает двух кусочков информации:
контекстной информации
информации начальной конфигурации
Движок сервлетов создает объект интерфейса servletConfig, который инкапсулирует вышеуказанную недостающую информацию. Затем движок сервлетов вызывает метод init() сервлета, передавая ему ссылку на объект servletConfig в качестве аргумента. После полного выполнения init() сервлет готов обслуживать запросы клиентов.
Вопрос: Сколько раз происходит инстанцирование и инициализация сервлета в его жизненном цикле?
Ответ: Инстанцирование и инициализация происходят только один раз (для каждого клиентского запроса создается новый поток). Только один экземпляр сервлета обрабатывает любое количество клиентских запросов, то есть после обработки одного клиентского запроса сервер не умирает. Он ждет других клиентских запросов, что позволяет обойти ограничение CGI (для каждого клиентского запроса создается новый процесс) – вместо этого внутри движка сервлетов создается поток.
Вопрос: Как работает концепция сессий?
Ответ: Каждый раз, когда вызывается метод getSession() на объекте HttpServletRequest, происходят следующие шаги:
Шаг 1: Объект запроса проверяется на наличие входящего идентификатора сессии.
Шаг 2: Если идентификатор отсутствует, создается новый объект HttpSession и генерируется его соответствующий идентификатор сессии (например, из HashTable). Идентификатор сессии сохраняется в объекте ответа HttpServlet, и ссылка на объект HttpSession возвращается в сервлет (doGet/doPost).
Шаг 3: Если идентификатор доступен, новый объект сессии не создается. Идентификатор сессии извлекается из объекта запроса, и производится поиск в коллекции сессий с использованием идентификатора сессии в качестве ключа.
Когда поиск успешен, идентификатор сессии сохраняется в HttpServletResponse, и ссылки на существующие объекты сессий возвращаются в doGet() или doPost() UserDefinedServlet.
Замечания:
Когда управление переходит от кода сервлета к клиенту, не забывайте, что объект сессии удерживается контейнером сервлетов, то есть движком сервлетов.
Многопоточность остается на усмотрение разработчиков сервлетов. Они должны реализовать обработку нескольких запросов от клиентов, не беспокоясь о многопоточных кодах.
Кратко:
Сервлет создается, когда приложение запускается (он развертывается в контейнере сервлетов) или когда к нему впервые обращаются (в зависимости от настройки загрузки при старте). Когда сервлет инстанцируется, вызывается метод init() сервлета. Затем сервлет (его единственный экземпляр) обрабатывает все запросы (метод service() вызывается многими потоками). Поэтому не рекомендуется использовать синхронизацию в нем, и следует избегать экземплярных переменных сервлета. Когда приложение снимается с развертывания (контейнер сервлетов останавливается), вызывается метод destroy().
Сессии - что сказал Крис Томпсон.
Инстанциирование - сервлет инстанцируется, когда контейнер получает первый запрос, сопоставленный с этим сервлетом (если только сервлет не настроен на загрузку при старте с помощью элемента <load-on-startup>
в web.xml
). Один и тот же экземпляр используется для обработки последующих запросов.
Спецификация сервлетов JSR-315 четко определяет поведение веб-контейнера в методах обработки запросов (service, doGet, doPost, doPut и т. д.) в разделе 2.3.3.1, посвященном многопоточности (стр. 9):
Веб-контейнер может обрабатывать несколько запросов параллельно через метод service сервлета. Чтобы корректно обрабатывать такие запросы, разработчик сервлета должен обеспечить адекватное решение для параллельной обработки с использованием нескольких потоков в методе service.
Хотя это не рекомендуется, разработчик может реализовать интерфейс SingleThreadModel, который требует от контейнера гарантировать, что в методе service будет обработан только один поток-запрос за раз. Веб-контейнер может удовлетворять это требование, сериализуя запросы к сервлету или поддерживая пул экземпляров сервлета. Если сервлет является частью веб-приложения, помеченного как распределимое, контейнер может поддерживать пул экземпляров сервлета в каждой JVM, на которую распределяется приложение.
Для сервлетов, не реализующих интерфейс SingleThreadModel, если метод service (или методы, такие как doGet или doPost, которые перенаправляются в метод service абстрактного класса HttpServlet) определен с использованием ключевого слова synchronized, веб-контейнер не может использовать подход с пулом экземпляров, и должен сериализовать запросы через него. Настоятельно рекомендуется, чтобы разработчики не синхронизировали метод service (или методы, перенаправленные в него) в таких обстоятельствах из-за отрицательного влияния на производительность.
Таким образом, при проектировании сервлетов стоит учитывать возможности и ограничения многопоточности, чтобы избежать проблем с производительностью и обеспечить корректную обработку запросов.
Нет. Сервлеты не являются потокобезопасными.
Это означает, что они могут обслуживать несколько потоков одновременно.
Если вы хотите сделать сервлет потокобезопасным, вы можете использовать следующие подходы:
Реализуйте интерфейс
SingleThreadModel
(не путайте сSingleThreadInterface
— это неверно). Этот интерфейс является "пустым", в нем нет методов. Однако стоит отметить, что использование этого интерфейса устарело и в большинстве случаев не рекомендуется.Вы можете синхронизировать методы. Для этого вы можете пометить весь метод сервиса ключевым словом
synchronized
, чтобы обеспечить доступ только одного потока к этому методу в один момент времени.
Пример:
public synchronized void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// ваш код
}
Или вы можете использовать синхронизированный блок внутри метода.
Пример:
synchronized (this) {
// ---- Ваши инструкции ----
}
Я считаю, что использование синхронизированного блока предпочтительнее, чем синхронизация всего метода, так как это позволяет минимизировать область блокировки и улучшает производительность.
Как видно из вышеуказанных объяснений, реализуя SingleThreadModel, можно гарантировать безопасность потоков для сервлета со стороны контейнера сервлетов. Контейнер может сделать это двумя способами:
Сериализация запросов (очередь) для одного экземпляра - это аналогично тому, что сервлет не реализует SingleThreadModel, но синхронизирует методы service/doXXX; Либо
Создание пула экземпляров - что является лучшим вариантом и компромиссом между усилиями/временем на запуск и инициализацию сервлета и ограничениями (память/время ЦП) окружения, в котором размещен сервлет.
"implements Runnable" против "extends Thread" в Java: что выбрать?
Разница между "wait()" и "sleep()" в Java
Что значит 'synchronized'?
В чем разница между JSF, Servlet и JSP?
Что такое PECS (Producer Extends Consumer Super)?