spring - La ''sesión'' del alcance no está activa para el hilo actual; IllegalStateException: no se ha encontrado ninguna solicitud vinculada
wicket (7)
Tengo un controlador que me gustaría ser único por sesión. De acuerdo con la documentación de primavera, hay dos detalles para la implementación:
1. Configuración web inicial
Para admitir el ámbito de los beans a petición, sesión y niveles de sesión globales (beans con ámbito web), se requiere una configuración inicial menor antes de definir los beans.
He agregado lo siguiente a mi web.xml
como se muestra en la documentación:
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
2. Habas con alcance como dependencias
Si desea inyectar (por ejemplo) un bean con ámbito de solicitud HTTP en otro bean, debe inyectar un proxy AOP en lugar del bean con ámbito.
He anotado el bean con @Scope
proporcionando el proxyMode
como se muestra a continuación:
@Controller
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class ReportBuilder implements Serializable {
...
...
}
Problema
A pesar de la configuración anterior, obtengo la siguiente excepción:
org.springframework.beans.factory.BeanCreationException: error al crear bean con el nombre ''scopedTarget.reportBuilder'': la ''sesión'' del alcance no está activa para el hilo actual; considere la posibilidad de definir un proxy con ámbito para este bean si tiene la intención de referirse a él desde un singleton; La excepción anidada es java.lang.IllegalStateException: no se ha encontrado ninguna solicitud enlazada a hilos: ¿Se refiere a atributos de solicitud fuera de una solicitud web real, o procesando una solicitud fuera del hilo que recibió originalmente? Si en realidad está operando dentro de una solicitud web y aún recibe este mensaje, su código probablemente se esté ejecutando fuera de DispatcherServlet / DispatcherPortlet: en este caso, use RequestContextListener o RequestContextFilter para exponer la solicitud actual.
Actualización 1
A continuación está mi escaneo de componentes. Tengo lo siguiente en 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>
Y lo siguiente en AppConfig.java
:
@Configuration
@EnableAsync
@EnableCaching
@ComponentScan("org.example")
@ImportResource("classpath:applicationContext.xml")
public class AppConfig implements AsyncConfigurer {
...
...
}
Actualización 2
Creé un caso de prueba reproducible. Este es un proyecto mucho más pequeño, por lo que hay diferencias, pero ocurre el mismo error. Hay bastantes archivos, así que lo he subido como tar.gz
a megafileupload .
El problema no está en tus anotaciones de Spring, sino en tu patrón de diseño. Mezcla diferentes ámbitos e hilos:
- semifallo
- sesión (o solicitud)
- grupo de trabajos de subprocesos
El singleton está disponible en cualquier lugar, está bien. Sin embargo, el alcance de sesión / solicitud no está disponible fuera de un hilo adjunto a una solicitud.
El trabajo asincrónico puede ejecutarse incluso si la solicitud o sesión ya no existe, por lo que no es posible usar un bean dependiente de la solicitud / sesión. Además, no hay forma de saber si está ejecutando un trabajo en un hilo separado, qué hilo es la solicitud del originador (significa que aop: proxy no es útil en este caso).
Creo que su código parece que desea hacer un contrato entre ReportController, ReportBuilder, UselessTask y ReportPage. ¿Hay alguna manera de usar solo una clase simple (POJO) para almacenar datos de UselessTask y leerlos en ReportController o ReportPage y ya no usar ReportBuilder ?
Estoy respondiendo mi propia pregunta porque proporciona una mejor visión general de la causa y las posibles soluciones. Le he otorgado la bonificación a @Martin porque él apuntó la causa.
Porque
Según lo sugerido por @Martin, la causa es el uso de múltiples hilos. El objeto de solicitud no está disponible en estos hilos, como se menciona en la Guía de primavera :
DispatcherServlet
,RequestContextListener
yRequestContextFilter
hacen exactamente lo mismo, es decir, enlazan el objeto de solicitud HTTP con el subproceso que está dando servicio a esa solicitud. Esto hace que los beans que están solicitados y el alcance de la sesión estén disponibles más adelante en la cadena de llamadas.
Solución 1
Es posible hacer que el objeto de solicitud esté disponible para otros hilos, pero pone un par de limitaciones en el sistema, que pueden no ser viables en todos los proyectos. Obtuve esta solución de Acceso a beans con ámbito de solicitud en una aplicación web de subprocesos múltiples :
Me las arreglé para solucionar este problema. Empecé a usar
SimpleAsyncTaskExecutor
lugar deWorkManagerTaskExecutor
/ThreadPoolExecutorFactoryBean
. El beneficio es queSimpleAsyncTaskExecutor
nunca volverá a usar hilos. Esa es solo la mitad de la solución. La otra mitad de la solución es usar unRequestContextFilter
lugar deRequestContextListener
.RequestContextFilter
(así comoDispatcherServlet
) tiene una propiedadthreadContextInheritable
que básicamente permite que los hilos secundarios hereden el contexto primario.
Solución 2
La única otra opción es usar el bean con ámbito de la sesión dentro del hilo de solicitud. En mi caso esto no fue posible porque:
- El método del controlador está anotado con
@Async
; - El método del controlador inicia un trabajo por lotes que utiliza subprocesos para pasos de trabajo paralelos.
Mi respuesta se refiere a un caso especial del problema general que describe el OP, pero lo agregaré en caso de que ayude a alguien.
Cuando se usa @EnableOAuth2Sso
, Spring coloca una OAuth2RestTemplate
en el contexto de la aplicación, y este componente asume que está relacionado con los subprocesos.
Mi código tiene un método asíncrono programado que utiliza una RestTemplate RestTemplate
. Esto no se está ejecutando dentro de DispatcherServlet
, pero Spring estaba inyectando OAuth2RestTemplate
, que produjo el error que describe el OP.
La solución fue hacer una inyección basada en el nombre. En la configuración de Java:
@Bean
public RestTemplate pingRestTemplate() {
return new RestTemplate();
}
y en la clase que lo usa:
@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;
Ahora Spring inyecta el RestTemplate
previsto, sin servlet.
Según la documentación :
Si está accediendo a beans con ámbito dentro de Spring Web MVC, es decir, dentro de una solicitud procesada por Spring DispatcherServlet o DispatcherPortlet, entonces no es necesaria ninguna configuración especial: DispatcherServlet y DispatcherPortlet ya exponen todo el estado relevante.
Si está ejecutando fuera de Spring MVC (No procesado por DispatchServlet), debe usar RequestContextListener
No solo ContextLoaderListener
.
Agregue lo siguiente en su web.xml
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
Eso proporcionará una sesión a Spring para mantener los beans en ese ámbito
Actualización: según otras respuestas, el @Controller
solo es sensato cuando estás en Spring MVC Context, por lo que @Controller no está cumpliendo el propósito real en tu código. Aún así puede inyectar sus beans en cualquier lugar con alcance de alcance / solicitud de sesión (no necesita Spring MVC / Controller para simplemente inyectar beans en un ámbito particular).
Actualización: RequestContextListener expone la solicitud al hilo actual solamente.
Ha autoconectado ReportBuilder en dos lugares
1. ReportPage
: puede ver que Spring ha ReportPage
correctamente el generador de informes aquí, porque todavía estamos en Same Web Thread. Cambié el orden de tu código para asegurarme de que ReportBuilder se inyectara en ReportPage de esta manera.
log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();
Sabía que el registro debería seguir según su lógica, solo para el propósito de depuración que agregué.
2. UselessTasklet
: Obtuvimos una excepción, porque este es un hilo diferente creado por Spring Batch, donde RequestContextListener
no expone la Solicitud.
Debería tener una lógica diferente para crear e inyectar la instancia de ReportBuilder
en Spring Batch (May Spring Batch Parameters y utilizando Future<ReportBuilder>
que puede devolver para referencia futura)
Si alguien más se quedó en el mismo punto, después de resolver mi problema.
En web.xml
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
En componente de sesión
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
En pom.xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
Solo necesita definir en su bean donde necesite un alcance diferente al del alcance singleton predeterminado, excepto el prototipo. Por ejemplo:
<bean id="shoppingCart"
class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
<aop:scoped-proxy/>
</bean>
https://.com/a/30640097/2569475
Para este problema, verifique Mi respuesta en la URL anterior
Usar un bean con ámbito de solicitud fuera de una solicitud web real