Scope ‘session’ n’est pas actif pour le thread en cours; IllegalStateException: aucune demande liée à un thread n’a été trouvée

J’ai un contrôleur que je voudrais être unique par session. Selon la documentation du spring, la mise en œuvre comporte deux détails:

1. Configuration Web initiale

Pour prendre en charge l’étendue des beans au niveau de la requête, de la session et de la session globale (beans Web-Portée), une configuration initiale mineure est requirejse avant de définir vos beans.

J’ai ajouté les éléments suivants à mon web.xml comme indiqué dans la documentation:

   org.springframework.web.context.request.RequestContextListener   

2. Les haricots couverts comme dépendances

Si vous voulez injecter (par exemple) un bean de requête HTTP dans un autre bean, vous devez injecter un proxy AOP à la place du bean de scope.

J’ai annoté le bean avec @Scope fournissant le proxyMode comme indiqué ci-dessous:

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

Problème

Malgré la configuration ci-dessus, j’obtiens l’exception suivante:

org.springframework.beans.factory.BeanCreationException: Erreur lors de la création du bean avec le nom ‘scopedTarget.reportBuilder’: La scope ‘session’ n’est pas active pour le thread en cours; envisagez de définir un proxy de scope pour ce bean si vous envisagez d’y faire référence à partir d’un singleton; l’exception nestede est java.lang.IllegalStateException: aucune demande liée à un thread n’a été trouvée: vous faites référence à des atsortingbuts de requête en dehors d’une requête Web réelle ou vous traitez une requête en dehors du thread destinataire initial? Si vous utilisez réellement une requête Web et recevez toujours ce message, votre code s’exécute probablement en dehors de DispatcherServlet / DispatcherPortlet: dans ce cas, utilisez RequestContextListener ou RequestContextFilter pour exposer la demande en cours.

Mise à jour 1

Ci-dessous est mon composant scan. J’ai les éléments suivants dans web.xml :

  contextClass  org.springframework.web.context.support.AnnotationConfigWebApplicationContext    contextConfigLocation org.example.AppConfig   org.springframework.web.context.ContextLoaderListener  

Et ce qui suit dans AppConfig.java :

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

Mise à jour 2

J’ai créé un cas de test reproductible. C’est un projet beaucoup plus petit, il y a donc des différences, mais la même erreur se produit. Il y a pas mal de fichiers, donc je les ai téléchargés en tant que tar.gz vers megafileupload .

Le problème ne réside pas dans vos annotations de spring mais dans votre modèle de conception. Vous mélangez différents scopes et fils:

  • singleton
  • session (ou demande)
  • pool de jobs

Le singleton est disponible n’importe où, ça va. Cependant, l’étendue de la session / demande n’est pas disponible en dehors d’un thread associé à une requête.

Le travail asynchrone peut exécuter même la demande ou la session n’existe plus, il n’est donc pas possible d’utiliser un bean dépendant de la requête / session. Il n’y a pas non plus de moyen de savoir, si vous exécutez un travail dans un thread séparé, quel thread est la requête d’origine (cela signifie aop: le proxy n’est pas utile dans ce cas).


Je pense que votre code ressemble à ce que vous voulez faire un contrat entre ReportController, ReportBuilder, UselessTask et ReportPage. Est-il possible d’utiliser une simple classe (POJO) pour stocker les données d’UselessTask et les lire dans ReportController ou ReportPage sans utiliser ReportBuilder ?

Si quelqu’un d’autre restait sur le même point, la suite du problème a été résolue.

Dans web.xml

    org.springframework.web.context.request.RequestContextListener   

Composant In Session

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

Dans pom.xml

   cglib cglib 3.1  

Je réponds à ma propre question car elle fournit un meilleur aperçu de la cause et des solutions possibles. J’ai atsortingbué le bonus à @Martin parce qu’il a souligné la cause.

Cause

Comme suggéré par @Martin, la cause est l’utilisation de plusieurs threads. L’object request n’est pas disponible dans ces threads, comme indiqué dans le Guide de Spring :

DispatcherServlet , RequestContextListener et RequestContextFilter font tous exactement la même chose, à savoir lier l’object de requête HTTP au thread qui traite cette requête. Cela rend les beans qui sont à la demande et à la session disponibles plus loin dans la chaîne d’appels.

Solution 1

Il est possible de rendre l’object de demande disponible pour les autres threads, mais cela impose quelques limitations au système, ce qui peut ne pas être possible dans tous les projets. J’ai obtenu cette solution en accédant à des demandes de haricots dans une application Web multithread :

J’ai réussi à contourner ce problème. J’ai commencé à utiliser SimpleAsyncTaskExecutor au lieu de WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean . L’avantage est que SimpleAsyncTaskExecutor ne réutilisera jamais les threads. C’est seulement la moitié de la solution. L’autre moitié de la solution consiste à utiliser un RequestContextFilter au lieu de RequestContextListener . RequestContextFilter (ainsi que DispatcherServlet ) possède une propriété threadContextInheritable qui permet essentiellement aux threads enfants d’hériter le contexte parent.

Solution 2

La seule autre option consiste à utiliser le bean de scope de session dans le thread de requête. Dans mon cas, ce n’était pas possible parce que:

  1. La méthode du contrôleur est annotée avec @Async ;
  2. La méthode du contrôleur démarre un travail par lots qui utilise des threads pour les étapes de travail parallèles.

Selon la documentation :

Si vous accédez à des beans de scope dans Spring Web MVC, c’est-à-dire dans une requête traitée par Spring DispatcherServlet ou DispatcherPortlet, aucune configuration particulière n’est nécessaire: DispatcherServlet et DispatcherPortlet exposent déjà tous les états pertinents.

Si vous exécutez en dehors de Spring MVC (non traité par DispatchServlet), vous devez utiliser ContextLoaderListener pas seulement ContextLoaderListener .

Ajoutez ce qui suit dans votre web.xml

    org.springframework.web.context.request.RequestContextListener   

Cela fournira une session au spring afin de maintenir les haricots dans cette scope

Mise à jour: selon d’autres réponses, le @Controller n’est sensible que lorsque vous êtes dans Spring MVC Context, donc le @Controller n’est pas utilisé dans votre code. Cependant, vous pouvez injecter vos beans dans n’importe quel endroit avec une scope / requête étendue (vous n’avez pas besoin de Spring MVC / Controller pour injecter des beans en particulier).

Update: RequestContextListener expose la requête uniquement au thread en cours.
Vous avez automatiquement envoyé ReportBuilder à deux endroits

1. ReportPage – Vous pouvez voir que Spring a correctement injecté le générateur de rapports ici, car nous sums toujours dans le même thread Web. J’ai changé l’ordre de votre code pour m’assurer que le ReportBuilder a injecté dans ReportPage comme ceci.

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

Je savais que le journal devrait suivre selon votre logique, juste pour le but de débogage que j’ai ajouté.

2. UselessTasklet – Nous avons obtenu une exception, car il s’agit d’un thread différent créé par Spring Batch, où la requête n’est pas exposée par RequestContextListener .

Vous devez avoir une logique différente pour créer et injecter l’instance ReportBuilder dans Spring Batch (vous pouvez renvoyer les parameters de lot Spring et utiliser Future pour référence ultérieure)

https://stackoverflow.com/a/30640097/2569475

Pour ce problème, cochez Ma réponse ci-dessus

Utilisation d’un bean de requête en dehors d’une requête Web réelle

Ma réponse fait référence à un cas particulier du problème général décrit par l’OP, mais je l’appendai au cas où cela aiderait quelqu’un.

Lorsque vous utilisez @EnableOAuth2Sso , Spring place un OAuth2RestTemplate sur le contexte de l’application et ce composant se trouve à prendre en charge des éléments liés aux servlets liés aux threads.

Mon code a une méthode asynchrone planifiée qui utilise un RestTemplate RestTemplate . Cela ne s’exécute pas dans DispatcherServlet , mais Spring injecte OAuth2RestTemplate , ce qui produit l’erreur décrite par l’OP.

La solution était de faire l’injection basée sur le nom. Dans la configuration Java:

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

et dans la classe qui l’utilise:

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

Maintenant, Spring injecte le RestTemplate sans RestTemplate .

Vous avez juste besoin de définir dans votre bean où vous avez besoin d’une scope différente de la scope singleton par défaut à l’exception du prototype. Par exemple: