1. 程式人生 > 實用技巧 >Spring中@Async註解執行非同步任務 & @Async Could not find unique TaskExecutor bean; NoUniqueBeanDefinitionException;

Spring中@Async註解執行非同步任務 & @Async Could not find unique TaskExecutor bean; NoUniqueBeanDefinitionException;

Spring中@Async註解執行非同步任務

https://segmentfault.com/a/1190000015190901

引言

在業務處理中,有些業務使用非同步的方式更為合理。比如在某個業務邏輯中,把一些資料存入到redis快取中,快取只是一個輔助的功能,成功或者失敗對主業務並不會產生根本影響,這個過程可以通過非同步的方法去進行。

Spring中通過在方法上設定@Async註解,可使得方法被非同步呼叫。也就是說該方法會在呼叫時立即返回,而這個方法的實際執行交給Spring的TaskExecutor去完成。

程式碼示例

專案是一個普通的Spring的專案,Spring的配置檔案:

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

    <!-- 包掃描 -->
    <context:component-scan base-package="com.lzumetal.ssm"/>

    <!-- 執行非同步任務的執行緒池TaskExecutor -->
    <task:executor id="myexecutor" pool-size="5"  />
    <task:annotation-driven executor="myexecutor"/>

</beans>

兩個Service類:

package com.lzumetal.ssm.anotation.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * 業務Service
 */
@Service
public class BusinessService {

    private static final Logger log = LoggerFactory.getLogger(BusinessService.class);

    @Autowired
    private CacheService cacheService;


    public void doBusiness() {
        log.error("start to deal with our business");
        cacheService.cacheData();
        log.error("comlete service operation");
    }

    /**
     * 獲取非同步方法執行的返回值
     */
    public void doBusinessWithAsyncReturn() throws ExecutionException, InterruptedException {
        log.error("start to deal with our business");
        Future<String> future = cacheService.cacheDataWithReturn();
        log.error(future.get()); //future.get()方法是會阻塞的
        log.error("comlete service operation");
    }
}
package com.lzumetal.ssm.anotation.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 快取服務
 */
@Service
public class CacheService {

    private static final Logger log = LoggerFactory.getLogger(CacheService.class);


    @Async(value = "myexecutor")    //指定執行任務的TaskExecutor
    public void cacheData() {
        try {
            TimeUnit.SECONDS.sleep(3L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.error("success store the result to cache");
    }


    @Async
    public Future<String> cacheDataWithReturn() {
        try {
            TimeUnit.SECONDS.sleep(3L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.error("success store the result to cache");
        //返回的結果需要通過AsyncResult這個類包裝
        return new AsyncResult<>("Async operation success");
    }
}

測試類:

package com.lzumetal.ssm.anotation.test;

import com.lzumetal.ssm.anotation.service.BusinessService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.concurrent.TimeUnit;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-context.xml"})
public class MainTest {


    @Autowired
    private BusinessService businessService;


    @Test
    public void test() throws InterruptedException {
        businessService.doBusiness();
        //不讓主執行緒過早結束,否則控制檯看不到非同步方法中的輸出內容
        TimeUnit.SECONDS.sleep(5L);     
    }

    @Test
    public void testAsyncReturn() throws Exception {
        businessService.doBusinessWithAsyncReturn();
        TimeUnit.SECONDS.sleep(5L);
    }

}

執行test()方法的結果:

22:20:33,207  INFO main support.DefaultTestContextBootstrapper:260 - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
22:20:33,226  INFO main support.DefaultTestContextBootstrapper:209 - Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
22:20:33,227  INFO main support.DefaultTestContextBootstrapper:187 - Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@100fc185, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@643b1d11, org.springframework.test.context.support.DirtiesContextTestExecutionListener@2ef5e5e3, org.springframework.test.context.transaction.TransactionalTestExecutionListener@36d4b5c, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@6d00a15d]22:20:33,324  INFO main xml.XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [spring-context.xml]
22:20:33,585  INFO main support.GenericApplicationContext:583 - Refreshing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 22:20:33 CST 2018]; root of context hierarchy
22:20:33,763  INFO main concurrent.ThreadPoolTaskExecutor:165 - Initializing ExecutorService 
22:20:33,766  INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.config.TaskExecutorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
22:20:33,767  INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
22:20:34,107 ERROR main service.BusinessService:24 - start to deal with our business
22:20:34,113 ERROR main service.BusinessService:26 - comlete service operation
22:20:37,166 ERROR myexecutor-1 service.CacheService:28 - success store the result to cache
22:20:39,117  INFO Thread-0 support.GenericApplicationContext:984 - Closing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 22:20:33 CST 2018]; root of context hierarchy
22:20:39,118  INFO Thread-0 concurrent.ThreadPoolTaskExecutor:203 - Shutting down ExecutorService

執行testAsyncReturn()方法的結果:

21:38:16,908  INFO main support.DefaultTestContextBootstrapper:260 - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
21:38:16,926  INFO main support.DefaultTestContextBootstrapper:209 - Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
21:38:16,927  INFO main support.DefaultTestContextBootstrapper:187 - Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@100fc185, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@643b1d11, org.springframework.test.context.support.DirtiesContextTestExecutionListener@2ef5e5e3, org.springframework.test.context.transaction.TransactionalTestExecutionListener@36d4b5c, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@6d00a15d]21:38:17,025  INFO main xml.XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [spring-context.xml]
21:38:17,263  INFO main support.GenericApplicationContext:583 - Refreshing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 21:38:17 CST 2018]; root of context hierarchy
21:38:17,405  INFO main concurrent.ThreadPoolTaskExecutor:165 - Initializing ExecutorService 
21:38:17,407  INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.config.TaskExecutorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
21:38:17,407  INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
21:38:17,692 ERROR main service.BusinessService:35 - start to deal with our business
21:38:20,833 ERROR myexecutor-1 service.CacheService:39 - success store the result to cache
21:38:20,834 ERROR main service.BusinessService:37 - Async operation success
21:38:20,835 ERROR main service.BusinessService:38 - comlete service operation
21:38:25,838  INFO Thread-0 support.GenericApplicationContext:984 - Closing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 21:38:17 CST 2018]; root of context hierarchy
21:38:25,839  INFO Thread-0 concurrent.ThreadPoolTaskExecutor:203 - Shutting down ExecutorService

@Async的使用注意點

  1. 返回值:不要返回值直接void;需要返回值用AsyncResult或者CompletableFuture
  2. 可自定義執行器並指定例如:@Async("otherExecutor")
  3. @Async必須不同類間呼叫: A類—>B類.C方法()(@Async註釋在B類/方法中),如果在同一個類中呼叫,會變同步執行,例如:A類.B()—>A類.@Async C()。
  4. @Async也可以加到類,表示這個類的所有方法都是非同步執行,並且方法上的註解會覆蓋類上的註解。但一般不這麼用!
___________________________________________________________________________________________________________________________________________________________

@Async Could not find unique TaskExecutor bean; NoUniqueBeanDefinitionException;

https://blog.csdn.net/chenping1993/article/details/103881649

非同步任務,專案啟動或介面呼叫,控制檯報錯:Could not find unique TaskExecutor bean;

或者報錯:org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available: expected single matching bean but found 2: applicationTaskExecutor,taskScheduler

原因為:@Async所註解的方法存在相同的方法名,或者同一個非同步任務被多個任務同時呼叫

解決方法:

1、將@Async所註解的方法名修改為不同的方法名

2、增加非同步任務配置類:

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
import java.util.concurrent.Executor;
 
/**
 * @description 非同步任務配置類,為防止非同步任務如下錯誤:
 * No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available:
 * expected single matching bean but found 2: applicationTaskExecutor,taskScheduler
 * @author: chenping
 * @create: 2020-01-17
 **/
@Configuration
@ComponentScan("cn.com.*.*.*.service")//非同步任務所在的包
@EnableAsync //開啟非同步任務支援
public class TaskExecutorConfig implements AsyncConfigurer {
 
    /**
     * @description:  實現AsyncConfigurer介面並重寫getAsyncExecutor方法,
     *  並返回一個ThreadPoolTaskExecutor,這樣我們就獲得了一個基於執行緒池TaskExecutor
     * @param:
     * @return: java.util.concurrent.Executor
     * @author: chenping
     * @date: 2020/1/17
     **/
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(80);
        taskExecutor.setQueueCapacity(100);
        taskExecutor.initialize();
        return taskExecutor;
    }
 
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}