当你想在Web应用中运行长时间的任务时,别忘了使用Spring的TaskExecutor,它能帮你管理相关组件。
Web应用中使用线程也不是什么罕见的事情,尤其是你需要开发长时间运行的任务时。
Spring中,我们必须特别注意并使用它已经提供的工具,而不是使用我们自己新建线程的方式。
我们希望我们的线程能够被Spring管理,这样就既能使用应用程序的其他组件,也不会带来任何坏影响。我们也希望优雅地关闭应用程序,而不是关闭的时候还有些工作还没完成。
Spring提供了TaskExecutor
作为任务执行者的抽象。
Spring的TaskExecutor
接口与java.util.concurrent.Executor
接口相同。
Spring发行版中包含了许多预先构建的TaskExecutor
实现,你可以从官方文档中找到更多关于它们的信息。
通过为Spring环境提供一个TaskExecutor
实现,你将能够将TaskExecutor
bean注入到其他bean中并可以访问托管线程:
package com.gkatzioura.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by gkatzioura on 4/26/17.
*/
@Service
public class AsynchronousService {
@Autowired
private ApplicationContext applicationContext;
// 注入一个TaskExecutor实现
@Autowired
private TaskExecutor taskExecutor;
// 该方法是同步的,但是其调用taskExecutor.execute()中的run()会在另外一个
// 线程中异步执行
public void executeAsynchronously() {
taskExecutor.execute(new Runnable() {
@Override
public void run() {
// 这里是需要异步执行的长时间任务逻辑
}
});
}
}
想像上面那样使用TaskExecutor
,第一步其是将TaskExecutor
配置添加到我们的Spring应用程序中:
package com.gkatzioura.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* Created by gkatzioura on 4/26/17.
*/
@Configuration
public class ThreadConfig {
// 定义一个类型为TaskExecutor的bean,方法名字无所谓
@Bean
public TaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(4);
executor.setThreadNamePrefix("default_task_executor_thread");
executor.initialize();
return executor;
}
}
一旦我们配置了执行器(译注:也就是上面配置的TaskExecutor
bean),任务处理就很简单了。我们将执行器TaskExecutor
bean注入到另外的Spring组件,然后提交包含要执行任务的Runnable
对象就可以了。
因为异步代码可能需要与应用程序的其他组件进行交互,所以最好的方法是创建原型作用域(prototype
)的Runnable
bean实例然后注入。例子如下:
package com.gkatzioura;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* Created by gkatzioura on 10/18/17.
*/
@Component
@Scope("prototype")
public class MyThread implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(MyThread.class);
@Override
public void run() {
LOGGER.info("Called from thread");
}
}
然后,我们就可以将执行器(上面类型为TaskExecutor
的executor)注入我们的服务并使用它来执行Runnable
实例了。
package com.gkatzioura.service;
import com.gkatzioura.MyThread;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by gkatzioura on 4/26/17.
*/
@Service
public class AsynchronousService {
@Autowired
private TaskExecutor taskExecutor;
@Autowired
private ApplicationContext applicationContext;
public void executeAsynchronously() {
// 获取我们自定义的作用域为prototype的可执行任务Runnable bean
MyThread myThread = applicationContext.getBean(MyThread.class);
// 使用 taskExecutor对其进行异步执行
taskExecutor.execute(myThread);
}
}
在下一篇文章中,我们将通过使用Spring的异步函数将我们的多线程代码库提升到一个新的层次。
你可以在GitHub上找到本文提到的源代码。