在我們使用spring框架的過程中,在很多時候我們會使用@async注解來異步執行某一些方法,提高系統的執行效率。今天我們來探討下 spring 是如何完成這個功能的。
spring 在掃描bean的時候會掃描方法上是否包含@async的注解,如果包含的,spring會為這個bean動態的生成一個子類,我們稱之為代理類(?), 代理類是繼承我們所寫的bean的,然后把代理類注入進來,那此時,在執行此方法的時候,會到代理類中,代理類判斷了此方法需要異步執行,就不會調用父類 (我們原本寫的bean)的對應方法。spring自己維護了一個隊列,他會把需要執行的方法,放入隊列中,等待線程池去讀取這個隊列,完成方法的執行, 從而完成了異步的功能。我們可以關注到再配置task的時候,是有參數讓我們配置線程池的數量的。因為這種實現方法,所以在同一個類中的方法調用,添加@async注解是失效的!,原因是當你在同一個類中的時候,方法調用是在類體內執行的,spring無法截獲這個方法調用。
那在深入一步,spring為我們提供了AOP,面向切面的功能。他的原理和異步注解的原理是類似的,spring在啟動容器的時候,會掃描切面所定義的 類。在這些類被注入的時候,所注入的也是代理類,當你調用這些方法的時候,本質上是調用的代理類。通過代理類再去執行父類相對應的方法,那spring只 需要在調用之前和之后執行某段代碼就完成了AOP的實現了!
那最后我們還有一個問題,spring是如何動態的生成某一個類的子類的?代理類?
簡單介紹:
Spring為任務調度與異步方法執行提供了注解支持。通過在方法上設置@Async注解,可使得方法被異步調用。也就是說調用者會在調用時立即返回,而被調用方法的實際執行是交給Spring的TaskExecutor來完成。
開啟@Async注解:
<task:annotation-driven executor="annotationExecutor" /> <!-- 支持 @Async 注解 --> <task:executor id="annotationExecutor" pool-size="20"/>
同時加入<context:component-scan />掃描注解。
為了比較,先來一個同步調用:
@Component
public class TestAsyncBean {
public void sayHello4() throws InterruptedException {
Thread.sleep(2 * 1000);//網絡連接中 。。。消息發送中。。。
System.out.println("我愛你啊!");
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/applicationContext.xml"})
public class TestAsync {
@Test
public void test_sayHello4() throws InterruptedException, ExecutionException {
System.out.println("你不愛我了么?");
testAsyncBean.sayHello4();
System.out.println("回的這么慢, 你肯定不愛我了, 我們還是分手吧。。。");
Thread.sleep(3 * 1000);// 不讓主進程過早結束
}
}
輸出結果:
你不愛我了么? 我愛你啊! 回的這么慢, 你肯定不愛我了, 我們還是分手吧。。。
同步調用會按代碼順序依次進行下去,如果哪里需要等待,那么就阻塞在那里,不再向下繼續進行。
使用@Async的異步調用:
@Component
public class TestAsyncBean {
@Async
public void sayHello3() throws InterruptedException {
Thread.sleep(2 * 1000);//網絡連接中 。。。消息發送中。。。
System.out.println("我愛你啊!");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/applicationContext.xml"})
public class TestAsync {
@Autowired
private TestAsyncBean testAsyncBean;
@Test
public void test_sayHello3() throws InterruptedException, ExecutionException {
System.out.println("你不愛我了么?");
testAsyncBean.sayHello3();
System.out.println("你竟無話可說, 我們分手吧。。。");
Thread.sleep(3 * 1000);// 不讓主進程過早結束
}
}
輸出結果:
你不愛我了么? 你竟無話可說, 我們分手吧。。。 我愛你啊!
異步調用,通過開啟新的線程來執行調用的方法,不影響主線程。異步方法實際的執行交給了Spring的TaskExecutor來完成。
上面這種方式是沒有返回值的,下面嘗試有返回值的異步調用:
@Component
public class TestAsyncBean {
@Async
public String sayHello2() throws InterruptedException {
Thread.sleep(2 * 1000);//網絡連接中 。。。消息發送中。。。
return "我愛你啊!";// 調用方調用后會立即返回,所以返回null
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/applicationContext.xml"})
public class TestAsync {
@Autowired
private TestAsyncBean testAsyncBean;
@Test
public void test_sayHello2() throws InterruptedException, ExecutionException {
System.out.println("你不愛我了么?");
System.out.println(testAsyncBean.sayHello2());
System.out.println("你說的啥? 我們還是分手吧。。。");
Thread.sleep(3 * 1000);// 不讓主進程過早結束
}
}
輸出結果:
你不愛我了么? null 你說的啥? 我們還是分手吧。。。
通過直接獲取返回值得方式是不行的,這里就需要用到異步回調,異步方法返回值必須為Future<>,就像Callable與Future。
下面通過AsyncResult<>來獲得異步調用的返回值:
@Component
public class TestAsyncBean {
@Async
public Future<String> sayHello1() throws InterruptedException {
int thinking = 2;
Thread.sleep(thinking * 1000);//網絡連接中 。。。消息發送中。。。
System.out.println("我愛你啊!");
return new AsyncResult<String>("發送消息用了"+thinking+"秒");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/applicationContext.xml"})
public class TestAsync {
@Autowired
private TestAsyncBean testAsyncBean;
@Test
public void test_sayHello1() throws InterruptedException, ExecutionException {
Future<String> future = null;
System.out.println("你不愛我了么?");
future = testAsyncBean.sayHello1();
System.out.println("你竟無話可說, 我們分手吧。。。");
Thread.sleep(3 * 1000);// 不讓主進程過早結束
System.out.println(future.get());
}
}
輸出結果:
你不愛我了么? 你竟無話可說, 我們分手吧。。。 我愛你啊! 發送消息用了2秒
