为什么需要异步任务呢?
1、假如向服务端发送一个请求(假设:给批量给用户发邮件或者短信通知或者批量消息推送),由于底层用户量基数很大,如果客户端同步发起指令后长时间等待,会造成客户端连接time out,或者等待超时的请求,这种需求一般客户端不需要全部
等会所有底层业务执行完毕,只需要接收操作成功或者命令已执行的指令。这种就需要使用异步操作。
- 一般场景下,我们会使用多线程的方式,发送请求后,重新开启一个线程去执行;(异步线程)这个能够达到我们的目的。
- 今天记录一下使用spring 3.X 以后提供的注解
@EnableAysnc、@Aysnc
项目准备(基于springboot)
pom.xml依赖引入
<dependencies>
<!--引入springbooweb,他会根据依赖引入springboot相关包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!--这个不是必须,这个是一个非常强大的工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.8</version>
</dependency>
<!--这个是一个不需要写get/set等快捷编码,写上相关注释后,编译器会自动生成代码,快速开发,代码简洁需要-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
</dependencies>
配置文件(yml)
application.yml配置文件名称
可以有可以没有,这个如果要自己重新指定端口子类的,重新定义就行了。
--我这里就啥不管,启动后默认端口就是8080
--如果要重新制定,这样就行了
server:
port: 8080
代码编写
主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync// 开启异步注解功能【必须要开启,否则异步任务不起作用】
public class MainRun {
public static void main(String[] args) {
SpringApplication.run(MainRun.class, args);
}
}
为什么呢?因为@EnableAsync注解里面导入了AsyncConfigurationSelector到容器,这样就会自动识别@Async注解,开启异步任务支持了。
@Import(AsyncConfigurationSelector.class)
业务代码编写(controller)
import com.wolf.boy.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/hello")
public Object hello() {
asyncService.hello();
return "success 处理成功";
}
@GetMapping("/simpleHello")
public Object simpleHello() {
asyncService.simpleHello();
return "simpleHello 处理成功";
}
}
业务代码编写(asyncService)
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
*
* 都是利用线程睡眠,模拟系统处理时间消耗
*/
@Service
public class AsyncService {
@Async // 表示这是一个异步方法,再线程池中获取一个线程来单独执行该方法
public void hello() {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("处理数据中..");
}
//普通方法
public void simpleHello() {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("处理数据中..");
}
}
测试用例
启动程序后,访问
localhost:8080/hello 可以看到客户端马上能够收到服务端响应。通过代码可以看见这里调用的是一个加了注解@Async的方法,说明这是一个异步方法,验证是成功的;
在输入
localhost:8080/simpleHello 这个访问后,浏览器一直等待服务端响应,由于服务端处理需要3秒以后才会执行业务处理,所以浏览器等待三秒以后才会看到服务端给予客户端的数据响应;
到此为止,异步任务的支持就完成了。
源码学习和理解一下,这个到底怎么执行的。
- 加入异步任务支持后,比如异步任务总的有线程池去执行,否则你加了又有什么用。
- 在springboot的spring-boot-autoconfigure包中,可以看到有一个对线程池的初始化AutoConfiguation类叫(TaskExecutionAutoConfiguration)
import java.util.concurrent.Executor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Shutdown;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.boot.task.TaskExecutorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link TaskExecutor}.
*
* @author Stephane Nicoll
* @author Camille Vienot
* @since 2.1.0
*/
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {
/**
* Bean name of the application {@link TaskExecutor}.
*/
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
ObjectProvider<TaskDecorator> taskDecorator) {
TaskExecutionProperties.Pool pool = properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
builder = builder.taskDecorator(taskDecorator.getIfUnique());
return builder;
}
//这里就通过上面创建的TaskExecutorBuilder构造器使用build方法,创建了一个ThreadPoolTaskExecutor
@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}
当然你也可以根据需要自定义线程池