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:
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
etRequestContextFilter
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 deWorkManagerTaskExecutor
/ThreadPoolExecutorFactoryBean
. L’avantage est queSimpleAsyncTaskExecutor
ne réutilisera jamais les threads. C’est seulement la moitié de la solution. L’autre moitié de la solution consiste à utiliser unRequestContextFilter
au lieu deRequestContextListener
.RequestContextFilter
(ainsi queDispatcherServlet
) 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:
@Async
; 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: