Scope ‘session’ non è attivo per il thread corrente; IllegalStateException: nessuna richiesta legata al thread trovata

Ho un controller che mi piacerebbe essere unico per sessione. Secondo la documentazione di spring ci sono due dettagli per l’implementazione:

1. Configurazione web iniziale

Per supportare l’ambito dei bean a richiesta, sessione e livelli di sessione globali (fagioli con ambito Web), è richiesta una configurazione iniziale minore prima di definire i bean.

Ho aggiunto quanto segue al mio web.xml come mostrato nella documentazione:

   org.springframework.web.context.request.RequestContextListener   

2. Fagioli scoperti come dipendenze

    Se si desidera iniettare (ad esempio) un bean con scope della richiesta HTTP in un altro bean, è necessario iniettare un proxy AOP al posto del bean con scope.

    Ho annotato il bean con @Scope fornendo il proxyMode come mostrato di seguito:

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

    Problema

    Nonostante la configurazione di cui sopra, ottengo la seguente eccezione:

    org.springframework.beans.factory.BeanCreationException: errore nella creazione di bean con nome ‘scopedTarget.reportBuilder’: Scope ‘session’ non è attivo per il thread corrente; considerare la definizione di un proxy con scope per questo bean se si intende fare riferimento ad esso da un singleton; l’eccezione annidata è java.lang.IllegalStateException: Nessuna richiesta legata al thread trovata: ti stai riferendo alla richiesta di attributi al di fuori di una richiesta web effettiva o all’elaborazione di una richiesta al di fuori del thread di ricezione originale? Se si sta effettivamente operando all’interno di una richiesta Web e si continua a ricevere questo messaggio, il codice probabilmente è in esecuzione all’esterno di DispatcherServlet / DispatcherPortlet: In questo caso, utilizzare RequestContextListener o RequestContextFilter per esporre la richiesta corrente.

    Aggiornamento 1

    Di seguito è la mia scansione dei componenti. Ho il seguente in web.xml :

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

    E il seguente in AppConfig.java :

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

    Aggiornamento 2

    Ho creato un caso di test riproducibile. Questo è un progetto molto più piccolo, quindi ci sono differenze, ma succede lo stesso errore. Ci sono parecchi file, quindi l’ho caricato come tar.gz su megafileupload .

    Il problema non è nelle annotazioni Spring, ma nel modello di progettazione. Mescoli insieme diversi ambiti e thread:

    • singleton
    • sessione (o richiesta)
    • pool di lavori

    Il singleton è disponibile ovunque, è ok. Tuttavia, l’ambito sessione / richiesta non è disponibile al di fuori di un thread allegato a una richiesta.

    Il lavoro asincrono può essere eseguito anche se la richiesta o la sessione non esiste più, quindi non è ansible utilizzare un bean dipendente da richiesta / sessione. Inoltre non c’è modo di sapere, se stai facendo un lavoro in un thread separato, quale thread è la richiesta di originator (significa aop: proxy non è utile in questo caso).


    Penso che il tuo codice abbia l’aspetto di un contratto tra ReportController, ReportBuilder, UselessTask e ReportPage. C’è un modo per usare solo una semplice class (POJO) per memorizzare i dati da UselessTask e leggerli in ReportController o ReportPage e non utilizzare più ReportBuilder ?

    Se qualcun altro si è bloccato sullo stesso punto, in seguito ha risolto il mio problema.

    Nel web.xml

        org.springframework.web.context.request.RequestContextListener   

    Nel componente Session

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

    In pom.xml

       cglib cglib 3.1  

    Sto rispondendo alla mia domanda perché fornisce una migliore panoramica della causa e delle possibili soluzioni. Ho assegnato il bonus a @Martin perché ha indicato la causa.

    Causa

    Come suggerito da @ Martin, la causa è l’uso di più thread. L’object richiesta non è disponibile in questi thread, come indicato nella Guida di spring :

    DispatcherServlet , RequestContextListener e RequestContextFilter fanno esattamente la stessa cosa, ovvero legano l’object di richiesta HTTP al thread che sta servendo quella richiesta. Ciò rende i bean con scope di richiesta e di sessione disponibili più in basso nella catena di chiamate.

    Soluzione 1

    È ansible rendere l’object di richiesta disponibile per altri thread, ma pone un paio di limitazioni sul sistema, che potrebbero non essere utilizzabili in tutti i progetti. Ho ottenuto questa soluzione dall’accesso ai bean con scope della richiesta in un’applicazione web multi-thread :

    Sono riuscito a risolvere questo problema. Ho iniziato a utilizzare SimpleAsyncTaskExecutor invece di WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean . Il vantaggio è che SimpleAsyncTaskExecutor non utilizzerà mai i thread. Questa è solo la metà della soluzione. L’altra metà della soluzione consiste nell’utilizzare un RequestContextFilter anziché RequestContextListener . RequestContextFilter (così come DispatcherServlet ) ha una proprietà threadContextInheritable che consente fondamentalmente ai thread figlio di ereditare il contesto padre.

    Soluzione 2

    L’unica altra opzione è usare il bean con scope di sessione all’interno del thread di richiesta. Nel mio caso questo non è stato ansible perché:

    1. Il metodo del controller è annotato con @Async ;
    2. Il metodo del controller avvia un processo batch che utilizza thread per fasi di lavoro parallele.

    Come da documentazione :

    Se si accede ai bean con scope in Spring Web MVC, ovvero all’interno di una richiesta elaborata da Spring DispatcherServlet o DispatcherPortlet, non è necessaria alcuna installazione speciale: DispatcherServlet e DispatcherPortlet espongono già tutti gli stati pertinenti.

    Se si esegue all’esterno di Spring MVC (Non elaborato da DispatchServlet), è necessario utilizzare RequestContextListener non solo ContextLoaderListener .

    Aggiungi quanto segue nel tuo web.xml

        org.springframework.web.context.request.RequestContextListener   

    Ciò fornirà la sessione a Spring per mantenere i bean in tale ambito

    Aggiornamento: Come per altre risposte, @Controller sensibile solo quando ci si trova in Spring MVC Context, quindi @Controller non sta servendo lo scopo reale nel codice. Tuttavia è ansible iniettare i bean in qualsiasi posizione con ambito di sessione / ambito di richiesta (non è necessario Spring MVC / Controller per iniettare i bean in un ambito particolare).

    Aggiornamento: RequestContextListener espone la richiesta solo al thread corrente.
    Hai creato ReportBuilder in due punti

    1. ReportPage : è ansible vedere Spring ha iniettato correttamente il generatore di report in quanto siamo ancora nella stessa web thread. Ho cambiato l’ordine del tuo codice per assicurarmi che il ReportBuilder sia stato iniettato in ReportPage come questo.

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

    Sapevo che il registro dovrebbe andare dopo secondo la tua logica, solo per scopo di debug che ho aggiunto.

    2. UselessTasklet – Abbiamo ottenuto un’eccezione, qui perché questo è diverso thread creato da Spring Batch, in cui la richiesta non è esposta da RequestContextListener .

    Dovresti avere una logica diversa per creare e iniettare ReportBuilder istanza di ReportBuilder su Spring Batch (May Spring Batch Parameters e using Future puoi tornare per futuro riferimento)

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

    Per questo problema controlla la mia risposta nell’url sopra indicato

    Utilizzo di un bean con ambito richiesta all’esterno di una richiesta Web effettiva

    La mia risposta si riferisce a un caso particolare del problema generale descritto dall’OP, ma lo aggiungerò nel caso in cui aiuti qualcuno a uscire.

    Quando si utilizza @EnableOAuth2Sso , Spring inserisce un OAuth2RestTemplate nel contesto dell’applicazione e questo componente assume OAuth2RestTemplate legate al thread associate a thread.

    Il mio codice ha un metodo async pianificato che utilizza un RestTemplate RestTemplate . Questo non è in esecuzione all’interno di DispatcherServlet , ma Spring stava iniettando OAuth2RestTemplate , che ha prodotto l’errore che l’OP descrive.

    La soluzione era fare un’iniezione basata sul nome. Nella configurazione di Java:

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

    e nella class che lo usa:

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

    Ora Spring inietta il RestTemplate , progettato e privo di RestTemplate .

    Devi solo definire nel bean in cui hai bisogno di un ambito diverso rispetto all’ambito Singleton predefinito eccetto il prototipo. Per esempio: