Sunday, August 12, 2018

@Async not working in Spring API rest with Interfaces

Leave a Comment

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.

CompletetableFuture.allOff javadoc for more info

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment