web-dev-qa-db-fra.com

Spring @Async ne fonctionne pas

Une méthode @Async dans une classe @Service- annotée n'est pas appelée de manière asynchrone - elle bloque le thread.

J'ai <task: annotation-driven /> dans ma config, et l'appel à la méthode vient de l'extérieur de la classe, le proxy devrait donc être touché. Lorsque j'examine le code, le proxy est touché, mais il ne semble pas aller près des classes liées à l'exécution dans un exécuteur de tâches. 

J'ai mis des points d'arrêt dans AsyncExecutionInterceptor et ils ne sont jamais touchés. J'ai débogué dans AsyncAnnotationBeanPostProcessor et je peux voir les conseils appliqués.

Le service est défini comme une interface (avec la méthode annotée @Async pour une bonne mesure) avec la méthode d'implémentation annotée @Async également. Ni sont marqués @Transactional.

Des idées sur ce qui peut avoir mal tourné?

- = UPDATE = -

Curieusement, cela fonctionne seulement lorsque mes éléments XML task sont présents dans mon fichier app-servlet.xml, et non dans mon fichier app-services.xml, et que mon composant analyse également les services à partir de celui-ci. Normalement, j'ai un fichier XML contenant uniquement des contrôleurs (et limitant l'analyse des composants en conséquence), et un autre contenant des services (encore une fois, avec une analyse des composants restreinte de manière à ne pas ré-analyser les contrôleurs chargés dans l'autre fichier).

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 (ne fonctionne pas si spécifié ici)

<?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 manque-t-il quelque chose de flagrant dans ma configuration, ou existe-t-il une subtile interaction entre les éléments de configuration?

40
EngineerBetter_DJ

Avec l’aide de cette excellente réponse de Ryan Stewart , j’ai pu comprendre cela (du moins pour mon problème spécifique).

En bref, le contexte chargé par la variable ContextLoaderListener (généralement de applicationContext.xml) est le parent du contexte chargé par la variable DispatcherServlet (généralement de *-servlet.xml). Si vous avez le bean avec la méthode @Async déclarée/analysée par le composant dans les deux contextes, la version du contexte enfant (DispatcherServlet) remplacera celle du contexte parent (ContextLoaderListener). J'ai vérifié cela en excluant ce composant de l'analyse de composant dans le *-servlet.xml - il fonctionne désormais comme prévu.

28
ach

Pour moi, la solution a été d’ajouter @EnableAsync à ma classe annotée @Configuration:

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

}

Désormais, la classe du package bla.package qui possède les méthodes annotées @Async peut réellement les appeler de manière asynchrone.

25
Shivan Dragon
  1. Essayez d’ajouter proxy-target-class="true" à tous les éléments <*:annotation-driven/> qui prennent en charge cet attribut.
  2. Vérifiez si votre méthode annotée avec @Async est publique.
11
Jiří Vypědřík

La réponse de Jiří Vypědřík a résolu mon problème. Plus précisément, 

  1. Vérifiez si votre méthode annotée avec @Async est publique.

Une autre information utile tirée des tutoriels de Spring https://spring.io/guides/gs/async-method/ :

La création d'une instance locale de la classe FacebookLookupService ne fait PAS autoriser la méthode findPage à s'exécuter de manière asynchrone. Il doit être créé à l'intérieur une classe @Configuration ou récupérée par @ComponentScan.

Cela signifie que si vous aviez une méthode statique Foo.bar (), l'appeler de cette manière ne l'exécuterait pas en async, même si elle était annotée avec @Async. Vous devrez annoter Foo avec @Component et, dans la classe appelante, obtenir une instance @Autowired de Foo.

Par exemple, si vous avez une barre de méthode annotée dans la classe Foo:

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

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

Un dans votre classe d'appelant:

class Test {

  @Autowired Foo foo;

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

}

Edit: On dirait que l'appeler de manière statique ne l'exécute pas non plus en async.

J'espère que cela t'aides.

11
typoerrpr

Tout d’abord, faites que votre .xml config ressemble à ceci:

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

(Oui, le nombre de planificateurs et la taille du pool d'unités d'exécution sont configurables)

Ou utilisez simplement default:

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

Deuxièmement, assurez-vous que les méthodes @Async sont publiques.

4
coderz

J'ai réalisé après le tutoriel code du tutoriel de méthode async que ma source était: le bean avec la méthode annotée @Async n'était pas créé, enveloppé dans un proxy . J'ai commencé à creuser et j'ai compris qu'il y avait un message 

Le bean 'NameOfTheBean' ne peut pas être traité par tous BeanPostProcessors (par exemple: non éligible pour le proxy automatique)

Vous pouvez voir ici réponses sur ce problème et le fait que BeanPostProcessors est requis par chaque Bean, ainsi chaque Bean injecté ici et ses dépendances seront exclus pour être traités ultérieurement par d'autres BeanPostProcessors, car ils ont corrompu le cycle de vie des Beans. . Identifiez donc quelle est la BeanPostProcessor qui est la cause et n'utilisez pas ou ne créez pas de haricots à l'intérieur.

Dans mon cas j'avais cette configuration

@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 est en fait une BeanPostProcessor et vous vous en rendez compte, car il existe toujours un modèle: @Configuration qui étend les classes et remplace certaines de ses fonctions pour installer ou modifier des beans impliqués dans certaines fonctionnalités non fonctionnelles, telles que le service Web ou la sécurité.

Dans l'exemple ci-dessus, vous devez remplacer les beans de la variable addInterceptors et des intercepteurs ajoutés. Par conséquent, si vous utilisez des annotations telles que @Async dans DefaultPayloadLoggingInterceptor, cela ne fonctionnera pas. Quelle est la solution? Obtenez ride de WsConfigurerAdapter pour commencer. Après avoir creusé un peu, j’ai réalisé qu’une classe nommée PayloadRootAnnotationMethodEndpointMapping à la fin était dotée de tous les intercepteurs valides; je l’ai donc fait manuellement pour redéfinir une fonction.

@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);
    }
}

Donc, ceci sera exécuté après que tous les BeanPostProcessor aient fait leur travail. La fonction setupInterceptors sera exécutée à la fin de la session et installera les beans interceptors. Ce cas d'utilisation peut être extrapolé à des cas tels que la sécurité.

Conclusions:

  • Si vous utilisez une @Configuration s'étendant d'une classe qui exécute automatiquement certaines fonctions données et que vous les remplacez, vous êtes probablement à l'intérieur d'une BeanPostProcessor, alors n'injectez pas de beans ici et essayez d'utiliser le comportement AOP, car cela ne fonctionnera pas et vous ne le ferez plus. see Spring vous le dit avec le message précédent dans la console. Dans ces cas, n'utilisez pas de haricots mais des objets (en utilisant la clause new).
  • Si vous avez besoin d'utiliser des haricots pour déterminer quelle classe transporte les haricots que vous souhaitez configurer à la fin, @Autowired et ajoutez ces haricots comme je le faisais auparavant.

J'espère que cela vous fera gagner du temps.

1
EliuX

Vous avez besoin de 3 lignes de code pour qu'Async fonctionne

  1. dans applicationContext.xml
  2. Au niveau de la classe @EnableAsync
  3. @Async au niveau de la méthode

@Service @ EnableAsync Public myClass {

@Async Public void myMethod () {

}

1
vsingh

@Async ne peut pas être utilisé avec des rappels de cycle de vie tels que @PostConstruct. Pour initialiser les beans Spring de manière asynchrone, vous devez actuellement utiliser un bean Spring d'initialisation distinct qui appelle la méthode annotée @Async sur la cible.

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();
  }
}

la source

0

Essayez ci-dessous: 1. Dans la configuration, créez un haricot pour ThreadPoolTaskExecutor

@Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }

2. Dans la méthode de service où @Async est utilisé, ajoutez 

@Async("threadPoolTaskExecutor")
    public void asyncMethod(){
    //do something
    }

Cela devrait faire fonctionner @Async.

0
Ravik

écrire une configuration de printemps indépendante pour le bean asynchrone.
par exemple:

@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;
    }
}

Je surmonte ce problème avec cette situation.

0
Cheng