web-dev-qa-db-fra.com

Problème Spring Async lors de la mise à niveau de 4.2.0.RC3 à 4.2.0.RELEASE

J'ai une application Web utilisant les artefacts Spring (4.2.x) spring-webmvc, spring-messaging, spring-websocket

J'ai les annotations @ Activer * ci-dessous dans ma classe Java Spring Config

@EnableWebMvc
@EnableWebSocketMessageBroker
@EnableAsync
@EnableMBeanExport

WebSocket est utilisé pour la diffusion de messages vers les clients du navigateur . Et il existe peu de méthodes asynchrones annotées avec @Async

L'application fonctionnait correctement avec la version à ressort 4.2.0.RC3. Mais lorsque je l'ai modifiée en GA version 4.2.0.RELEASE, l'exception ci-dessous est générée au démarrage. Si je supprime @EnableAsync, cela fonctionne bien, mais j'ai besoin de la fonctionnalité async.

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.core.task.TaskExecutor] is defined: expected single matching bean but found 4: clientOutboundChannelExecutor,messageBrokerTaskScheduler,clientInboundChannelExecutor,brokerChannelExecutor
    org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.Java:366)
    org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.Java:332)
    org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor.setBeanFactory(AsyncAnnotationBeanPostProcessor.Java:128)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeAwareMethods(AbstractAutowireCapableBeanFactory.Java:1597)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.Java:1565)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.Java:545)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.Java:482)
    org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.Java:305)
    org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.Java:230)
    org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.Java:301)
    org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.Java:201)
    org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.Java:228)
    org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.Java:682)
    org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.Java:522)
    org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.Java:667)
    org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.Java:539)
    org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.Java:493)
    org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.Java:136)
17
Subham Saha

Un de vos @Configuration doit implémenter AsyncConfigurer pour spécifier la TaskExecutor pour les méthodes @Async

Sinon, il est difficile de choisir lequel choisir parmi applicationContext.

Même si cela fonctionnait avec RC3, peu importe qu'il soit correct, le bogue a donc été corrigé pour GA.

METTRE À JOUR

Le code source dans la AsyncAnnotationBeanPostProcessor ressemble à ceci:

Executor executorToUse = this.executor;
if (executorToUse == null) {
    try {
        // Search for TaskExecutor bean... not plain Executor since that would
        // match with ScheduledExecutorService as well, which is unusable for
        // our purposes here. TaskExecutor is more clearly designed for it.
        executorToUse = beanFactory.getBean(TaskExecutor.class);
    }
    catch (NoUniqueBeanDefinitionException ex) {
        try {
            executorToUse = beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, TaskExecutor.class);
        }
        catch (NoSuchBeanDefinitionException ex2) {
            throw new IllegalStateException("More than one TaskExecutor bean exists within the context, " +
                    "and none is named 'taskExecutor'. Mark one of them as primary or name it " +
                    "'taskExecutor' (possibly as an alias); or specify the AsyncConfigurer interface " +
                    "and implement getAsyncExecutor() accordingly.", ex);
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        logger.debug("Could not find default TaskExecutor bean", ex);
        // Giving up -> falling back to default executor within the advisor...
    }
}

Je suppose donc qu'avant de passer de la RC3 à la position GA, vous avez eu une idée taskExecutor

Comme on voit par toi StackTrace il y a déjà un tel haricot ...

10
Artem Bilan

Ajouter le bean à la configuration du contexte d'application Spring

@Configuration
@EnableAsync
public class AppContext extends WebMvcConfigurationSupport {
    @Bean
    public Executor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
}

Je suggère que vous vous référiez à linuxism.tistory.com/2076

Si vous déclarez vos exécuteurs en XML, vous pouvez créer une classe et la nommer TaskExecutor. Ensuite, lorsque Spring essaiera de trouver le bean TaskExecutor, il le trouvera.

@Component
public class TaskExecutor extends SimpleAsyncTaskExecutor {
}
19
linuxism

Pour ceux qui, comme moi, utilisent encore une configuration XML à l'ancienne ....

Cela fonctionnait pour moi avant le printemps 4.2:

<task:annotation-driven />
<task:executor id="executorA" pool-size="50" />
<task:executor id="executorB" pool-size="100" />

@Async("executorA")
public void executeA() {}

@Async("executorB")
public void executeB() {}

Comme l'a souligné Artem, Spring 4.2 commence à ne pas savoir quel pool utiliser pour les méthodes asynchrones sans qualificateur, même si vous n'en avez pas dans votre application.

Pour résoudre ce problème, j'ai utilisé ceci:

<task:annotation-driven executor="defaultExecutor"/>

<task:executor id="defaultExecutor" pool-size="1" queue-capacity="0"/>
<task:executor id="executorA" pool-size="50" />
<task:executor id="executorB" pool-size="100" />

@Async("executorA")
public void executeA() {}

@Async("executorB")
public void executeB() {}

Notez que si vous ajoutez des méthodes @Async sans qualificateur, ces méthodes utiliseront le pool de threads defaultExectuor:

@Async
public void myDefaultExecute() {}

Et, bien sûr, les appels executeA () utiliseront executorA-pool de threads et executeB () utiliseront executorB-pool.

J'espère que cela pourra aider.

1
tnabeel