J'ai un contrôleur que j'aimerais être unique par session. Selon la documentation de printemps, la mise en œuvre comporte deux détails:
1. Configuration Web initiale
Pour prendre en charge la définition des beans aux niveaux de la demande, de la session et de la session globale (beans web), une configuration initiale mineure est requise avant de définir vos beans.
J'ai ajouté les éléments suivants à mon web.xml
, comme indiqué dans la documentation:
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
2. Scoped haricots comme dépendances
Si vous souhaitez injecter (par exemple) un bean périmé de requête HTTP dans un autre bean, vous devez injecter un proxy AOP à la place du bean paramétré.
J'ai annoté le bean avec @Scope
fournissant la 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 d'un bean avec le nom 'scopedTarget.reportBuilder': Scope 'session' n'est pas actif pour le thread actuel; Pensez à définir un proxy délimité pour ce bean si vous souhaitez le référencer à partir d'un singleton. L'exception imbriquée est Java.lang.IllegalStateException: aucune requête liée à un thread n'a été trouvée: faites-vous référence à des attributs de requête en dehors d'une requête Web réelle ou traitez-vous une requête en dehors du thread à l'origine de la réception? Si vous opérez réellement dans 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 requête en cours.
Mise à jour 1
Vous trouverez ci-dessous l'analyse de mes composants. J'ai les éléments suivants dans 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>
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, donc il y a des différences, mais la même erreur se produit. Il y a pas mal de fichiers, je l'ai donc téléchargé en tant que tar.gz
dans megafileupload .
Je réponds à ma propre question car elle donne un meilleur aperçu de la cause et des solutions possibles. J'ai attribué le bonus à @Martin parce qu'il a identifié la cause.
Cause
Comme suggéré par @Martin, la cause en est l'utilisation de plusieurs threads. L'objet de requête n'est pas disponible dans ces threads, comme indiqué dans le Spring Guide :
DispatcherServlet
,RequestContextListener
etRequestContextFilter
font tous exactement la même chose, à savoir lier l'objet de requête HTTP au thread qui traite cette requête. Ainsi, les beans dont la portée est définie par les requêtes et les sessions sont disponibles plus loin dans la chaîne d'appels.
Solution 1
Il est possible de rendre l'objet de requête disponible pour d'autres threads, mais cela impose quelques limitations au système, qui peuvent ne pas être exploitables dans tous les projets. J'ai obtenu cette solution de accès aux beans périmés à une requête dans une application Web multi-thread)
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. Ce n'est que la moitié de la solution. L'autre moitié de la solution consiste à utiliser unRequestContextFilter
au lieu deRequestContextListener
.RequestContextFilter
(ainsi queDispatcherServlet
) a une propriététhreadContextInheritable
qui permet fondamentalement aux fils enfants d'hériter du contexte parent.
_/Solution 2
La seule autre option consiste à utiliser le bean périmé de session dans le thread de demande. Dans mon cas, cela n'a pas été possible parce que:
@Async
;Le problème ne réside pas dans vos annotations Spring, mais dans votre modèle de conception. Vous mélangez différents domaines et thèmes:
Le singleton est disponible n'importe où, c'est ok. Cependant, l'étendue de la session/demande n'est pas disponible en dehors d'un thread attaché à une demande.
Le travail asynchrone peut être exécuté même si la demande ou la session n'existe plus, il n'est donc pas possible d'utiliser un bean dépendant de la demande/session. En outre, il n'existe aucun moyen de savoir, si vous exécutez un travail dans un thread séparé, quel thread est la demande de l'expéditeur (cela signifie que 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. Existe-t-il un moyen d'utiliser uniquement une classe simple (POJO) pour stocker des données de UselessTask et les lire dans ReportController ou ReportPage et ne plus utiliser ReportBuilder ?
Si quelqu'un d'autre s'en tenait au même point, le problème suivant était résolu.
Dans web.xml
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
Composant en session
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
Dans pom.xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
Comme par documentation :
Si vous accédez à des beans scoped dans Spring Web MVC, c’est-à-dire dans une demande traitée par Spring DispatcherServlet ou DispatcherPortlet, aucune configuration spéciale 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 la variable RequestContextListener
et pas seulement ContextLoaderListener
.
Ajouter ce qui suit dans votre web.xml
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
Cela fournira une session à Spring afin de maintenir les haricots dans cette portée
Mettre à jour : Comme dans d’autres réponses, le @Controller
n’est utile que lorsque vous vous trouvez dans le contexte Spring MVC. Par conséquent, le @Controller ne sert pas à quoi que ce soit dans votre code. Vous pouvez toujours injecter vos beans dans n’importe quel endroit avec une étendue de session/une étendue de session (vous n’avez pas besoin de Spring MVC/Controller pour simplement injecter des beans dans une étendue particulière) .
Mettre à jour : RequestContextListener expose uniquement la requête au thread actuel.
Vous avez auto-câblé ReportBuilder à deux endroits
1. ReportPage
- Vous pouvez voir que Spring a correctement injecté le générateur de rapports ici, car nous sommes toujours dans le même fil Web. J'ai changé l'ordre de votre code pour m'assurer que ReportBuilder a été injecté dans ReportPage comme ceci.
log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();
je savais que le journal devrait aller après selon votre logique, juste pour des fins de débogage, j’ai ajouté.
2. UselessTasklet
- Nous avons une exception, car il s'agit d'un thread différent créé par Spring Batch, où la demande n'est pas exposée par RequestContextListener
.
Vous devez avoir une logique différente pour créer et injecter une instance ReportBuilder
dans Spring Batch (paramètres de batch Spring, et avec Future<ReportBuilder>
, vous pouvez y retourner pour référence future)
https://stackoverflow.com/a/30640097/2569475
Pour cette question, cochez Ma réponse à l’URL indiquée ci-dessus.
Utilisation d'un haricot 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 le PO, mais je l’ajouterai au cas où cela aiderait quelqu'un.
Lors de l'utilisation de @EnableOAuth2Sso
, Spring place un OAuth2RestTemplate
sur le contexte de l'application et ce composant assume par conséquent des tâches liées aux servlets liées aux threads.
Mon code a une méthode asynchrone planifiée qui utilise une RestTemplate
à virement automatique. Cela ne fonctionne pas dans DispatcherServlet
, mais Spring injectait le OAuth2RestTemplate
, ce qui a généré l'erreur décrite par l'OP.
La solution consistait à faire l'injection nominative. 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 la variable RestTemplate
prévue, sans servlet.
Vous devez simplement définir dans votre bean où vous avez besoin d'une étendue différente de celle par défaut du singleton, à l'exception du prototype. Par exemple:
<bean id="shoppingCart"
class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
<aop:scoped-proxy/>
</bean>
J'ai résolu ce problème en ajoutant le code suivant dans mon fichier.
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
Configuration XML -
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
Ci-dessus, nous pouvons utiliser la configuration Java -
@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}
Comment ajouter un RequestContextListener avec une configuration no-xml?
J'utilise la version de printemps 5.1.4.RELEASE et inutile pour ajouter les modifications ci-dessous dans pom.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>