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?
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.
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.
proxy-target-class="true"
à tous les éléments <*:annotation-driven/>
qui prennent en charge cet attribut.@Async
est publique.La réponse de Jiří Vypědřík a résolu mon problème. Plus précisément,
- 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.
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.
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:
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
).@Autowired
et ajoutez ces haricots comme je le faisais auparavant.J'espère que cela vous fera gagner du temps.
Vous avez besoin de 3 lignes de code pour qu'Async fonctionne
@Service @ EnableAsync Public myClass {
@Async Public void myMethod () {
}
@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();
}
}
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.
é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.