Spring @Async не работает
Метод @Async
в классе @Service
-annotated не вызывается асинхронно - он блокирует поток.
У меня есть <task: annotation-driven />
в моей конфигурации, и вызов метода поступает извне класса, поэтому прокси-сервер должен быть поражен. Когда я просматриваю код, прокси-сервер действительно попадает, но он, похоже, не приближается ни к каким классам, связанным с запуском в исполнителе задач.
Я поставил точки останова в AsyncExecutionInterceptor
, и они никогда не попадают. Я отлаживался в AsyncAnnotationBeanPostProcessor
и вижу, как применяется обращение к советам.
Служба определяется как интерфейс (с помощью метода annotated @Async
там для хорошей меры) с помощью метода реализации, аннотированного @Async
. Не отмечены @Transactional
.
Любые идеи, что могло пойти не так?
- = ОБНОВЛЕНИЕ = -
Любопытно, что он работает только тогда, когда у меня есть мои XML-элементы task
в моем файле app-servlet.xml, а не в моем файле app-services.xml, и если я тоже просматриваю свой компонент, просматривая службы. Обычно у меня есть один XML файл, в котором есть только контроллеры (и соответственно ограничить его), а другой - с услугами в нем (опять же с ограниченным просмотром компонентов, чтобы он не пересканировал контроллеры, загруженные в другой файл).
приложение-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 (не работает, если указано здесь)
<?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"/>
...
Я пропустил что-то совершенно очевидное в моей конфигурации, или происходит какое-то тонкое взаимодействие между элементами конфигурации?
Ответы
Ответ 1
С помощью этого отличного ответа Райана Стюарта я смог понять это (по крайней мере, для моей конкретной проблемы).
Вкратце, контекст, загруженный ContextLoaderListener
(обычно из applicationContext.xml), является родителем контекста, загружаемого DispatcherServlet
(обычно из *-servlet.xml
). Если у вас есть bean с объявленным/компонентным сканированием @Async
в обоих контекстах, версия из дочернего контекста (DispatcherServlet
) переопределит версию в родительском контексте (ContextLoaderListener
). Я проверил это, исключив этот компонент из сканирования компонентов в *-servlet.xml
- теперь он работает так, как ожидалось.
Ответ 2
Для меня решение заключалось в том, чтобы добавить @EnableAsync
в мой аннотированный класс @Configuration
:
@Configuration
@ComponentScan("bla.package")
@EnableAsync
public class BlaConfiguration {
}
Теперь класс в пакете bla.package
, у которого есть @Async
аннотированные методы, действительно может быть вызван асинхронно.
Ответ 3
- Попробуйте добавить
proxy-target-class="true"
ко всем <*:annotation-driven/>
элементам, которые поддерживают этот атрибут.
- Проверьте, является ли ваш метод аннотированным с помощью
@Async
общедоступным.
Ответ 4
Jiří Vypědřík ответ решил мою проблему. В частности,
- Проверьте, является ли ваш метод аннотированным с @Async общедоступным.
Еще одна полезная информация из Spring tutorials https://spring.io/guides/gs/async-method/:
Создание локального экземпляра класса FacebookLookupService НЕ разрешить метод findPage асинхронно. Он должен быть создан внутри класс @Configuration или выбранный @ComponentScan.
Это означает, что если бы у вас был статический метод Foo.bar(), вызов его таким образом не выполнял бы его в async, даже если он был аннотирован с @Async. Вам нужно будет аннотировать Foo с @Component, а в вызывающем классе получить @Autowired экземпляр Foo.
Т.е., если у вас есть аннотированная панель методов в классе Foo:
@Component
class Foo {
@Async
public static void bar(){ /* ... */ }
@Async
public void bar2(){ /* ... */ }
}
An в вашем классе вызывающего абонента:
class Test {
@Autowired Foo foo;
public test(){
Foo.bar(); // Not async
foo.bar(); // Not async
foo.bar2(); // Async
}
}
Изменить: похоже, что вызов статически также не выполняется в async.
Надеюсь, что это поможет.
Ответ 5
Сначала создайте конфигурацию .xml
:
<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" />
(Да, размер планировщика и размер пула потоков исполнителей настраивается)
Или просто используйте значение по умолчанию:
<!-- enable task annotation to support @Async, @Scheduled, ... -->
<task:annotation-driven />
Во-вторых, убедитесь, что методы @Async
общедоступны.
Ответ 6
Я понял после учебника код учебника асинхронного метода, который был моим источником проблем: bean с аннотированным методом @Async
не создавался, завернутый в прокси.
Я начал копать и понял, что было сообщение о том, что
Bean 'NameOfTheBean' не подлежит обработке всеми BeanPostProcessors (например: не допускается автопроксирование)
Вы можете увидеть здесь ответы об этой проблеме и ее в основном, что BeanPostProcessors требуются каждым Bean, поэтому каждый bean, введенный здесь и его зависимостями, будет исключается для обработки позже другими BeanPostProcessors, потому что это испортило жизненный цикл beans. Поэтому укажите, что является BeanPostProcessor
, которое вызывает это и не использует или не создает beans внутри него.
В моем случае у меня была эта конфигурация
@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
на самом деле является BeanPostProcessor
, и вы понимаете это, потому что всегда существует шаблон: @Configuration
, который расширяет классы и переопределяет некоторые из его функций для установки или настройки beans, задействованных в некоторых не функциональных функциях, таких как веб-службы или безопасности.
В вышеприведенном примере вам нужно переопределить addInterceptors
и добавленные перехватчики beans, поэтому, если вы используете некоторую аннотацию типа @Async
внутри DefaultPayloadLoggingInterceptor
, она не будет работать. Каково решение? Пройдите WsConfigurerAdapter
, чтобы начать.
После копания бит я понял, что класс с именем PayloadRootAnnotationMethodEndpointMapping
в конце был у всех допустимых перехватчиков, поэтому я сделал это вручную, чтобы переопределить функцию.
@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);
}
}
Итак, это будет запущено после того, как все BeanPostProcessor
выполнили свою работу. Функция setupInterceptors
будет работать, когда эта сторона закончится, и установите перехватчики beans. Этот вариант использования может быть экстраполирован на такие случаи, как Безопасность.
Выводы:
- Если вы используете @Configuration, проходящую от некоторого класса, который автоматически запускает некоторые заданные функции, и вы переопределяете их, вы, вероятно, внутри
BeanPostProcessor
, поэтому не вводите beans и не пытайтесь использовать поведение AOP, потому что он не будет работать, и вы увидите, что Spring сообщит вам об этом с предыдущим сообщением в консоли. В этих случаях не используйте beans, а объекты (используя предложение new
).
- Если вам нужно использовать beans digg, о том, какой класс несет beans, который вы хотите установить в конце,
@Autowired
и добавьте те beans, как раньше.
Надеюсь, это может сэкономить вам некоторое время.
Ответ 7
@Async нельзя использовать в сочетании с обратными вызовами жизненного цикла, такими как @PostConstruct. Чтобы асинхронно инициализировать Spring beans, вам в настоящее время нужно использовать отдельную инициализацию Spring bean, которая затем вызывает аннотированный метод @Async для целевого объекта.
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();
}
}
источник
Ответ 8
написать независимую конфигурацию Spring для асинхронного bean.
например:
@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;
}
}
Я преодолеваю эту проблему с этой ситуацией.