Подтвердить что ты не робот

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.

4b9b3361

Ответ 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>