你好呀,我是沉默王二,一個和黃家駒一樣身高,劉德華一樣顏值的程序員(不信圍觀朋友圈唄)。從 2 位偶像的年紀(jì)上,你就可以斷定我的碼齡至少在 10 年以上,但實話實說,我一直堅信自己只有 18 歲,因為好學(xué)使我年輕。本篇文章就打算通過我和三妹對話的形式來聊一聊“Spring 的 Aware、異步編程、計劃任務(wù)”。
教妹學(xué) Spring,沒見過這么放肆的標(biāo)題吧?“語不驚人死不休”,沒錯,本篇文章的標(biāo)題就是這么酷炫,不然你怎么會點進來?

我有一個漂亮如花的妹妹(見上圖,怎么又變了?還不能一天做個夢),她叫什么呢?我想聰明的讀者能猜得出:沉默王三,沒錯,年方三六。父母正考慮讓她向我學(xué)習(xí),做一名正兒八經(jīng)的 Java 程序員。我一開始是反對的,因為程序員這行業(yè)容易掉頭發(fā),女生可不適合掉頭發(fā)。但家命難為啊,與其反對,不如做點更積極的事情,比如說寫點有趣的文章教教她。

“二哥,聽說今天要學(xué)習(xí) Spring 的 Aware、異步編程、計劃任務(wù),真的是翹首以盼啊?!?/p>
“哎呀,三妹,瞧你那迫不及待的大眼神,就好像昨晚上月亮一樣圓,一樣大?!?/p>
01、Spring Aware
“二哥,據(jù)說 Aware 的目的是讓 Bean 獲取 Spring 容器的服務(wù),你能給我具體說說嗎?”
“沒問題啊?!?/p>
Bean 一般不需要了解容器的狀態(tài)和直接使用容器,但是在某些情況下,需要在 Bean 中直接對容器進行操作,這時候,就可以通過特定的 Aware 接口來完成。常見的 Spring Aware 接口有下面這些:
| Aware 子接口 | 描述 |
|---|---|
| BeanNameAware | 獲取容器中 Bean 的名稱 |
| BeanFactoryAware | Bean 被容器創(chuàng)建以后,會有一個相應(yīng)的 BeanFactory,可以直接通過它來訪問容器 |
| ApplicationContextAware | Bean 被初始化后,會被注入到 ApplicationContext,可以直接通過它來訪問容器 |
| MessageSourceAware | 獲取 Message Source 的相關(guān)文本信息 |
| ResourceLoaderAware | 獲取資源加載器,以獲取外部資源文件 |
1)BeanNameAware
新建一個 MyBeanName 類,內(nèi)容如下:
public class MyBeanName implements BeanNameAware {
@Override
public void setBeanName(String beanName) {
System.out.println(beanName);
}
}
MyBeanName 實現(xiàn)了 BeanNameAware 接口,并重寫了 setBeanName() 方法。beanName 參數(shù)表示 Bean 在 Spring 容器中注冊的 name。
新建一個 Config 類,內(nèi)容如下:
@Configuration
public class Config {
@Bean(name = "myCustomBeanName")
public MyBeanName getMyBeanName() {
return new MyBeanName();
}
}
@Bean 注解用在 getMyBeanName() 方法上,表明當(dāng)前方法返回一個 Bean 對象(MyBeanName),并通過 name 屬性指定 Bean 的名字為“myCustomBeanName”。
新建 BeanNameMain 類,代碼如下:
public class BeanNameMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanNameConfig.class);
MyBeanName myBeanName = context.getBean(MyBeanName.class);
context.close();
}
}
程序輸出的結(jié)果如下所示:
myCustomBeanName
如果把 @Bean() 注解中的 (name = "myCustomBeanName)" 去掉的話,程序輸出的內(nèi)容將會是 BeanNameConfig 類的 getMyBeanName() 的方法名“getMyBeanName”。
2)BeanFactoryAware
新建一個 MyBeanFactory 類,內(nèi)容如下:
public class MyBeanFactory implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public void getMyBeanName() {
MyBeanName myBeanName = beanFactory.getBean(MyBeanName.class);
System.out.println(beanFactory.isSingleton("myCustomBeanName"));
System.out.println(beanFactory.isSingleton("getMyBeanFactory"));
}
}
借助 setBeanFactory() 方法,可以將容器中的 BeanFactory 賦值給 MyBeanFactory 類的成員變量 beanFactory,這樣就可以在 getMyBeanName() 方法中使用 BeanFactory 了。
通過 getBean() 方法可以獲取 Bean 的實例;通過 isSingleton() 方法判斷 Bean 是否為一個單例。
在 Config 類中追加 MyBeanFactory 的 Bean:
@Configuration
public class Config {
@Bean(name = "myCustomBeanName")
public MyBeanName getMyBeanName() {
return new MyBeanName();
}
@Bean
public MyBeanFactory getMyBeanFactory() {
return new MyBeanFactory();
}
}
新建 BeanFactoryMain 類,代碼如下:
public class BeanFactoryMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
MyBeanFactory myBeanFactory = context.getBean(MyBeanFactory.class);
myBeanFactory.getMyBeanName();
context.close();
}
}
初始化 MyBeanFactory 后就可以調(diào)用 getMyBeanName() 方法了,程序輸出的結(jié)果如下所示:
myCustomBeanName
true
true
結(jié)果符合我們的預(yù)期:MyBeanName 的名字為“myCustomBeanName”,MyBeanName 和 MyBeanFactory 的 scope 都是 singleton。
3)其他幾個 Aware 接口就不再舉例說明了。通常情況下,不要實現(xiàn) Aware 接口,因為它會使 Bean 和 Spring 框架耦合。
02、異步編程
“二哥,據(jù)說 Spring 可以通過 @Async 來實現(xiàn)異步編程,你能給我詳細說說嗎?”
“沒問題啊。”
新建一個 AsyncService 類,內(nèi)容如下:
public class AsyncService {
@Async
public void execute() {
System.out.println(Thread.currentThread().getName());
}
}
@Async 注解用在 public 方法上,表明 execute() 方法是一個異步方法。
新建一個 AsyncConfig 類,內(nèi)容如下:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public AsyncService getAsyncService() {
return new AsyncService();
}
}
在配置類上使用 @EnableAsync 注解用以開啟異步編程,否則 @Async 注解不會起作用。
新建一個 AsyncMain 類,內(nèi)容如下:
public class AsyncMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AsyncConfig.class);
AsyncService service = context.getBean(AsyncService.class);
for (int i = 0; i < 10; i++) {
service.execute();
}
}
程序輸出結(jié)果如下:
SimpleAsyncTaskExecutor-1
SimpleAsyncTaskExecutor-9
SimpleAsyncTaskExecutor-7
SimpleAsyncTaskExecutor-8
SimpleAsyncTaskExecutor-10
SimpleAsyncTaskExecutor-3
SimpleAsyncTaskExecutor-2
SimpleAsyncTaskExecutor-4
SimpleAsyncTaskExecutor-6
SimpleAsyncTaskExecutor-5
OK,結(jié)果符合我們的預(yù)期,異步編程實現(xiàn)了。就像你看到的那樣,Spring 提供了一個默認(rèn)的 SimpleAsyncTaskExecutor 用來執(zhí)行線程,我們也可以在方法級別和應(yīng)用級別上對執(zhí)行器進行配置。
1)方法級別
新建 AsyncConfig 類,內(nèi)容如下:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public AsyncService getAsyncService() {
return new AsyncService();
}
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
return executor;
}
}
在配置類中創(chuàng)建了一個返回類型為 Executor 的 Bean,其名稱定義為“threadPoolTaskExecutor”,并且重新設(shè)置了 ThreadPoolTaskExecutor 的核心線程池大小,默認(rèn)為 1,現(xiàn)在修改為 5。
新進 AsyncService 類,內(nèi)容如下:
public class AsyncService {
@Async("threadPoolTaskExecutor")
public void execute() {
System.out.println(Thread.currentThread().getName());
}
}
@Async 注解上需要指定我們之前配置的線程池執(zhí)行器“threadPoolTaskExecutor”。
新建 AsyncMain 類,內(nèi)容如下:
public class AsyncMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AsyncConfig.class);
AsyncService service = context.getBean(AsyncService.class);
for (int i = 0; i < 10; i++) {
service.execute();
}
}
}
程序運行結(jié)果如下:
threadPoolTaskExecutor-1
threadPoolTaskExecutor-2
threadPoolTaskExecutor-4
threadPoolTaskExecutor-3
threadPoolTaskExecutor-5
threadPoolTaskExecutor-3
threadPoolTaskExecutor-2
threadPoolTaskExecutor-4
threadPoolTaskExecutor-1
threadPoolTaskExecutor-5
從結(jié)果中可以看得出,線程池執(zhí)行器變成了“threadPoolTaskExecutor”,并且大小為 5。
2)應(yīng)用級別
新建 AsyncConfig 類,內(nèi)容如下:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Bean
public AsyncService getAsyncService() {
return new AsyncService();
}
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.initialize();
return executor;
}
}
需要實現(xiàn) AsyncConfigurer 接口,并重寫 getAsyncExecutor() 方法,這次設(shè)置線程池的大小為 3。注意執(zhí)行器要執(zhí)行一次 initialize() 方法。
新進 AsyncService 類,內(nèi)容如下:
public class AsyncService {
@Async
public void execute() {
System.out.println(Thread.currentThread().getName());
}
}
新建 AsyncMain 類,內(nèi)容如下:
public class AsyncMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AsyncConfig.class);
AsyncService service = context.getBean(AsyncService.class);
for (int i = 0; i < 10; i++) {
service.execute();
}
}
}
程序運行結(jié)果如下:
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-1
ThreadPoolTaskExecutor-3
從結(jié)果中可以看得出,線程池執(zhí)行器變成了“ThreadPoolTaskExecutor”,并且大小為 3。
03、計劃任務(wù)
“二哥,據(jù)說 Spring 可以通過 @Scheduled 來實現(xiàn)計劃任務(wù),你能給我詳細說說怎么實現(xiàn)嗎?”
“沒問題啊?!?/p>
新建一個 ScheduledService 類,內(nèi)容如下:
@Service
public class ScheduledService {
@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
System.out.println(
"固定時間段后執(zhí)行任務(wù) - " + System.currentTimeMillis() / 1000);
}
@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() {
System.out.println(
"固定的頻率執(zhí)行任務(wù) - " + System.currentTimeMillis() / 1000);
}
@Scheduled(cron = "0/2 * * * * ?")
public void scheduleTaskUsingCronExpression() {
long now = System.currentTimeMillis() / 1000;
System.out.println(
"Cron 表達式執(zhí)行任務(wù) - " + now);
}
}
@Service 注解用于指定 ScheduledService 類為一個業(yè)務(wù)層的 Bean。@Scheduled 注解用于指定當(dāng)前方法(返回類型為 void,無參)為一個任務(wù)執(zhí)行方法,常見的用法有以下 3 種:
1)fixedDelay 用于確保任務(wù)執(zhí)行的完成時間與任務(wù)下一次執(zhí)行的開始時間之間存在 n 毫秒的延遲,下一次任務(wù)執(zhí)行前,上一次任務(wù)必須執(zhí)行完。
2)fixedRate 用于確保每 n 毫秒執(zhí)行一次計劃任務(wù),即使最后一次調(diào)用可能仍在運行。
3)Cron 表達式比 fixedDelay 和 fixedRate 都要靈活,由 7 個部分組成,各部分之間用空格隔開,其完整的格式如下所示:
Seconds Minutes Hours Day-of-Month Month Day-of-Week Year
單詞都很簡單,就不用我翻譯了。其中 Year 是可選項。常見的范例如下所示:
*/5 * * * * ? 每隔 5 秒執(zhí)行一次
0 */1 * * * ? 每隔 1 分鐘執(zhí)行一次
0 0 23 * * ? 每天 23 點執(zhí)行一次
0 0 1 * * ? 每天凌晨 1 點執(zhí)行一次:
0 0 1 1 * ? 每月 1 號凌晨 1 點執(zhí)行一次
0 0 23 L * ? 每月最后一天 23 點執(zhí)行一次
0 0 1 ? * L 每周星期天凌晨 1 點執(zhí)行一次
0 26,29,33 * * * ? 在 26 分、29 分、33 分執(zhí)行一次
0 0 0,13,18,21 * * ? 每天的 0 點、13 點、18 點、21 點各執(zhí)行一次
新建 ScheduledConfig 類,內(nèi)容如下:
@Configuration
@EnableScheduling
@ComponentScan("high.scheduled")
public class ScheduledConfig {
}
@EnableScheduling 注解用于開啟計劃任務(wù)。@ComponentScan 注解用于掃描當(dāng)前包下的類,如果它使用了注解(比如 @Service),就將其注冊成為一個 Bean。
新建 ScheduledMain 類,內(nèi)容如下:
public class ScheduledMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScheduledConfig.class);
}
}
程序運行結(jié)果如下:
固定的頻率執(zhí)行任務(wù) - 1584666273
固定時間段后執(zhí)行任務(wù) - 1584666273
Cron 表達式執(zhí)行任務(wù) - 1584666274
固定的頻率執(zhí)行任務(wù) - 1584666274
固定時間段后執(zhí)行任務(wù) - 1584666274
固定的頻率執(zhí)行任務(wù) - 1584666275
固定時間段后執(zhí)行任務(wù) - 1584666275
Cron 表達式執(zhí)行任務(wù) - 1584666276
從結(jié)果中可以看得出,如果任務(wù)之間沒有沖突的話,fixedDelay 任務(wù)之間的間隔和 fixedRate 任務(wù)之間的間隔是相同的,都是 1 秒;Cron 表達式任務(wù)與上一次任務(wù)之間的間隔為 2 秒。
“二哥,這篇文章中的示例代碼你上傳到 GitHub 了嗎?”
“你到挺貼心啊,三妹。傳送門~”
“二哥,你教得真不錯,我完全學(xué)會了,一點也不枯燥?!?/p>
“那必須得啊,期待下一篇吧?”
“那是當(dāng)然啊,期待,非常期待,望眼欲穿的感覺?!?/p>

請允許我熱情地吐槽一下,這篇文章我不希望再被噴了,看在我這么辛苦搞原創(chuàng)(創(chuàng)意+干貨+有趣)的份上,多鼓勵鼓勵好不好?別瞅了,點贊唄,你最美你最帥。
如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀,回復(fù)【666】【1024】更有我為你精心準(zhǔn)備的 500G 高清教學(xué)視頻(已分門別類),以及大廠技術(shù)牛人整理的面經(jīng)一份。