Область 'сессия' не активна для текущего потока; 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
Единственный другой вариант - использовать сессионный компонент внутри потока запроса. В моем случае это было невозможно, потому что:
- Метод контроллера аннотируется
@Async
; - Метод контроллера запускает пакетное задание, которое использует потоки для параллельных шагов задания.
Проблема не в ваших весенних аннотациях, а в вашем шаблоне дизайна. Вы смешиваете разные сферы и темы:
- одиночка
- сеанс (или запрос)
- пул потоков заданий
Синглтон доступен везде, это нормально. Однако область сеанса / запроса недоступна вне потока, который присоединен к запросу.
Асинхронное задание может запускать даже запрос или сеанс больше не существует, поэтому невозможно использовать компонент, зависящий от запроса / сеанса. Также нет способа узнать, если вы выполняете задание в отдельном потоке, какой поток является запросом отправителя (это означает, что 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>
вы можете вернуться для дальнейшего использования)
Для этого вопроса проверьте мой ответ по указанному выше 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>