An @Async
method in a @Service
-annotated class is not being called asynchronously - it's blocking the thread.
I've got <task: annotation-driven />
in my config, and the call to the method is coming from outside of the class so the proxy should be being hit. When I step through the code, the proxy is indeed hit, but it doesn't seem to go anywhere near any classes related to running in a task executor.
I've put breakpoints in AsyncExecutionInterceptor
and they never get hit. I've debugged into AsyncAnnotationBeanPostProcessor
and can see advice getting applied.
The service is defined as an interface (with the method annotated @Async
there for good measure) with the implementation's method annotated @Async
too. Neither are marked @Transactional
.
Any ideas what may have gone wrong?
-=UPDATE=-
Curiously, it works only when I have my task
XML elements in my app-servlet.xml file, and not in my app-services.xml file, and if I do my component scanning over services from there too. Normally I have one XML file with only controllers in it (and restrict the component-scan accordingly), and another with services in it (again with a component-scan restricted such that it doesn't re-scan the controllers loaded in the other file).
app-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 (doesn't work when specified here)
<?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"/>
...
Am I missing something glaringly obvious in my configuration, or is there some subtle interplay between config elements going on?
proxy-target-class="true"
to all<*:annotation-driven/>
elements that support this attribute.@Async
is public.With the help of this excellent answer by Ryan Stewart, I was able to figure this out (at least for my specific problem).
In short, the context loaded by the
ContextLoaderListener
(generally from applicationContext.xml) is the parent of the context loaded by theDispatcherServlet
(generally from*-servlet.xml
). If you have the bean with the@Async
method declared/component-scanned in both contexts, the version from the child context (DispatcherServlet
) will override the one in the parent context (ContextLoaderListener
). I verified this by excluding that component from component scanning in the*-servlet.xml
-- it now works as expected.I realized following the tutorial async-method tutorial code that my issue source was: the bean with the annotated
@Async
method was not being created wrapped in a proxy. I started digging and realized that there was a message sayingYou can see here responses about this issue and its basically that BeanPostProcessors are required by every Bean, so every bean injected here and its dependencies will be excluded to be processed later by other BeanPostProcessors, because it corrupted the life cycle of beans. So identify which is the
BeanPostProcessor
that is causing this and dont use or create beans inside of it.In my case i had this configuration
WsConfigurerAdapter
is actually aBeanPostProcessor
and you realize it because there is always a pattern:@Configuration
that extends classes and override some of it functions to install or tweak beans involved in some non functional features, like web service or security.In the aforementioned example you have to override the
addInterceptors
and added interceptors beans, so if you are using some annotation like@Async
insideDefaultPayloadLoggingInterceptor
it wont work. What is the solution? Get ride ofWsConfigurerAdapter
to start. After digging a bit i realized a class namedPayloadRootAnnotationMethodEndpointMapping
at the end was which had all valid interceptors, so i did it manually insted of overriding a function.So this will be run after all
BeanPostProcessor
have done their job. ThesetupInterceptors
function will run when that party is over and install the interceptors beans. This use case may be extrapolated to cases like Security.Conclusions:
BeanPostProcessor
, so dont inject beans there and try to use AOP behaviour, because it wont work, and you will see Spring tells it to you with the beforementioned message in the console. In those cases dont use beans but objects (using thenew
clause).@Autowired
it and add those beans like i did before.I hope this may save some time for you.
You need 3 lines of code for Async to work
@Service @EnableAsync public myClass {
@Async public void myMethod(){
}
@Async can not be used in conjunction with lifecycle callbacks such as @PostConstruct. To asynchonously initialize Spring beans you currently have to use a separate initializing Spring bean that invokes the @Async annotated method on the target then.
source
For me the solution was to add
@EnableAsync
on my@Configuration
annotated class:Now the class in package
bla.package
which has@Async
annotated methods can really have them called asynchronously.