2011-07-07 30 views
36

Un método @Async en un @Service: la clase anotada no se está llamando de forma asíncrona, está bloqueando el hilo.Spring @Async Not Working

Tengo <task: annotation-driven /> en mi configuración, y la llamada al método proviene de fuera de la clase, por lo que el proxy debe ser golpeado. Cuando paso por el código, el proxy es de hecho afectado, pero no parece llegar a ninguna clase relacionada con la ejecución en un ejecutor de tareas.

He puesto puntos de corte en AsyncExecutionInterceptor y nunca reciben ningún golpe. He depurado en AsyncAnnotationBeanPostProcessor y puedo ver cómo se aplican los consejos.

El servicio se define como una interfaz (con el método anotado @Async existe una buena medida) con el método de la aplicación anotada @Async también. Ninguno de los dos está marcado @Transactional.

¿Alguna idea de lo que pudo haber salido mal?

- ACTUALIZACIÓN = = -

Curiosamente, funciona solamente cuando tengo mis task elementos XML en mi archivo app-servlet.xml, y no en mi archivo app-services.xml, y si hacer mi escaneo de componentes sobre servicios desde allí también. Normalmente tengo un archivo XML con solo controladores (y restringe el escaneo de componentes en consecuencia) y otro con servicios (de nuevo con un componente de escaneo restringido de modo que no vuelva a escanear los controladores cargados en el otro) archivo).

App-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:jee="http://www.springframework.org/schema/jee" 
xmlns:mvc="http://www.springframework.org/schema/mvc" 
xmlns:webflow="http://www.springframework.org/schema/webflow-config" 
xmlns:task="http://www.springframework.org/schema/task" 
xmlns:tx="http://www.springframework.org/schema/tx" 
xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd 
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 
    http://www.springframework.org/schema/task 
    http://www.springframework.org/schema/task/spring-task-3.0.xsd 
    http://www.springframework.org/schema/mvc 
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd 
    http://www.springframework.org/schema/jee 
    http://www.springframework.org/schema/jee/spring-jee-3.0.xsd" 
> 
<task:annotation-driven executor="executor" /> 
<task:executor id="executor" pool-size="7"/> 

<!-- Enable controller annotations --> 
<context:component-scan base-package="com.package.store"> 
    <!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> --> 
</context:component-scan> 

<tx:annotation-driven/> 
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 
    <property name="sessionFactory" ref="sessionFactory"/> 
</bean> 

<mvc:annotation-driven conversion-service="conversionService" /> 

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
    <property name="prefix" value="/WEB-INF/jsp/" /> 
    <property name="suffix" value=".jsp" /> 
</bean> 

App-services.xml (no funciona cuando se especifica aquí)

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
     http://www.springframework.org/schema/context 
     http://www.springframework.org/schema/context/spring-context-3.0.xsd 
     http://www.springframework.org/schema/task 
     http://www.springframework.org/schema/task/spring-task-3.0.xsd"> 

    <!-- Set up Spring to scan through various packages to find annotated classes --> 
    <context:component-scan base-package="com.package.store"> 
     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> 
    </context:component-scan> 

    <task:annotation-driven executor="han" /> 
    <task:executor id="han" pool-size="6"/> 
    ... 

Me estoy perdiendo algo manifiestamente obvio en mi configuración, ¿o hay alguna interacción sutil entre los elementos de configuración?

+1

¿Estás seguro de que estás utilizando el tipo Spring ''@ ASync', y no uno de otra biblioteca? – skaffman

+0

'import org.springframework.scheduling.annotation.Async;' es lo que estoy usando. He visto que cuando se invoca el proxy, cree que la clase declarada (la interfaz) no tiene anotaciones de clase o de nivel de método, ¡lo cual es mentira! –

+4

¿Solo para verificarlo dos veces, se llama al método externamente, no desde el servicio correcto? ¿Y está llamando al método en el servicio que se construyó a partir de Spring, no a un servicio que haya "actualizado"? – Pace

Respuesta

26

Con la ayuda de este excellent answer by Ryan Stewart, pude resolver esto (al menos para mi problema específico).

En resumen, el contexto cargado por el ContextLoaderListener (generalmente de applicationContext.xml) es la matriz del contexto cargado por el DispatcherServlet (generalmente de *-servlet.xml). Si tiene el bean con el método @Async declarado/escaneado por componente en ambos contextos, la versión del contexto secundario (DispatcherServlet) anulará la que está en el contexto primario (ContextLoaderListener). Lo verifiqué excluyendo ese componente del escaneo de componentes en el *-servlet.xml - ahora funciona como se esperaba.

+0

Lamentablemente no puedo verificar esta solución ya que no tengo acceso al código más, pero estoy casi seguro de que este es el problema. La relación entre la raíz y los contextos web me había estado molestando por un tiempo. ¡Muchas gracias a ti y a Ryan Stewart! –

+0

He estado corriendo en el mismo problema, y ​​esta fue exactamente la solución, gracias por publicar, ach. – SteveT

+0

por favor dime cuál es la solución. También tengo el mismo problema. ¿Qué puedo hacer para resolver este problema? –

11
  1. Pruebe agregar proxy-target-class="true" a todos los <*:annotation-driven/> elementos que admitan este atributo.
  2. Compruebe si su método anotado con @Async es público.
21

Para mí la solución fue añadir @EnableAsync en mi @Configuration clase anotada:

@Configuration 
@ComponentScan("bla.package") 
@EnableAsync 
public class BlaConfiguration { 

} 

Ahora la clase en el paquete bla.package que tiene @Async métodos anotados puede realmente tener los llamó de forma asíncrona.

+0

¡este también fue el caso para mí! tanx – azerafati

+0

¡Funciona como un encanto! –

+0

Gracias, hombre, ¡esto también lo resolvió para mí! –

8

La respuesta de Jiří Vypědřík resolvió mi problema. En concreto,

  1. Compruebe si su método anotado con @Async es público.

Otra información útil a partir de la primavera tutoriales https://spring.io/guides/gs/async-method/:

Creación de una instancia local de la clase FacebookLookupService qué NO permiten el método BuscarPágina para ejecutar de forma asincrónica. Debe crearse dentro de una clase @Configuration o recogido por @ComponentScan.

Lo que esto significa es que si usted tenía un método estático Foo.bar(), llamándolo de esa manera no lo ejecutaría en asíncrono, aunque fue anotado con @Async. Tendrá que anotar Foo con @Component, y en la clase de llamada obtener una instancia @Autowired de Foo.

Es decir, si usted tiene una barra anotada método en la clase Foo:

@Component 
class Foo { 
    @Async 
    public static void bar(){ /* ... */ } 

    @Async 
    public void bar2(){ /* ... */ } 
} 

Una persona que llama en su clase:

class Test { 

    @Autowired Foo foo; 

    public test(){ 
    Foo.bar(); // Not async 
    foo.bar(); // Not async 
    foo.bar2(); // Async 
    } 

} 

Editar: Parece que calificó también de forma estática no ejecutarlo en asincrónico

Espero que esto ayude.

4

En primer lugar hacer su .xml config parece:

<task:scheduler id="myScheduler" pool-size="10" /> 
<task:executor id="myExecutor" pool-size="10" /> 
<task:annotation-driven executor="myExecutor" scheduler="myScheduler" proxy-target-class="true" /> 

(Sí, el recuento de programador y el tamaño del grupo de subprocesos ejecutor es configurable)

o simplemente utilizar por defecto:

<!-- enable task annotation to support @Async, @Scheduled, ... --> 
<task:annotation-driven /> 

En segundo lugar hacer seguro @Async métodos son públicos.

1

Me di cuenta siguiendo el tutorial async-method tutorial code que mi fuente de problema era: el bean con el método anotado @Async no se estaba creando envuelto en un proxy. empecé a cavar y se dieron cuenta de que había un mensaje que dice

Bean NameOfTheBean 'no es elegible para obtener procesados ​​por todos BeanPostProcessors (por ejemplo: no es elegible para auto-proxy)

Usted puede ver here respuestas sobre este tema y es básicamente que BeanPostProcessors es requerido por cada Bean, por lo que cada bean inyectado aquí y sus dependencias serán excluidos para ser procesados ​​posteriormente por otros BeanPostProcessors, ya que corrompe el ciclo de vida de los beans. Así que identifique cuál es el BeanPostProcessor que está causando esto y no use o cree frijoles dentro de él.

En mi caso tuve esta configuración

@EnableWs 
@Configuration 
public class WebServiceConfig extends WsConfigurerAdapter { 

    @Autowired 
    private Wss4jSecurityInterceptor securityInterceptor; 

    @Autowired 
    private DefaultPayloadLoggingInterceptor payloadLoggingInterceptor; 

    @Override 
    public void addInterceptors(List<EndpointInterceptor> interceptors) { 
     interceptors.add(securityInterceptor); 
     interceptors.add(payloadLoggingInterceptor); 
    } 
} 

WsConfigurerAdapter es en realidad un BeanPostProcessor y comprenden que, porque siempre hay un patrón: @Configuration que se extiende clases y anular algunas de las funciones de TI para instalar o modificar los granos involucrados en algunas características no funcionales, como el servicio web o la seguridad.

En el ejemplo antes mencionado que tiene que anular las habas y añadido addInterceptors interceptores, así que si usted está usando alguna anotación como @Async dentro DefaultPayloadLoggingInterceptor que no funcionará. ¿Cuál es la solución? Obtener el paseo de WsConfigurerAdapter para comenzar. Después de excavar un poco, me di cuenta de que una clase llamada PayloadRootAnnotationMethodEndpointMapping al final era la que tenía todos los interceptores válidos, así que lo hice manualmente para evitar una función.

@EnableWs 
@Configuration 
public class WebServiceConfig { 

    @Autowired 
    private Wss4jSecurityInterceptor securityInterceptor; 

    @Autowired 
    private DefaultPayloadLoggingInterceptor payloadLoggingInterceptor; 

    @Autowired 
    public void setupInterceptors(PayloadRootAnnotationMethodEndpointMapping endpointMapping) { 
     EndpointInterceptor[] interceptors = { 
       securityInterceptor, 
       payloadLoggingInterceptor 
     }; 

     endpointMapping.setInterceptors(interceptors); 
    } 
} 

Así que este se ejecutará después de todo BeanPostProcessor han hecho su trabajo. La función setupInterceptors se ejecutará cuando esa parte termine e instalará los interceptores. Este caso de uso se puede extrapolar a casos como Seguridad.

Conclusiones:

  • Si está utilizando un @Configuration que se extiende desde alguna clase que ejecuta algunas funciones dadas de forma automática y que los redefina, que son, probablemente, en el interior de un BeanPostProcessor, así que no inyectar habas allí y tratar de usa el comportamiento de AOP, porque no funcionará, y verás que Spring te lo dice con el mensaje antes mencionado en la consola. En esos casos, no use frijoles sino objetos (usando la cláusula new).
  • Si necesita utilizar beans digg sobre qué clase lleva los beans que desea configurar al final, @Autowired y agrégueles los beans como lo hice antes.

Espero que esto pueda ahorrarle algo de tiempo.

0

@Async no se puede utilizar junto con devoluciones de llamada de ciclo de vida como @PostConstruct. Para inicializar de forma asincrónica los beans de Spring, en este momento, tiene que usar un Spring Bean de inicialización separado que invoca el método @Async anotado en el objetivo.

public class SampleBeanImpl implements SampleBean { 

    @Async 
    void doSomething() { … } 
} 


public class SampleBeanInititalizer { 

    private final SampleBean bean; 

    public SampleBeanInitializer(SampleBean bean) { 
    this.bean = bean; 
    } 

    @PostConstruct 
    public void initialize() { 
    bean.doSomething(); 
    } 
} 

source

+0

Cuando dices "actualmente", ¿a qué versión de Spring te refieres? –

+0

esta es una cita de la documentación de Spring 3.0.x, pero estoy enfrentando lo mismo en la primavera 4.3.6 y pude hacer que funcione de esta manera –

0

escribir una configuración independiente para la primavera de frijol asíncrona.
por ejemplo:

@Configuration 
@ComponentScan(basePackages="xxxxxxxxxxxxxxxxxxxxx") 
@EnableAsync 
public class AsyncConfig { 

    /** 
    * used by asynchronous event listener. 
    * @return 
    */ 
    @Bean(name = "asynchronousListenerExecutor") 
    public Executor createAsynchronousListenerExecutor() { 
     ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 
     executor.setMaxPoolSize(100); 
     executor.initialize(); 
     return executor; 
    } 
} 

puedo superar este problema con esta situación.