Domanda 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 primavera 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 il seguente al mio web.xml come mostrato nella documentazione:

<listener>
  <listener-class>
    org.springframework.web.context.request.RequestContextListener
  </listener-class>
</listener>

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:

<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>

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 a megafileupload.


44
2018-01-22 15:11


origine


risposte:


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 è possibile 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 sembri quello che vuoi fare contrarre tra ReportController, ReportBuilder, UselessTask e ReportPage. C'è un modo per utilizzare solo una semplice classe (POJO) per archiviare i dati da UselessTask e leggerli in ReportController o ReportPage e non utilizzare ReportBuilder più?


41
2018-01-25 20:39



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

Nel web.xml

 <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
  </listener>

Nel componente Session

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

In pom.xml

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>

28
2017-10-12 17:21



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'oggetto richiesta non è disponibile in questi thread, come menzionato nel file Guida di primavera:

DispatcherServlet, RequestContextListener e RequestContextFilter tutti fanno esattamente la stessa cosa, cioè legano l'oggetto 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

È possibile rendere l'oggetto di richiesta disponibile per altri thread, ma pone un paio di limitazioni sul sistema, che potrebbero non essere utilizzabili in tutti i progetti. Ho avuto questa soluzione da Accesso ai bean con scope della richiesta in un'applicazione Web multi-thread:

Sono riuscito a risolvere questo problema. Ho iniziato a usare SimpleAsyncTaskExecutor invece di WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean. Il vantaggio è questo SimpleAsyncTaskExecutor non riutilizzerà mai i thread. Questa è solo la metà della soluzione. L'altra metà della soluzione è usare a RequestContextFilter invece di RequestContextListener. RequestContextFilter (così come DispatcherServlet) ha un threadContextInheritable proprietà che fondamentalmente consente ai thread figlio di ereditare il contesto genitore.

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 possibile 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.

24
2018-01-28 08:55



Come per documentazione:

Se si accede ai bean con scope in Spring Web MVC, ad esempio all'interno di una richiesta elaborata da Spring DispatcherServlet o DispatcherPortlet, non è necessaria alcuna installazione speciale: DispatcherServlet e DispatcherPortlet espongono già tutto lo stato pertinente.

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

Aggiungi quanto segue nel tuo web.xml

   <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
    </listener>        

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

Aggiornare :      Come per altre risposte, il @Controller solo sensibile quando ci si trova in Spring MVC Context, quindi @Controller non sta servendo lo scopo reale nel codice. Tuttavia è possibile 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).

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

 1. ReportPage - Puoi vedere Spring ha iniettato correttamente il generatore di rapporti, perché siamo ancora nella stessa discussione web. 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 a Spring Batch (May Spring Batch Parameters e utilizzo Future<ReportBuilder> puoi tornare per futuro riferimento)


7
2018-01-26 00:55



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


2
2018-06-04 09:48



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 usa @EnableOAuth2Sso, La primavera mette un OAuth2RestTemplate nel contesto dell'applicazione, e questo componente assume supposizioni legate al thread legate al thread.

Il mio codice ha un metodo asincrono programmato che utilizza un'autowired RestTemplate. Questo non sta funzionando dentro DispatcherServlet, ma la primavera stava iniettando il 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 classe che lo usa:

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

Ora Spring inietta l'inteso, privo di servlet RestTemplate.


1
2018-04-23 20:25



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

<bean id="shoppingCart" 
   class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
   <aop:scoped-proxy/> 
</bean>

0
2017-07-30 02:24