Singleton не может автоматически связывать SessionBean, но ScopedProxy может.
Это утверждение несколько сбивает с толку. Следует переформулировать как
Spring не может внедрять bean-компоненты с областью действия сеанса в bean-компоненты с областью действия singleton, если только первые не определены как прокси (с областью действия).
Другими словами, Spring не сможет внедрить не проксируемый bean-компонент с областью действия сеанса в bean-компонент с одноэлементной областью. Ему удастся внедрить прокси-компонент с областью действия сеанса в компонент с областью действия-одиночкой.
Предполагая, что 100 пользователей одновременно имеют действительный сеанс в одном и том же приложении, как ScopedProxy решает, какой сеанс имеется в виду?
Первое, что нужно уточнить, это то, что сеанс является компонентом контейнера сервлетов, представленным HttpSession
. Spring (и Spring MVC) абстрагирует его с помощью bean-компонентов с областью действия сеанса (и других вещей, таких как атрибуты flash).
HttpSession
объекты обычно связываются с пользователем через соответствующие файлы cookie. HTTP-запрос предоставляет файл cookie со значением, идентифицирующим пользователя, а контейнер сервлета извлекает или создает связанный файл HttpSession
. Другими словами, сеанс идентифицируется по информации в запросе. Вам или Spring нужен доступ к запросу.
Очевидно, что Spring MVC имеет доступ к запросу через DispatcherServlet
, хотя обычно он не предоставляет его методам обработчика (помните, что Spring MVC пытается скрыть от вас API сервлета).
Ниже приведены более или менее детали реализации. Spring MVC вместо того, чтобы распространять объект запроса (HttpServletRequest
) по всему стеку вызовов, сохранит его в RequestContextHolder
.
Класс держателя для предоставления веб-запроса в виде связанного с потоком объекта RequestAttributes
.
Это возможно благодаря тому, что контейнеры сервлетов обычно (т. е. не асинхронно) обрабатывают запросы в одном потоке. Если вы выполняете код в этом потоке обработчика запросов, у вас есть доступ к запросу. И если у вас есть доступ к запросу, у вас есть доступ к HttpSession
.
Фактическая реализация довольно длинная. Если вы хотите углубиться в это, начните с SessionScope
и идите дальше.
Все это говорит о том, что Spring не внедряет объект конкретного типа bean-компонента, он внедряет прокси. В следующем примере используются прокси JDK (только интерфейсы), таково поведение прокси-серверов с областью действия сеанса. Данный
interface SessionScopedBean {...}
class SessionScopedBeanImpl implements SessionScopedBean {...}
Spring создаст прокси SessionScopedBean
аналогично (но гораздо более сложно) для
SessionScopedBean proxy = (SessionScopedBean) Proxy.newProxyInstance(Sample.class.getClassLoader(),
new Class<?>[] { SessionScopedBean.class }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HttpSession session = ...;// get session through RequestContextHolder
SessionScopedBean actual = session.getAttribute("some.bean.identifier");
if (actual == null) {
// if absent, set it
session.setAttribute("some.bean.identifier", actual = new SessionScopedBeanImpl());
}
return method.invoke(actual, args); // delegate to actual object
}
});
и внедрить этот объект proxy
в ваши одноэлементные компоненты (предположительно, в контроллер). Когда ваш singleton bean-компонент собирается использовать bean-компонент с областью действия сеанса, он фактически проходит через прокси-сервер, а прокси-сервер извлекает и делегирует вызов фактическому объекту.
Что делать, если у 0 пользователей есть сеанс? Произойдет ли NullPointerException
?
Spring вводит прокси. Прокси не null
. Это объект. Он знает, как получить и использовать реальную цель. В области сеанса, если цель не существует, она создается и сохраняется в сеансе (а затем используется).
Опасность здесь заключается в попытке использовать прокси на уровне сеанса вне контекста сеанса. Как говорилось ранее, весь этот трюк работает, потому что контейнеры сервлетов работают, обрабатывая один запрос в одном потоке. Если вы попытаетесь получить доступ к bean-компоненту с областью действия сеанса в потоке, где запрос не связан, вы получите исключение.
Таким образом, не пытайтесь передавать bean-компоненты с областью действия сеанса через границы потоков. Спецификация сервлета позволяет использовать Асинхронная обработка и Spring MVC поддерживает его с DefferedResult
и Callable
. Об этом есть серия блогов, здесь. Вы по-прежнему не можете обойти bean-компонент с областью действия сеанса. Однако если у вас есть ссылка на AsyncContext
, вы можете получить HttpServletRequest
и получить доступ к HttpSession
самостоятельно.
Если вы контролируете, как вы отправляете потоки (или, скорее, Runnable
s), существуют методы, которые вы можете использовать для копирования контекста запроса, как описано здесь.
Вот несколько связанных сообщений о (сеансовых) областях и прокси:
person
Sotirios Delimanolis
schedule
14.10.2015