Scope 'session' неактивен для текущего потока; IllegalStateException: запрос на привязку не найден
У меня есть контроллер, который хотел бы быть уникальным для каждого сеанса. Согласно документации spring для реализации есть две детали:
1. Начальная веб-конфигурация
Чтобы поддерживать область охвата beans на уровнях запросов, сеансов и глобальных сеансов (веб-область beans), перед определением вашего beans требуется некоторая младшая первоначальная конфигурация.
Я добавил следующее к моему web.xml
, как показано в документации:
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
2. Области beans как зависимости
Если вы хотите ввести (например) HTTP-запрос с областью bean в другой bean, вы должны ввести прокси-сервер AOP вместо области bean.
Я аннотировал bean с @Scope
, предоставляя proxyMode
, как показано ниже:
@Controller
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class ReportBuilder implements Serializable {
...
...
}
Проблема
Несмотря на указанную выше конфигурацию, я получаю следующее исключение:
org.springframework.beans.factory.BeanCreationException: ошибка при создании bean с именем 'scopedTarget.reportBuilder': Scope 'session' неактивен для текущего потока; рассмотрите определение прокси-сервера с ограниченным доступом для этого bean, если вы собираетесь ссылаться на него из одноэлементного; Вложенное исключение - это 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
в megafileupload.
Ответы
Ответ 1
Я отвечаю на свой вопрос, потому что он дает лучший обзор причины и возможных решений. Я наградил бонус @Martin за то, что он указал причину.
Причина
Как было предложено @Martin, причиной является использование нескольких потоков. Объект запроса недоступен в этих потоках, как указано в Spring Guide:
DispatcherServlet
, RequestContextListener
и RequestContextFilter
все делают точно то же самое, а именно связывают объект HTTP-запроса с Thread, который обслуживает этот запрос. Это делает beans доступными для запроса и сеанса далее по цепочке вызовов.
Решение 1
Можно сделать объект запроса доступным для других потоков, но он накладывает пару ограничений на систему, что может не работать во всех проектах. Я получил это решение из Доступ к области запросов beans в многопоточном веб-приложении:
Мне удалось обойти эту проблему. Я начал использовать SimpleAsyncTaskExecutor
вместо WorkManagerTaskExecutor
/ThreadPoolExecutorFactoryBean
. Преимущество состоит в том, что SimpleAsyncTaskExecutor
никогда не будет повторно использовать потоки. Это только половина решения. Другая половина решения - использовать RequestContextFilter
вместо RequestContextListener
. RequestContextFilter
(а также DispatcherServlet
) имеет свойство threadContextInheritable
, которое в основном позволяет дочерним потокам наследовать родительский контекст.
Решение 2
Единственным другим вариантом является использование области с ограниченным сеансом bean внутри потока запросов. В моем случае это было невозможно, потому что:
- Метод контроллера аннотируется с помощью
@Async
;
- Метод контроллера запускает пакетное задание, которое использует потоки для параллельных шагов задания.
Ответ 2
Проблема не в ваших аннотациях Spring, а в шаблоне дизайна. Вы смешиваете разные области и темы:
- singleton
- сеанс (или запрос)
- поток пулов заданий
Синглтон доступен в любом месте, это нормально. Однако область сеанса/запроса недоступна вне потока, прикрепленного к запросу.
Асинхронное задание может выполняться, даже если запрос или сеанс больше не существует, поэтому невозможно использовать зависимый от запроса/сеанса bean. Также нет никакого способа узнать, если вы выполняете задание в отдельном потоке, какой поток является запросом оригинатора (это означает, что в этом случае не рекомендуется использовать прокси-сервер).
Я думаю, что ваш код выглядит так, что вы хотите заключить контракт между ReportController, ReportBuilder, UselessTask и ReportPage. Есть ли способ использовать простой класс (POJO) для хранения данных из UselessTask и читать его в ReportController или ReportPage и больше не использовать ReportBuilder?
Ответ 3
Если кто-то еще застрял в той же точке, после решения моей проблемы.
В web.xml
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
В компоненте сеанса
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
В pom.xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
Ответ 4
Как для документации:
Если вы получаете доступ к области beans внутри Spring веб-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, чтобы поддерживать beans в этой области
Обновление: В соответствии с другими ответами, @Controller
только разумно, когда вы находитесь в Spring Контекст MVC, поэтому @Controller не служит для фактической цели в вашем коде. Тем не менее вы можете вставить свой beans в любое место с областью видимости/запроса сеанса (вам не нужно Spring MVC/Controller, чтобы просто вставить beans в определенную область).
Обновление: RequestContextListener предоставляет запрос только для текущего потока.
У вас есть автоответчик ReportBuilder в двух местах
1. ReportPage
- Вы можете видеть, что Spring правильно ввел построитель отчетов, потому что мы все еще находимся в одной и той же веб-теме. я изменил порядок вашего кода, чтобы убедиться, что ReportBuilder введен в ReportPage, как это.
log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();
Я знал, что журнал должен идти по логике, только для цели отладки, которую я добавил.
2. UselessTasklet
- У нас есть исключение, потому что это другой поток, созданный пакетом Spring Batch, где Request не отображается RequestContextListener
.
У вас должна быть другая логика для создания и вставки ReportBuilder
экземпляра в Spring Пакет (май Spring Пакетные параметры и использование Future<ReportBuilder>
, вы можете вернуться для дальнейшего использования)
Ответ 5
fooobar.com/questions/136879/...
Для этого вопроса проверьте мой ответ выше, указанный url
Использование области запроса bean вне фактического веб-запроса
Ответ 6
Мой ответ относится к частному случаю общей проблемы, описываемой ОП, но я добавлю его на всякий случай, если он поможет кому-то.
При использовании @EnableOAuth2Sso
, Spring помещает OAuth2RestTemplate
в контекст приложения, и этот компонент принимает связанные с привязкой к потоку данные, связанные с сервлетом.
Мой код имеет запланированный асинхронный метод, который использует autwired RestTemplate
. Это не работает внутри DispatcherServlet
, но Spring вводил OAuth2RestTemplate
, который вызывал ошибку, описанную OP.
Решение заключалось в том, чтобы сделать инъекцию на основе имени. В конфигурации Java:
@Bean
public RestTemplate pingRestTemplate() {
return new RestTemplate();
}
и в классе, который его использует:
@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;
Теперь Spring вводит предполагаемый, без сервлета RestTemplate
.
Ответ 7
Вам просто нужно определить в bean, где вам нужна другая область действия, чем область одиночного Singleton, кроме прототипа. Например:
<bean id="shoppingCart"
class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
<aop:scoped-proxy/>
</bean>