I'm working with @Async to stored some data in parallel in the database with hibernate. I need to do that because before saving the information to the database I need to run some task that takes several minutes. So I implemented @Async.
The issue is that @Async seems to not be working. Please find the code below:
WebConfig
@Configuration @EnableAsync @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { }
StudentServiceImpl:
@Autowired RunSomeTaskService runSomeTaskService; @Override Transactional public Response saveWithoutWaiting(StudentBO[] students, String username) throws Exception { ... for (StudentBO student : students) { .... Future<Response> response = runSomeTaskService.doTasks(student); //Finish without waiting for doTasks(). } @Override Transactional public Response saveWithWaiting(StudentBO[] students, String username) throws Exception { ... for (StudentBO student : students) { .... Future<Response> response = runSomeTaskService.doTasks(student); //Finish and wait for doTasks(). response.get(); }
RunSomeTaskService:
public interface RunSomeTaskService{ @Async public Future<Response> doTasks(Student student); }
RunSomeTaskServiceImpl:
public class RunSomeTaskServiceImpl extends CommonService implements RunSomeTaskService{ Student student; @Override public Future<Response> doTasks(Student student) { Response response = new Response(); this.student = student; //do Task return new AsyncResult<Response>(response); } }
web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>Sample Spring Maven Project</display-name> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>jwtTokenAuthFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>jwtTokenAuthFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
spring.config.xml
<?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:util="http://www.springframework.org/schema/util" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:annotation-config /> <context:component-scan base-package="com.app.controller" /> <tx:annotation-driven transaction-manager="transactionManager"/> <mvc:annotation-driven /> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> ... </bean> <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> ... </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses"> <list> <value>//every model generated with Hibernate</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> <prop key="hibernate.show_sql">true</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="persistenceExceptionTranslationPostProcessor" class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" /> <bean id="studentService" class="com.app.services.StudentServiceImpl"></bean> <bean id="studentDao" class="com.app.dao.StudentDaoImpl"></bean> ... <bean id="jwtTokenAuthFilter" class="com.app.security.JWTTokenAuthFilter" /> </beans>
So, could you please help me to understand why @Async is not working?
5 Answers
Answers 1
Here you find the solutions
// servlet.setAsyncSupported(true);
//For Example
public class WebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(WebConfig.class); ctx.setServletContext(servletContext); ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); servlet.setAsyncSupported(true); //Servlets were marked as supporting async // For CORS Pre Filght Request servlet.setInitParameter("dispatchOptionsRequest", "true"); } }
Answers 2
Well, finally I make it work...
I used Executors in the following way:
ExecutorService executor = Executors.newFixedThreadPool(students.size()); for (StudentBO student : students) { executor.submit(() -> extractDataService.doTask(student)); }
Where doTask is a regular function, that when I don't need it to work in a different thread, I just call it as it is. When I need the threads, I use the code above.
Answers 3
More Sophisticated way would be to implement AsyncConfigurer and set the AsyncExecutor to threadPoolTaskExecutor.
Sample Code below
@Configuration @EnableAsync(proxyTargetClass=true) //detects @Async annotation public class AsyncConfig implements AsyncConfigurer { public Executor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); // create 10 Threads at the time of initialization executor.setQueueCapacity(10); // queue capacity executor.setMaxPoolSize(25); // if queue is full, then it will create new thread and go till 25 executor.setThreadNamePrefix("DEMO-"); executor.initialize();//Set up the ExecutorService. return executor; } @Override public Executor getAsyncExecutor() { return threadPoolTaskExecutor(); } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new YOUR_CUSTOM_EXCEPTION_HANDLER(); } }
The above configuration will detect @Async annotation wherever mentioned
Answers 4
You can do CompletableFuture , with this you know when all your tasks are complete
List<CompletableFuture<T>> futureList = new ArrayList<>(); for(Student student:studentList){ CompletableFuture<T> returnedFuture = CompletableFuture.supplyAsync(() -> doSomething(student),executor).exceptionally(e -> { log.error("Error occured in print something future",e); return 0; }); futureList.add(returnedFuture); } Completable.allOf(futureList);
Then you can pipeline with thenCompose or thenApply (to take consumer) to have complete control on the task pipeline. you can shutdonw executors when you are done safely.
Answers 5
There is possibility that the @EnableAsync annotation in WebConfig.java is never scanned. The web.xml points to the spring-context.xml.
You can change the DispatcherServlet definition in web.xml to:
<servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value> com.yourpackage.WebConfig </param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet>
And include all configuration from spring-config.xml to this class.
Or Add <task:annotation-driven>
in spring-config.xml.
Updated
Currently, com.app.controller
package is scanned in spring-config.xml. Make sure the WebConfig.java is in this package or one of it's sub-package. If not add WebConfig's package to base package attribute separated by comma.
Additionally, you can control the thread pool used by async task. Create a executor bean
@Bean public Executor asyncTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setThreadNamePrefix("asynctaskpool-"); executor.initialize(); return executor; }
And in your async method use the bean name like this
@Async("asyncTaskExecutor") public Future<Response> doTasks(Student student);
This will ensure all task will be executed in this thread pool.
0 comments:
Post a Comment