Как ScopedProxy решает, какую сессию использовать?

Singleton не может автоматически связывать SessionBean, но ScopedProxy может.

Предполагая, что 100 пользователей одновременно имеют действительный сеанс в одном и том же приложении, как ScopedProxy решает, какой сеанс имеется в виду?

Я не думаю, что ScopedProxy выбирает какой-либо случайный сеанс, на мой взгляд, это было бы чепухой.

  1. Как ScopedProxy решает, какой сеанс использовать?
  2. Что делать, если у 0 пользователей есть сеанс? Произойдет ли NullPointerException?
  3. @Async — это другой поток, чем вызывающий поток обработки запросов. Как внедрить HttpRequest-Context в задачу Async?

person Grim    schedule 09.10.2015    source источник


Ответы (3)


ThreadLocal в значительной степени является ответом на ваш вопрос. находясь в поиске.

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

Spring имеет RequestContextHolder< /а>

Holder для предоставления веб-запроса в виде привязанного к потоку объекта RequestAttributes. Запрос будет унаследован любыми дочерними потоками, порожденными текущим потоком, если для наследуемого флага установлено значение true.

Внутри класса вы увидите следующее:

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");

И вот фактический сеттер (обратите внимание, что он статичен):

/**
     * Bind the given RequestAttributes to the current thread.
     * @param attributes the RequestAttributes to expose,
     * or {@code null} to reset the thread-bound context
     * @param inheritable whether to expose the RequestAttributes as inheritable
     * for child threads (using an {@link InheritableThreadLocal})
     */
    public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {}

Итак, как видите, никакой магии здесь нет, только переменные, специфичные для потока, предоставляемые ThreadLocal.

Если вам достаточно любопытно, вот реализация ThreadLocal.get (которая возвращает значение в текущей копии потока этой локальной переменной потока):

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

Как видите, он просто зависит от ThreadLocalMap:

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {

getEntry() выполняет поиск на карте. Надеюсь, теперь вы видите всю картину.

О потенциальном исключении NullPointerException

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

Я бы сказал, что это довольно большая проблема для ScopedProxy. Он действительно решает некоторые проблемы прозрачно (например, упрощает цепочку вызовов), но если вы не будете следовать правилам, вы, вероятно, получите java.lang.IllegalStateException: No thread-bound request found

(В справочной документации Spring Framework) говорится следующее:

DispatcherServlet, RequestContextListener и RequestContextFilter делают одно и то же, а именно привязывают объект HTTP-запроса к потоку, который обслуживает этот запрос. Это делает bean-компоненты с областью действия запроса и сеанса доступными дальше по цепочке вызовов.

Вы также можете проверить следующий вопрос: Доступ к компонентам области запроса в многопоточном веб-приложении

@Async и внедрение атрибутов запроса

Вообще говоря, нет простого способа решить проблему. Как показано ранее, у нас есть привязанные к потоку атрибуты RequestAttributes.

Потенциальное решение состоит в том, чтобы передать требуемый объект вручную и убедиться, что логика @Async учитывает это.

Немного более умное решение (предложенное Евгением Кулешовым) состоит в том, чтобы сделать это прозрачно. Скопирую код для упрощения чтения и поставлю ссылку под блоком кода.

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

/**
 * @author Eugene Kuleshov
 */
public abstract class RequestAwareRunnable implements Runnable {
  private final RequestAttributes requestAttributes;
  private Thread thread;

  public RequestAwareRunnable() {
    this.requestAttributes = RequestContextHolder.getRequestAttributes();
    this.thread = Thread.currentThread();
  }

  public void run() {
    try {
      RequestContextHolder.setRequestAttributes(requestAttributes);
      onRun();
    } finally {
      if (Thread.currentThread() != thread) {
        RequestContextHolder.resetRequestAttributes();
      }
      thread = null;
    }
  }

  protected abstract void onRun();
} 

Вот этот вопрос: Доступ к прокси-компонентам с ограниченной областью действия в потоках

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

Вот еще одна довольно интересная тема Аннотированный метод @Async, зависший в bean-компоненте с областью действия сеанса

person Renat Gilmanov    schedule 11.10.2015

Я сделаю очень простое объяснение

@Component
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
class YourScopedProxy {

public String dosomething() {
        return "Hello"; 
     }

}


@Component
class YourSingleton {
 @Autowired private YourScopedProxy meScopedProxy;

 public String usedosomething(){
  return this.meScopedProxy.dosomething();
 }
}


   1. How does the ScopedProxy decide what session to use?

we have 100 users

1 user (http session) call YourSingleton.usedosomething => call meScopedProxy :  
=> meScopedProxy  is not the YourScopedProxy  (original)  but a proxy to the YourScopedProxy
   and this proxy understands the scope 
=>   in this case : proxy get real 'YourScopedProxy' object from  HTTP Session  


   2. What if 0 users have a Session? Will a NullPointerException occur?
No because meScopedProxy is a proxy , when u use it 
=> proxy get real 'YourScopedProxy' object from  HTTP Session  
person question_maven_com    schedule 14.10.2015

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 самостоятельно.

Если вы контролируете, как вы отправляете потоки (или, скорее, Runnables), существуют методы, которые вы можете использовать для копирования контекста запроса, как описано здесь.


Вот несколько связанных сообщений о (сеансовых) областях и прокси:

person Sotirios Delimanolis    schedule 14.10.2015