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(), мои bean-компоненты не проксируются, поэтому методы @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);
}
}

person ilopezluna    schedule 09.06.2015    source источник
comment
Поскольку это класс времени конфигурации, который не должен реализовываться вашими реальными асинхронными классами, они предназначены для настройки асинхронной обработки.   -  person M. Deinum    schedule 09.06.2015
comment
Извините, но я не понимаю, не могли бы вы расширить свой ответ?   -  person ilopezluna    schedule 09.06.2015
comment
AsyncConfigurer Если для настройки вашей асинхронной инфраструктуры она должна быть реализована компонентами конфигурации, НЕ ТАКИМ вашим сервисным компонентом.   -  person M. Deinum    schedule 09.06.2015
comment
Ах, хорошо, да, это то, что я делаю, проблема возникает, когда я меняю AsyncConfiguration, чтобы реализовать AsyncConfigurer   -  person ilopezluna    schedule 09.06.2015
comment
Затем опубликуйте эту конфигурацию вместо нерабочей. Пожалуйста, добавьте то, что вы пробовали.   -  person M. Deinum    schedule 09.06.2015
comment
Удалите @Bean из методов переопределения.   -  person M. Deinum    schedule 09.06.2015
comment
Обновлено. надеюсь теперь вопрос более ясен   -  person ilopezluna    schedule 09.06.2015
comment
Давайте продолжим обсуждение в чате.   -  person ilopezluna    schedule 09.06.2015
comment
Я сталкиваюсь с теми же проблемами. Этот решён?   -  person rainerhahnekamp    schedule 19.02.2016


Ответы (1)



EDIT: я подал отчет об ошибке (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;
}

Также по какой-то причине, если я использую bean-компонент в методе @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, в зависимости от ваших требований, вы можете установить для свойства демона значение true, иначе ваше приложение будет продолжать работать вечно (это не большая проблема для веб-приложений/рабочих приложений). Вот что говорит JavaDoc от setDaemon(boolean):

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

person βξhrαng    schedule 26.08.2016
comment
Большое спасибо. Я использую вашу идею для решения моей проблемы. Еще раз спасибо! - person Forest10; 06.03.2019