Spring async не работает, когда реализует AsyncConfigurer

Наличие класса конфигурации Spring для асинхронных методов:

@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
public class AsyncConfiguration {

@Autowired
private ApplicationContext applicationContext;

@Bean
public ActivityMessageListener activityMessageListener() {
    return new ActivityMessageListener();
}
@Bean
public TaskExecutor defaultExecutor()
{
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(10);
    threadPoolTaskExecutor.setMaxPoolSize(10);
    threadPoolTaskExecutor.setQueueCapacity(Integer.MAX_VALUE);

    return threadPoolTaskExecutor;
}

Все мое @Asyncметоды работают как положено, но если я реализую AsyncConfigurerв AsyncConfiguration чтобы поймать реализацию исключений getAsyncUncaughtExceptionHandler() метод, мои бины не проксируются, поэтому методы @Asyncне работает в пуле исполнителя.

Это нерабочая конфигурация:

@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {

@Autowired
private ApplicationContext applicationContext;

@Bean
public ActivityMessageListener activityMessageListener() {
    return new ActivityMessageListener();
}

@Override
public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(10);
    threadPoolTaskExecutor.setMaxPoolSize(10);
    threadPoolTaskExecutor.setQueueCapacity(Integer.MAX_VALUE);

    return threadPoolTaskExecutor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler()        {
    return new SimpleAsyncUncaughtExceptionHandler();
}

Что может происходить?

Мы используем @Async как это:

public class ActivityMessageListener extends BaseMessageListener {

public static final String PARAM_USER_ID                = "userId";
public static final String PARAM_COMPANY_ID             = "companyId";
public static final String PARAM_CREATE_DATE            = "createDate";
public static final String PARAM_CLASS_NAME             = "className";
public static final String PARAM_CLASS_PK               = "classPK";
public static final String PARAM_TYPE                   = "type";
public static final String PARAM_EXTRA_DATA             = "extraData";
public static final String PARAM_RECEIVED_USER_ID       = "receiverUserId";

@Override @Async(value = "defaultExecutor")
public Future<String> doReceive(Message message) throws Exception {

    String name = Thread.currentThread().getName();
    Map<String, Object> parameters  = message.getValues();
    Long userId                     = (Long)parameters.get(ActivityMessageListener.PARAM_USER_ID);
    Long companyId                  = (Long)parameters.get(ActivityMessageListener.PARAM_COMPANY_ID);
    Date createDate                 = (Date)parameters.get(ActivityMessageListener.PARAM_CREATE_DATE);
    String className                = (String)parameters.get(ActivityMessageListener.PARAM_CLASS_NAME);
    Long classPK                    = (Long)parameters.get(ActivityMessageListener.PARAM_CLASS_PK);
    Integer type                    = (Integer)parameters.get(ActivityMessageListener.PARAM_TYPE);
    String extraData                = (String)parameters.get(ActivityMessageListener.PARAM_EXTRA_DATA);
    Long receiverUserId             = (Long)parameters.get(ActivityMessageListener.PARAM_RECEIVED_USER_ID);
    ActivityLocalServiceUtil.addActivity(userId, companyId, createDate, className, classPK, type, extraData, receiverUserId);

    return new AsyncResult<String>(name);
}
}

1 ответ

Решение

РЕДАКТИРОВАТЬ: Я подал отчет об ошибке (SPR-14630).


Я был на грани отправки отчета об ошибке в средство отслеживания проблем Spring, однако, когда я готовил небольшое приложение для воспроизведения ошибки, я нашел и исправил проблему.

Прежде всего, при использовании ThreadPoolTaskExecutor позвони ее initialize() метод перед возвратом:

@Override
public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setMaxPoolSize(1);
    executor.setCorePoolSize(1);
    executor.setThreadNamePrefix("CUSTOM-");

    // Initialize the executor
    executor.initialize();

    return executor;
}

Также по какой-то причине, если я использую бин в @PostConstruct метод, определенный в том же классе конфигурации, он не будет работать асинхронно. Причина в том, что @PostConstruct метод выполняется раньше getAsyncExecutor() а также getAsyncUncaughtExceptionHandler() выполнены:

AsyncBean.java:

@Component
public class AsyncBean implements IAsyncBean {

    @Override
    @Async
    public void whoAmI() {
        final String message =
                String.format("My name is %s and I am running in %s", getClass().getSimpleName(), Thread.currentThread());

        System.out.println(message);
    }
}

AsyncDemoApp.java:

@SpringBootApplication
@EnableAsync
public class AsyncDemoApp implements AsyncConfigurer {

    @Autowired
    private IAsyncBean asyncBean;

    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApp.class, args);
    }

    @Override
    public Executor getAsyncExecutor() {
        System.out.println("AsyncDemoApp.getAsyncExecutor");

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("CUSTOM-");
        executor.initialize();

        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        System.out.println("AsyncDemoApp.getAsyncUncaughtExceptionHandler");
        return (throwable, method, objects)
                -> throwable.printStackTrace();
    }

    @PostConstruct
    public void start() {
        System.out.println("AsyncDemoApp.start");
        asyncBean.whoAmI();
    }
}

Выход:

AsyncDemoApp.start
My name is AsyncBean and I am running in Thread[main,5,main]
AsyncDemoApp.getAsyncExecutor
AsyncDemoApp.getAsyncUncaughtExceptionHandler

Однако, если вы используете ваш bean-компонент после того, как контекст приложения готов к использованию, все должно работать как положено:

@SpringBootApplication
@EnableAsync
public class AsyncDemoApp implements AsyncConfigurer {

    public static void main(String[] args) {
        final ConfigurableApplicationContext context = SpringApplication.run(AsyncDemoApp.class, args);

        final IAsyncBean asyncBean = context.getBean(IAsyncBean.class);

        asyncBean.whoAmI();
    }

    @Override
    public Executor getAsyncExecutor() {
        System.out.println("AsyncDemoApp.getAsyncExecutor");

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("CUSTOM-");
        executor.initialize();

        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        System.out.println("AsyncDemoApp.getAsyncUncaughtExceptionHandler");
        return (throwable, method, objects)
                -> throwable.printStackTrace();
    }
}

Другое странное поведение заключается в том, что если вы автоматически подключаете асинхронный компонент в том же классе конфигурации, автоматическое подключение происходит до того, как настраивается пользовательский асинхронный исполнитель, поэтому компонент не запускается асинхронно и выполняется в основном потоке. Это можно проверить, добавив @PostConstruct в AsyncBean и используя CommandLineRunner запустить приложение (лично я думаю, что это ошибка. Поведение очень удивительно, если не сказать больше):

AsyncBean с @PostConstruct:

@Component
public class AsyncBean implements IAsyncBean {

    @Override
    @Async
    public void whoAmI() {
        final String message =
                String.format("My name is %s and I am running in %s", getClass().getSimpleName(), Thread.currentThread());

        System.out.println(message);
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("AsyncBean is constructed");
    }
}

AsyncDemoApp реализации CommandLineRunner:

@SpringBootApplication
@EnableAsync
public class AsyncDemoApp implements AsyncConfigurer, CommandLineRunner {

    @Autowired
    private IAsyncBean asyncBean;

    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApp.class, args);
    }

    @Override
    public Executor getAsyncExecutor() {
        System.out.println("AsyncDemoApp.getAsyncExecutor");

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("CUSTOM-");
        executor.initialize();

        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        System.out.println("AsyncDemoApp.getAsyncUncaughtExceptionHandler");
        return (throwable, method, objects)
                -> throwable.printStackTrace();
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("AsyncDemoApp.run");
        asyncBean.whoAmI();
    }
}

Выход:

AsyncBean is constructed
AsyncDemoApp.getAsyncExecutor
AsyncDemoApp.getAsyncUncaughtExceptionHandler
AsyncDemoApp.run
My name is AsyncBean and I am running in Thread[main,5,main]

Еще кое-что!:) Если вы используете ThreadPoolTaskExecutor в зависимости от ваших требований вы можете установить для его свойства daemon значение true, иначе ваше приложение будет работать вечно (это не является большой проблемой для приложений Web/Worker). Вот что такое JavaDoc setDaemon(boolean) говорит:

Укажите, должна ли эта фабрика создавать потоки демонов, просто выполняя их в течение всего времени работы приложения. По умолчанию установлено значение "false": бетонные заводы обычно поддерживают явную отмену. Следовательно, если приложение закрывается, Runnables по умолчанию завершит свое выполнение. Укажите "true" для активного отключения потоков, которые все еще активно выполняют Runnable во время закрытия самого приложения.

Другие вопросы по тегам