【问题标题】:@Async not working in Spring API rest with Interfaces@Async 不能在 Spring API 中使用接口休息
【发布时间】:2019-01-11 19:00:03
【问题描述】:

我正在与@Async 一起使用休眠将一些数据并行存储在数据库中。我需要这样做,因为在将信息保存到数据库之前,我需要运行一些需要几分钟的任务。所以我实现了@Async。

问题是@Async 似乎不起作用。请在下面找到代码:

网络配置

@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>

那么,你能帮我理解为什么@Async 不起作用吗?

更新:@Async 现在正在工作,但我没有得到预期的结果。

对于这种情况,我必须等待结果(同步情况) CompletableFuture.get() 没有等待响应,我得到了错误:

我的代码:

CompletableFuture<Response> res = extractDataService.doTask(student);

错误:

org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection : <unknown>
Collection contents: [[]]
        at org.hibernate.collection.internal.AbstractPersistentCollection.setCurrentSession(AbstractPersistentCollection.java:627)
        at org.hibernate.event.internal.OnUpdateVisitor.processCollection(OnUpdateVisitor.java:46)
        at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:104)
        at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:65)
        at org.hibernate.event.internal.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:59)
        at org.hibernate.event.internal.AbstractVisitor.process(AbstractVisitor.java:126)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:293)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
        at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:648)
        at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:640)
        at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:635)
        at com.app.dao.CommonDaoImpl.addOrUpdate(CommonDaoImpl.java:28)
        at com.app.services.ExtractDataServiceImpl.doExtraction(ExtractDataServiceImpl.java:361)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:108)
        at org.springframework.aop.interceptor.AsyncExecutionAspectSupport$CompletableFutureDelegate$1.get(AsyncExecutionAspectSupport.java:237)
        at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

当我不必等待并以异步方式执行几个案例时,我得到:

12:17:44.040 [DEMO-4] DEBUG o.h.r.t.b.j.i.JdbcResourceLocalTransactionCoordinatorImpl - JDBC transaction marked for rollback-only (exception provided for stack trace)
java.lang.Exception: exception just for purpose of providing stack trace
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.markRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:265) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:156) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:581) [spring-orm-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [spring-aop-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:108) [spring-aop-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.aop.interceptor.AsyncExecutionAspectSupport$CompletableFutureDelegate$1.get(AsyncExecutionAspectSupport.java:237) [spring-aop-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590) [na:1.8.0_171]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_171]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_171]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_171]
12:17:44.043 [DEMO-4] DEBUG o.s.o.h.HibernateTransactionManager - Initiating transaction rollback after commit exception
org.hibernate.AssertionFailure: null id in com.app.model.FieldValue entry (don't flush the Session after an exception occurs)

【问题讨论】:

  • future.get() 这一行是什么?
  • @ErvinSzilagyi 据我了解是强制等待任务完成......但我只是在 saveWithWaiting 方法中使用它。
  • 是的,但是你的代码中没有声明future,忽略之后的语法错误。
  • 能否请您在doTaskssaveWithoutWaiting 方法中添加带有线程名称的日志,以检查它们是否在同一个线程中调用。您确定启动应用程序时会扫描WebConfig 吗?
  • @Async 实际上工作得很好。问题是你没有指定一个特定的TaskExecutor 来启动你的线程。异步请求处理(&lt;async-supported&gt;true&lt;/async-supported&gt;)也是异步执行方法的东西。那是不同的野兽。您对RunSomeTaskServiceImpl 的实现也存在缺陷,因为它不是线程安全的。 DispatcherServlet 旁边的另一个问题你有一个正在加载服务的ContextLoaderListener 吗?因为那会使您的 @EnableAsync 几乎毫无用处,因为它生活在不同的环境中。

标签: java multithreading asynchronous


【解决方案1】:

只需使用:

servlet.setAsyncSupported(true);

举例

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");
    }
}

【讨论】:

  • 我在 WebConfig.java 中实现了它,我得到了一个错误,说我有两个 ApplicationContext.. 所以,然后我发现在 web.xml 中我必须添加 true 用于执行您建议的相同配置,但它也不起作用...我将 web.xml 的定义添加到帖子中,以便您检查
  • 这是我得到的错误 java.lang.IllegalStateException: No unique WebApplicationContext found: more than one DispatcherServlet register with publishContext=true?
【解决方案2】:

更复杂的方法是实现 AsyncConfigurer 并将 AsyncExecutor 设置为 threadPoolTask​​Executor。

下面的示例代码

@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();
    }

} 

上面的配置会检测到任何提到的@Async注解

【讨论】:

  • 这是问题...我在与 WebConfig 相同的包中创建了一个新类,并将该包添加到基础包中,但我仍然不断收到数据库会话错误...我知道会话是重复的,类似的事情......
  • 您可以共享堆栈跟踪吗?谢谢。
  • 当然!补充说..我对此感到绝望。我正在做的是我正在做一些过程,然后我正在以并行方式更新数据库中的那些学生
  • 这使应用程序以异步方式执行方法!
【解决方案3】:

好吧,我终于成功了……

我通过以下方式使用 Executors:

ExecutorService executor = Executors.newFixedThreadPool(students.size());
for (StudentBO student : students) {
    executor.submit(() -> extractDataService.doTask(student));
}

doTask 是一个常规函数,当我不需要它在不同的线程中工作时,我就直接调用它。当我需要线程时,我使用上面的代码。

【讨论】:

  • 这只是一种解决方法。当然,使用您自己的执行者将完成这项工作。
  • 有了这个你就不需要@Async了。
  • 代码很危险,因为您在创建线程池时从未清理它们。最终您将耗尽资源和大量空闲线程。如果你想要这个,你必须在 finally 块中 shutdown executor。此外,如果您的系统中只有 16 个内核能够完成这项工作,您还想限制线程数并且不想创建 100 个线程。
  • @M.Deinum 和 finally 将在所有线程完成后被解雇?或者我如何确保它们在触发 finally 块之前完成?
  • 一旦没有更多任务要处理,shutdown 就会关闭。
【解决方案4】:

你可以做 CompletableFuture ,这样你就知道你的所有任务何时完成

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);

然后您可以使用 thenCompose 或 thenApply 进行管道化(以获取消费者)以完全控制任务管道。安全完成后,您可以关闭执行程序。

CompletetableFuture.allOff javadoc for more info

【讨论】:

  • 我更新了代码以使用 CompletableFuture.. 但我在休眠会话中遇到错误,我在帖子中添加了 stracktrace
【解决方案5】:

WebConfig.java 中的@EnableAsync 注解可能永远不会被扫描。 web.xml 指向 spring-context.xml。

您可以将 web.xml 中的 DispatcherServlet 定义更改为:

<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>

并将 spring-config.xml 中的所有配置包含到该类中。

或者 在 spring-config.xml 中添加&lt;task:annotation-driven&gt;

更新

目前,com.app.controller 包在 spring-config.xml 中被扫描。确保 WebConfig.java 位于此包或其子包之一中。如果不添加 WebConfig 的包到基本包属性,用逗号分隔。

此外,您可以控制异步任务使用的线程池。创建一个执行器 bean

@Bean
public Executor asyncTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setThreadNamePrefix("asynctaskpool-");
    executor.initialize();
    return executor;
} 

在你的异步方法中使用这样的bean名称

@Async("asyncTaskExecutor")
public Future<Response> doTasks(Student student);

这将确保所有任务都将在此线程池中执行。

【讨论】:

  • 我添加了 spring-config.xml 你能看一下并分享你的意见吗?
  • @Faabass 您的 WebConfig.java 是在 com.app.controller 包还是它的子包中?如果不是,则不会对其进行扫描,并且需要将其包添加到基本包中。 base-package 属性可以有多个逗号分隔的包,如base-package="x.y.z.service, x.y.z.controller"
  • 谢谢!我将它添加到基本包中,现在它在@Async 中工作,无论如何我有其他问题。请看我的帖子
  • 我强烈建议提出一个新问题并将其标记为原始问题的答案。
猜你喜欢
  • 1970-01-01
  • 2021-10-28
  • 2018-01-01
  • 2019-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多