首先来看看@Async异步注解的使用,它的作用的用的方法变为异步方法,本质就是创建了线程。它相比传统的创建线程的方式,使用@Async有多简洁呢?
先看这个演示,我这是一个Spring Boot项目:
这个@Async注解是直接加在方法上面,这样getStatus()就变成了异步方法
@SpringBootConfiguration
public class AsyncService {
@Async
public void getStatus(){
try {
Thread.sleep(2000);
System.out.println("<发送了一份邮件给用户>");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
配置好启动类,要在启动类上加上@EnableAsync注解才能使用。
@SpringBootApplication
@MapperScan("com.symc.mapper")
@EnableAsync
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
那么如何使用这个异步方法呢?很简单,直接调用它:
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/getMessage")
public String getMessage(){
System.out.println("<收到了用户的请求>");
asyncService.getStatus();
System.out.println("<正在处理用户请求>");
return "Status Code : 200";
}
}
尝试访问,观察结果。我们发现正常情况下代码应该是从上往下执行,同步的话浏览器应该是转圈圈等待两秒才能获取结果。而实际直接就返回了结果,这个测试结果说明了getStatus()是异步执行的,它本质是在此处创建了一个新的线程去执行这个方法。
这个注解的原理就相当于下面这段代码的效果,只不过Spring的这个@Async整合了线程池。
@RequestMapping("/getMessage")
public String getMessage(){
System.out.println("<收到了用户的请求>");
new Thread(new Runnable() {
public void run() {
asyncService.getStatus();
}
}).start();
System.out.println("<正在处理用户请求>");
return "Status Code : 200";
}
实现方式:使用aop拦截,只要在我们的方法上有使用到@MyAsync单独的开启一个异步线程执行我们的目标方法。所以首先你应该掌握Aop技术,我这里使用Spring的Aop实现。
首先要定义一个注解:com.symc.util.annotation.MyAsync
直接参考复制Spring 的@Async即可。
package com.symc.util.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: Forward Seen
* @CreateTime: 2022/04/30 21:05
* @Description: 异步注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAsync {
String value() default "";
}
然后定义一个切面类,因为要创建一个线程,实现异步的方法应该被包围,所以要使用环绕通知来实现。
通知的目标是注解MyAsync。
package com.symc.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @Author: Forward Seen
* @CreateTime: 2022/04/30 21:03
* @Description: MyAsync注解添加代码
*/
@Aspect
@Component
public class ThreadAsyncAop {
@Around(value = "@annotation(com.symc.util.annotation.MyAsync)")
public void around(final ProceedingJoinPoint cutPoint){
new Thread(() -> {
try {
cutPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}).start();
}
}
这样异步注解就算实现了。
可以尝试使用:
@Component
public class AsyncService {
@MyAsync
public void getStatus(){
try {
Thread.sleep(2000);
System.out.println("<发送了一份邮件给用户>");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/getMessage")
public String getMessage(){
System.out.println("<收到了用户的请求>");
asyncService.getStatus();
System.out.println("<正在处理用户请求>");
return "Status Code : 200";
}
}
用浏览器访问一样能实现上面的打印顺序。
最后我来谈谈这段代码的执行过程(推荐使用Debug观察执行顺序):
首先,我们使用浏览器访问/getMessage
,然后服务端就会执行AsyncController.getMessage()
,然后执行第一个代码<收到了用户的请求>
,接下来要执行asyncService.getStatus();
,这时候检查到该方法的注解头上有@MyAsync
,因为AOP拦截了该注解,立刻带着AsyncService.getStatus()
增强代码,执行拦截方法ThreadAsyncAop.around()
,然后创建线程,在该线程中执行cutPoint.proceed();
,也就是执行AsyncService.getStatus()
,这样该方法就是在一个新的线程中执行,从而达到异步效果。