SpringBoot入门建站全系列(二十二)异步任务执行的几种方式

SpringBoot入门建站全系列(二十二)异步任务执行的几种方式

一、概述

异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行。

实现异步任务的方式有很多,但是可以总结为多线程异步和多进程异步。

多线程异步:

  • 多线程实现异步就是新建个线程,将任务交给新线程执行。
  • 不管是自己new Thread实现异步,还是使用ThreadPoolTaskExecutor线程池,还是使用Spring的@EnableAsync注解,这些都是多线程实现的异步。

多进程异步:

  • 将任务交给另外一个进程处理,已经不在本应用中了。
  • 比如将任务交给MQ,这个就是异步操作了。因为交给MQ以后,你不必等待结果返回。
  • 当然,如果你调用另外一个应用/进程,另外的一个应用/进程将任务加入任务队列,然后立即返回你成功失败,那这个过程也属于异步任务。这个过程就是MQ做的事情了。。

本篇重点讲述多线程异步任务的执行方式。多进程方式可以查看《SpringBoot入门建站全系列(十七)整合ActiveMq(JMS类消息队列)》《SpringBoot入门建站全系列(十八)整合RabbitMQ(AMQP类消息队列)》中的消息发送方式。

二、前言

多线程的异步任务执行有几种方式,还是前面所说的那样,方式虽然不同,但原理是一样的,就是在新线程中执行任务,但是还是要说一下这几种方式。

  • new Thread 普通线程方式执行;
  • ThreadPool线程池,线程使用频繁的时候,减少线程创建回收的开销。实现方式有多种,但是jdk1.5开始支持线程池,就在java.util.concurrent里。
  • Spring提供的@Async注解。其实也是基于线程池。
  • 注意Runnable和Callable在异步任务中的应用,一个不带返回值,一个带返回值而已。

本文是在Springboot环境中测试的。引入spring-boot-starter-web即可。如果不会搭建,可以打开这篇文章看一看《SpringBoot入门建站全系列(一)项目建立》

在web项目中测试会比较方便,我们利用RequestContextHolder获取ThreadLocal的Request对象来判断线程是否改变了

假设我们要执行的任务是这样的, 为了异步执行AsyncTaskService 中的asyncTask方法:

package com.cff.springbootwork.async.service;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Service
public class AsyncTaskService {
	private Logger log = LoggerFactory.getLogger(this.getClass());

	/**
	 * 普通的一方法而已,供异步任务调用
	 */
	public void asyncTask() {
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		if (requestAttributes != null) {
			HttpServletRequest curRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
			log.info("异步任务开始执行,当前请求属性test为:{}", curRequest.getAttribute("test"));
		} else {
			log.info("异步任务不是同一个线程了,别想拿ThreadLocal对象了");
		}

		try {
			Thread.sleep(5000);
			log.info("我是异步任务,我就是个打印!");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		log.info("5s后异步任务终于执行完成");
	}

}

三、普通线程方式

首先我们在Request中塞入一个值,后面用来获取判断是否是主线程。

然后用new Thread新建线程执行异步任务。

我们将逻辑写在AsyncService中,来调用AsyncTaskService的asyncTask方法。

AsyncService:

/**
	 * 测试new Thread 异步任务
	 */
	public void asyncThread() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				asyncTaskService.asyncTask();
			}
		});
		thread1.start();

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

结果如下:

2019-08-08 16:39:50,516 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:39:50,517 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:39:50,522 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:39:50,530 [Thread-4][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:39:55,530 [Thread-4][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:39:55,530 [Thread-4][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
  • 首先获取下request的test属性,能获取到;
  • 执行异步任务,拿不到request对象了,因为不是同一个线程,ThreadLocal没有。
  • 程序直接返回了,异步任务5s以后才执行完。

四、线程池方式

首先我们在Request中塞入一个值,后面用来获取判断是否是主线程。

然后将异步任务扔给线程池执行。

这里我们分别使用Runnable和Callable来测试下。

先建立一个线程池:

package com.cff.springbootwork.async.service;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.stereotype.Service;

@Service
public class ThreadPoolService {
	private ExecutorService executor;

	@PostConstruct
	public void init() {
//		executor = new ThreadPoolExecutor(20, 30, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
//				new ThreadPoolExecutor.AbortPolicy());
		
		executor = Executors.newCachedThreadPool();
	}

	public void execute(Runnable task) {
		executor.execute(task);
	}

	public <T> Future<T> submit(Callable<T> task) {
		return executor.submit(task);
	}

	@PreDestroy
	public void shutdown() {
		executor.shutdown();
	}
}

4.1 Runnable普通异步任务

我们将逻辑写在AsyncService中,来调用AsyncTaskService的asyncTask方法。

AsyncService:

/**
	 * 测试 线程池 异步任务
	 */
	public void asyncThreadPool() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		threadPoolService.execute(new Runnable() {
			public void run() {
				asyncTaskService.asyncTask();
			}
		});

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

结果如下:

2019-08-08 16:44:19,136 [http-nio-8080-exec-4][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:44:19,136 [http-nio-8080-exec-4][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:44:19,137 [http-nio-8080-exec-4][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:44:19,138 [pool-1-thread-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:44:24,140 [pool-1-thread-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:44:24,140 [pool-1-thread-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
  • 首先获取下request的test属性,能获取到;
  • 执行异步任务,拿不到request对象了,因为不是同一个线程,ThreadLocal没有。
  • 程序直接返回了,异步任务5s以后才执行完。

和new Thread方式一样。唯一区别就是它是线程池,线程可以回收,线程使用频繁的时候,减少线程创建回收的开销。

4.2 Callable异步任务返回结果

我们将逻辑写在AsyncService中,来调用AsyncTaskService的asyncTask方法。

AsyncService:

/**
	 * 测试 线程池 异步任务 Future回调
	 */
	public void asyncFuturePool() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		Callable<String> callable = new Callable<String>() {
			@Override
			public String call() throws Exception {
				asyncTaskService.asyncTask();
				return "1111";
			}
		};

		Future<String> future = threadPoolService.submit(callable);

		try {
			// future.get(); // 阻塞函数,如果直接用,它就一直阻塞,就不是异步了。
			if (future.isDone()) {
				log.info("这么快就完成了?不可能!");
				String result = future.get();
				log.info("任务结果为:{}", result);
			}
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		} catch (ExecutionException e1) {
			e1.printStackTrace();
		}

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

如果注掉future.get(),结果如下:

2019-08-08 16:46:44,001 [http-nio-8080-exec-7][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:46:44,002 [http-nio-8080-exec-7][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:46:44,003 [http-nio-8080-exec-7][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:46:44,003 [pool-1-thread-2][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:46:49,003 [pool-1-thread-2][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:46:49,003 [pool-1-thread-2][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成

这个过程和4.1完全一样了。

如果不注掉future.get(),结果如下:

2019-08-08 16:48:02,472 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:48:02,472 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:48:02,478 [pool-1-thread-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:48:07,478 [pool-1-thread-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:48:07,478 [pool-1-thread-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
2019-08-08 16:48:07,478 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 这么快就完成了?不可能!
2019-08-08 16:48:07,478 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 任务结果为:1111
2019-08-08 16:48:07,478 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd

虽然拿到了结果,也使用了多线程,但是这个过程变成同步了,因为主线程一直等待另外一个线程执行完才执行下一步。

五、Spring的@Async注解方式

@Async注解的方法,不能和调用方法在同一个类中,因为它是动态代理调用的。同一个类中动态代理个毛啊。。

首先要使用注解@EnableAsync开启异步执行功能。

package com.cff.springbootwork.async.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class AsyncConfig {
	
}

5.1 同一个类中测试

我们将逻辑写在AsyncService中,新建个方法asyncTaskAnnotation,加上@Async注解并调用AsyncTaskService的asyncTask方法,进行测试。

AsyncService:

/**
	 * 测试 @Async 对异步任务的支持
	 */
	@Async
	public void asyncTaskAnnotation() {
		asyncTaskService.asyncTask();
	}

	/**
	 * 测试 Spring的@Async 对异步任务的支持, 同一个类内的方法不能实现异步
	 */
	public void asyncSpringAnnotationOne() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		asyncTaskAnnotation();

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

测试结果如下:

2019-08-08 16:54:09,957 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:54:09,957 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:54:09,962 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 异步任务开始执行,当前请求属性test为:asdasd
2019-08-08 16:54:14,962 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:54:14,962 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
2019-08-08 16:54:14,962 [http-nio-8080-exec-1][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd

可以看出,异步任务竟然可以拿到request的test属性了,表明这压根就是同一个线程,@Async无效。

5.2 不同类中测试

我们将逻辑写在AsyncService中,在AsyncTaskService新建个方法asyncTaskAnnotation,加上@Async注解并调用asyncTask方法,进行测试。

AsyncService:

/**
	 * 测试 Spring的@Async 对异步任务的支持, 不同类的方法可以实现异步
	 */
	public void asyncSpringAnnotationMuti() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		asyncTaskService.asyncTaskAnnotation();

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

测试结果如下:

2019-08-08 16:56:50,964 [http-nio-8080-exec-6][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:56:50,964 [http-nio-8080-exec-6][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:56:50,964 [http-nio-8080-exec-6][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:56:50,965 [SimpleAsyncTaskExecutor-2][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:56:55,965 [SimpleAsyncTaskExecutor-2][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:56:55,965 [SimpleAsyncTaskExecutor-2][IP:|USER:][INFO  com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成

可以看出,异步任务拿不到request对象了,@Async正常使用。

六、 测试类及Service完整代码

AsyncRest:

package com.cff.springbootwork.async.web;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.cff.springbootwork.async.service.AsyncService;

/**
 * 测试乐异步任务
 * 
 * @author fufei
 *
 */
@RestController
@RequestMapping("/async")
public class AsyncRest {

	@Autowired
	AsyncService asyncService;

	@RequestMapping(value = "/thread", method = { RequestMethod.GET })
	public String thread(HttpServletRequest request) {
		request.setAttribute("test", "asdasd");
		asyncService.asyncThread();
		return "0000";
	}

	@RequestMapping(value = "/pool", method = { RequestMethod.GET })
	public String pool(HttpServletRequest request) {
		request.setAttribute("test", "asdasd");
		asyncService.asyncThreadPool();
		return "0000";
	}

	@RequestMapping(value = "/future", method = { RequestMethod.GET })
	public String future(HttpServletRequest request) {
		request.setAttribute("test", "asdasd");
		asyncService.asyncFuturePool();
		return "0000";
	}

	@RequestMapping(value = "/springOne", method = { RequestMethod.GET })
	public String springOne(HttpServletRequest request) {
		request.setAttribute("test", "asdasd");
		asyncService.asyncSpringAnnotationOne();
		return "0000";
	}

	@RequestMapping(value = "/springMuti", method = { RequestMethod.GET })
	public String springMuti(HttpServletRequest request) {
		request.setAttribute("test", "asdasd");
		asyncService.asyncSpringAnnotationMuti();
		return "0000";
	}
}

AsyncService:

package com.cff.springbootwork.async.service;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Service
public class AsyncService {
	private Logger log = LoggerFactory.getLogger(this.getClass());

	@Autowired
	AsyncTaskService asyncTaskService;

	@Autowired
	ThreadPoolService threadPoolService;

	/**
	 * 测试new Thread 异步任务
	 */
	public void asyncThread() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				asyncTaskService.asyncTask();
			}
		});
		thread1.start();

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

	/**
	 * 测试 线程池 异步任务
	 */
	public void asyncThreadPool() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		threadPoolService.execute(new Runnable() {
			public void run() {
				asyncTaskService.asyncTask();
			}
		});

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

	/**
	 * 测试 线程池 异步任务 Future回调
	 */
	public void asyncFuturePool() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		Callable<String> callable = new Callable<String>() {
			@Override
			public String call() throws Exception {
				asyncTaskService.asyncTask();
				return "1111";
			}
		};

		Future<String> future = threadPoolService.submit(callable);

		try {
//			future.get(); // 阻塞函数,如果直接用,它就一直阻塞,就不是异步了。
			if (future.isDone()) {
				log.info("这么快就完成了?不可能!");
				String result = future.get();
				log.info("任务结果为:{}", result);
			}
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		} catch (ExecutionException e1) {
			e1.printStackTrace();
		}

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

	/**
	 * 测试 @Async 对异步任务的支持
	 */
	@Async
	public void asyncTaskAnnotation() {
		asyncTaskService.asyncTask();
	}

	/**
	 * 测试 Spring的@Async 对异步任务的支持, 同一个类内的方法不能实现异步
	 */
	public void asyncSpringAnnotationOne() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		asyncTaskAnnotation();

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

	/**
	 * 测试 Spring的@Async 对异步任务的支持, 不同类的方法可以实现异步
	 */
	public void asyncSpringAnnotationMuti() {
		log.info("开始执行任务");
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		log.info("请求属性test为:{}", request.getAttribute("test"));

		asyncTaskService.asyncTaskAnnotation();

		HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
	}

}

AsyncTaskService:

package com.cff.springbootwork.async.service;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Service
public class AsyncTaskService {
	private Logger log = LoggerFactory.getLogger(this.getClass());

	/**
	 * 普通的一方法而已,供异步任务调用
	 */
	public void asyncTask() {
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		if (requestAttributes != null) {
			HttpServletRequest curRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
			log.info("异步任务开始执行,当前请求属性test为:{}", curRequest.getAttribute("test"));
		} else {
			log.info("异步任务不是同一个线程了,别想拿ThreadLocal对象了");
		}

		try {
			Thread.sleep(5000);
			log.info("我是异步任务,我就是个打印!");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		log.info("5s后异步任务终于执行完成");
	}
	
	@Async
	public void asyncTaskAnnotation() {
		asyncTask();
	}
}

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端达人

「JS小技巧」随机不重复的ID,模板标签替换,XML与字符串互转,快速取整

今天笔者整理了一份自己最近用到的一些小技巧分享给大家,虽然都是基础技术,不过在某些特殊时刻还蛮有用的,不至于加载一堆体积庞大的第三方库,今天笔者用本文归纳一下分...

17420
来自专栏全栈者

阿里开源框架egg.js入门与实战

Eggjs是一个基于Koajs的框架,所以它应当属于框架之上的框架,它继承了Koajs的高性能优点,同时又加入了一些约束与开发规范,来规避Koajs框架本身的...

31730
来自专栏趣谈前端

一张图教你快速玩转vue-cli3

本文系统的梳理了vue-cli3搭建项目的常见用法,目的在于让你快速掌握独立搭建vue项目的能力。你将会了解如下知识点:

8810
来自专栏全栈者

nodejs框架Koa做中间层使用总结(含示例)

一个以nodejs为基础的一个后台框架。直白一点来说,就是一个javascript语言需要编写的库,它的定位是作为服务端应用提供服务,本身对外暴露了一些api,...

61220
来自专栏全栈者

看看你知道的“浅拷贝”是对的吗

关于本篇文章的起源是一位大佬在面试的时候,询问应聘者关于浅拷贝的知识后,在应聘者的回答中,笔者发现有好一部分人对浅拷贝都是错误的,故有了此篇内容。

7220
来自专栏call_me_R

nginx处理跨域

最近从mac转成用window来开发,在安装nginx的时候碰了下钉子,那我就不开心了。想着既然都安装好了,那么就搞点事情吧~

51420
来自专栏前端自习课

【JS】312- 复习 JavaScript 严格模式(Strict Mode)

注:本文为 《 JavaScript 完全手册(2018版) 》第30节,你可以查看该手册的完整目录。

11430
来自专栏全栈者

重温基础:ES9系列

所有整理的文章都收录到我《Cute-JavaScript》系列文章中,访问地址:http://js.pingan8787.com

10610
来自专栏趣谈前端

9012教你如何使用gulp4开发项目脚手架

由于本文重点是介绍gulp4.0搭建脚手架的思路,相关插件的用法以及项目结构的设计,由于gulp的基本用法很简单,如果不熟悉可以移步官网自行研究学习。该脚手架的...

14910
来自专栏前端达人

「CSS 3D 专题」学习前,你需要搞明白什么是CSS 3D?

CSS最令人兴奋的新领域之一莫过于在3D空间操作网页元素,这一新技能给你打开了3D世界的大门,如果你能Get这一项技能,你也能绘制这个真实的世界。CSS 3D ...

11520

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励