Область 'сессия' не активна для текущего потока; IllegalStateException: не найден связанный с потоком запрос

У меня есть контроллер, который я хотел бы быть уникальным для каждой сессии. Согласно весенней документации есть две детали реализации:

1. Начальная веб-конфигурация

Чтобы поддерживать область действия bean-компонентов на уровне запросов, сеансов и глобальных сеансов (bean-объекты bean-scoped), перед определением ваших bean-компонентов требуется небольшая начальная конфигурация.

Я добавил следующее к моему web.xml как показано в документации:

<listener>
  <listener-class>
    org.springframework.web.context.request.RequestContextListener
  </listener-class>
</listener>

2. Область применения бобов как зависимости

Если вы хотите внедрить (например) компонент области действия HTTP-запроса в другой компонент, вы должны внедрить прокси-сервер AOP вместо этого компонента.

Я аннотировал боб @Scope предоставляя proxyMode как показано ниже:

@Controller
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class ReportBuilder implements Serializable {
    ...
    ...
}

проблема

Несмотря на вышеуказанную конфигурацию, я получаю следующее исключение:

org.springframework.beans.factory.BeanCreationException: Ошибка создания бина с именем 'scopedTarget.reportBuilder': область 'сеанс' не активна для текущего потока; рассмотрите возможность определения прокси-объекта с областью видимости для этого компонента, если вы намереваетесь ссылаться на него из синглтона; Вложенное исключение - java.lang.IllegalStateException: не найден привязанный к потоку запрос: Вы ссылаетесь на атрибуты запроса вне фактического веб-запроса или обрабатываете запрос вне первоначально получающего потока? Если вы на самом деле работаете с веб-запросом и по-прежнему получаете это сообщение, ваш код, вероятно, выполняется за пределами DispatcherServlet/DispatcherPortlet: в этом случае используйте RequestContextListener или RequestContextFilter для предоставления текущего запроса.

Обновление 1

Ниже мой компонент сканирования. У меня есть следующее в web.xml:

<context-param>
  <param-name>contextClass</param-name>
  <param-value>
    org.springframework.web.context.support.AnnotationConfigWebApplicationContext
  </param-value>
</context-param>

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>org.example.AppConfig</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

И следующее в AppConfig.java:

@Configuration
@EnableAsync
@EnableCaching
@ComponentScan("org.example")
@ImportResource("classpath:applicationContext.xml")
public class AppConfig implements AsyncConfigurer {
  ...
  ...
}

Обновление 2

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

7 ответов

Решение

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

причина

Как предполагает @Martin, причиной является использование нескольких потоков. Объект запроса недоступен в этих потоках, как указано в руководстве по Spring:

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

Решение 1

Можно сделать объект запроса доступным для других потоков, но это накладывает на систему пару ограничений, которые могут не работать во всех проектах. Я получил это решение от Access bean-объекта bean-объекта в многопоточном веб-приложении:

Мне удалось обойти эту проблему. Я начал использовать SimpleAsyncTaskExecutor вместо WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean, Преимущество в том, что SimpleAsyncTaskExecutor никогда не будет повторно использовать темы. Это только половина решения. Другая половина решения заключается в использовании RequestContextFilter вместо RequestContextListener, RequestContextFilter (так же как DispatcherServlet) имеет threadContextInheritable свойство, которое в основном позволяет дочерним потокам наследовать родительский контекст.

Решение 2

Единственный другой вариант - использовать сессионный компонент внутри потока запроса. В моем случае это было невозможно, потому что:

  1. Метод контроллера аннотируется @Async;
  2. Метод контроллера запускает пакетное задание, которое использует потоки для параллельных шагов задания.

Проблема не в ваших весенних аннотациях, а в вашем шаблоне дизайна. Вы смешиваете разные сферы и темы:

  • одиночка
  • сеанс (или запрос)
  • пул потоков заданий

Синглтон доступен везде, это нормально. Однако область сеанса / запроса недоступна вне потока, который присоединен к запросу.

Асинхронное задание может запускать даже запрос или сеанс больше не существует, поэтому невозможно использовать компонент, зависящий от запроса / сеанса. Также нет способа узнать, если вы выполняете задание в отдельном потоке, какой поток является запросом отправителя (это означает, что aop: в этом случае прокси не полезен).


Я думаю, что ваш код выглядит так, что вы хотите заключить договор между ReportController, ReportBuilder, UselessTask и ReportPage. Есть ли способ использовать простой класс (POJO) для хранения данных из UselessTask и чтения их в ReportController или ReportPage и больше не использовать ReportBuilder?

Если кто-то еще застрял в той же точке, следуя, решил мою проблему.

В web.xml

 <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
  </listener>

В компоненте Session

@Component
@Scope(value = "session",  proxyMode = ScopedProxyMode.TARGET_CLASS)

В pom.xml

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>

Я исправил эту проблему, добавив следующий код в мой файл.

@Component
@Scope(value = "session",  proxyMode = ScopedProxyMode.TARGET_CLASS)

Конфигурация XML -

<listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener 
        </listener-class>
</listener>

Выше мы можем сделать с помощью конфигурации Java -

@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}

Как добавить RequestContextListener с конфигурацией no-xml?

Я использую весеннюю версию 5.1.4.RELEASE и нет необходимости добавлять ниже изменения в pom.

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.10</version>
</dependency>

Согласно документации:

Если вы обращаетесь к bean-объектам области действия в Spring Web MVC, то есть в запросе, который обрабатывается Spring DispatcherServlet или DispatcherPortlet, то никакой специальной настройки не требуется: DispatcherServlet и DispatcherPortlet уже предоставляют все соответствующие состояния.

Если вы работаете вне Spring MVC (не обрабатывается DispatchServlet), вы должны использовать RequestContextListener Не просто ContextLoaderListener,

Добавьте следующее в ваш web.xml

   <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
    </listener>        

Это обеспечит сессию для Spring, чтобы поддерживать бины в этой области

Обновление: согласно другим ответам, @Controller только разумно, когда вы работаете в Spring MVC Context, поэтому @Controller не служит реальной цели в вашем коде. Тем не менее, вы можете внедрить ваши bean-компоненты в любое место с помощью области видимости сеанса / запроса (вам не нужен Spring MVC / Controller, чтобы просто вводить bean-компоненты в определенной области видимости) .

Обновление: RequestContextListener предоставляет запрос только текущему потоку.
Вы автоматически подключили ReportBuilder в двух местах

1. ReportPage - Вы можете видеть, что Spring правильно внедрил построитель отчетов здесь, потому что мы все еще находимся в той же веб-теме. я изменил порядок вашего кода, чтобы убедиться, что ReportBuilder внедрен в ReportPage, как это.

log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();

я знал, что журнал должен идти в соответствии с вашей логикой, просто для целей отладки я добавил.


2. UselessTasklet - Мы получили исключение, потому что это другой поток, созданный Spring Batch, где запрос не предоставляется RequestContextListener,


Вы должны иметь другую логику для создания и введения ReportBuilder экземпляр для Spring Batch (майские параметры Spring Batch и использование Future<ReportBuilder> вы можете вернуться для дальнейшего использования)

/questions/43686704/ispolzovanie-bean-obekta-oblasti-dejstviya-vne-realnogo-veb-zaprosa/43686720#43686720

Для этого вопроса проверьте мой ответ по указанному выше URL

Использование bean-объекта области действия вне реального веб-запроса

Мой ответ относится к частному случаю общей проблемы, описываемой ОП, но я добавлю ее на всякий случай, если она кому-нибудь поможет.

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

Мой код имеет запланированный асинхронный метод, который использует автопровод RestTemplate, Это не работает внутри DispatcherServletВесной вводила OAuth2RestTemplate, который произвел ошибку, которую описывает OP.

Решением было сделать инъекцию на основе имени. В конфиге Java:

@Bean
public RestTemplate pingRestTemplate() {
    return new RestTemplate();
}

и в классе, который его использует:

@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;

Теперь Spring вводит желаемое, без сервлетов RestTemplate,

Была такая же ошибка, когда у меня была @Orderаннотация к классу фильтра. Даже ты добавил фильтр через HttpSecurity цепь.

Удалено @Order и это сработало.

Вам просто нужно определить в своем bean-компоненте, где вам нужна область, отличная от одноэлементной области по умолчанию, кроме прототипа. Например:

<bean id="shoppingCart" 
   class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
   <aop:scoped-proxy/> 
</bean>
Другие вопросы по тегам