趣学Spring:一文搞懂Aware、异步编程、计划任务
你好呀,我是沉默王二,一个和黄家驹一样身高,刘德华一样颜值的程序员(不信围观朋友圈呗)。从 2 位偶像的年纪上,你就可以断定我的码龄至少在 10 年以上,但实话实说,我一直坚信自己只有 18 岁,因为好学使我年轻。本篇文章就打算通过我和三妹对话的形式来聊一聊“Spring 的 Aware、异步编程、计划任务”。
教妹学 Spring,没见过这么放肆的标题吧?“语不惊人死不休”,没错,本篇文章的标题就是这么酷炫,不然你怎么会点进来?

我有一个漂亮如花的妹妹(见上图,怎么又变了?还不能一天做个梦),她叫什么呢?我想聪明的读者能猜得出:沉默王三,没错,年方三六。父母正考虑让她向我学习,做一名正儿八经的 Java 程序员。我一开始是反对的,因为程序员这行业容易掉头发,女生可不适合掉头发。但家命难为啊,与其反对,不如做点更积极的事情,比如说写点有趣的文章教教她。

“二哥,听说今天要学习 Spring 的 Aware、异步编程、计划任务,真的是翘首以盼啊。”
“哎呀,三妹,瞧你那迫不及待的大眼神,就好像昨晚上月亮一样圆,一样大。”
01、Spring Aware
“二哥,据说 Aware 的目的是让 Bean 获取 Spring 容器的服务,你能给我具体说说吗?”
“没问题啊。”
Bean 一般不需要了解容器的状态和直接使用容器,但是在某些情况下,需要在 Bean 中直接对容器进行操作,这时候,就可以通过特定的 Aware 接口来完成。常见的 Spring Aware 接口有下面这些:
| Aware 子接口 | 描述 |
|---|---|
| BeanNameAware | 获取容器中 Bean 的名称 |
| BeanFactoryAware | Bean 被容器创建以后,会有一个相应的 BeanFactory,可以直接通过它来访问容器 |
| ApplicationContextAware | Bean 被初始化后,会被注入到 ApplicationContext,可以直接通过它来访问容器 |
| MessageSourceAware | 获取 Message Source 的相关文本信息 |
| ResourceLoaderAware | 获取资源加载器,以获取外部资源文件 |
1)BeanNameAware
新建一个 MyBeanName 类,内容如下:
public class MyBeanName implements BeanNameAware {
@Override
public void setBeanName(String beanName) {
System.out.println(beanName);
}
}
MyBeanName 实现了 BeanNameAware 接口,并重写了 setBeanName() 方法。beanName 参数表示 Bean 在 Spring 容器中注册的 name。
新建一个 Config 类,内容如下:
@Configuration
public class Config {
@Bean(name = "myCustomBeanName")
public MyBeanName getMyBeanName() {
return new MyBeanName();
}
}
@Bean 注解用在 getMyBeanName() 方法上,表明当前方法返回一个 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();
}
}
程序输出的结果如下所示:
myCustomBeanName
如果把 @Bean() 注解中的 (name = "myCustomBeanName)" 去掉的话,程序输出的内容将会是 BeanNameConfig 类的 getMyBeanName() 的方法名“getMyBeanName”。
2)BeanFactoryAware
新建一个 MyBeanFactory 类,内容如下:
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 后就可以调用 getMyBeanName() 方法了,程序输出的结果如下所示:
myCustomBeanName
true
true
结果符合我们的预期:MyBeanName 的名字为“myCustomBeanName”,MyBeanName 和 MyBeanFactory 的 scope 都是 singleton。
3)其他几个 Aware 接口就不再举例说明了。通常情况下,不要实现 Aware 接口,因为它会使 Bean 和 Spring 框架耦合。
02、异步编程
“二哥,据说 Spring 可以通过 @Async 来实现异步编程,你能给我详细说说吗?”
“没问题啊。”
新建一个 AsyncService 类,内容如下:
public class AsyncService {
@Async
public void execute() {
System.out.println(Thread.currentThread().getName());
}
}
@Async 注解用在 public 方法上,表明 execute() 方法是一个异步方法。
新建一个 AsyncConfig 类,内容如下:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public AsyncService getAsyncService() {
return new AsyncService();
}
}
在配置类上使用 @EnableAsync 注解用以开启异步编程,否则 @Async 注解不会起作用。
新建一个 AsyncMain 类,内容如下:
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();
}
}
程序输出结果如下:
SimpleAsyncTaskExecutor-1
SimpleAsyncTaskExecutor-9
SimpleAsyncTaskExecutor-7
SimpleAsyncTaskExecutor-8
SimpleAsyncTaskExecutor-10
SimpleAsyncTaskExecutor-3
SimpleAsyncTaskExecutor-2
SimpleAsyncTaskExecutor-4
SimpleAsyncTaskExecutor-6
SimpleAsyncTaskExecutor-5
OK,结果符合我们的预期,异步编程实现了。就像你看到的那样,Spring 提供了一个默认的 SimpleAsyncTaskExecutor 用来执行线程,我们也可以在方法级别和应用级别上对执行器进行配置。
1)方法级别
新建 AsyncConfig 类,内容如下:
@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;
}
}
在配置类中创建了一个返回类型为 Executor 的 Bean,其名称定义为“threadPoolTaskExecutor”,并且重新设置了 ThreadPoolTaskExecutor 的核心线程池大小,默认为 1,现在修改为 5。
新进 AsyncService 类,内容如下:
public class AsyncService {
@Async("threadPoolTaskExecutor")
public void execute() {
System.out.println(Thread.currentThread().getName());
}
}
@Async 注解上需要指定我们之前配置的线程池执行器“threadPoolTaskExecutor”。
新建 AsyncMain 类,内容如下:
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();
}
}
}
程序运行结果如下:
threadPoolTaskExecutor-1
threadPoolTaskExecutor-2
threadPoolTaskExecutor-4
threadPoolTaskExecutor-3
threadPoolTaskExecutor-5
threadPoolTaskExecutor-3
threadPoolTaskExecutor-2
threadPoolTaskExecutor-4
threadPoolTaskExecutor-1
threadPoolTaskExecutor-5
从结果中可以看得出,线程池执行器变成了“threadPoolTaskExecutor”,并且大小为 5。
2)应用级别
新建 AsyncConfig 类,内容如下:
@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;
}
}
需要实现 AsyncConfigurer 接口,并重写 getAsyncExecutor() 方法,这次设置线程池的大小为 3。注意执行器要执行一次 initialize() 方法。
新进 AsyncService 类,内容如下:
public class AsyncService {
@Async
public void execute() {
System.out.println(Thread.currentThread().getName());
}
}
新建 AsyncMain 类,内容如下:
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();
}
}
}
程序运行结果如下:
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-1
ThreadPoolTaskExecutor-3
从结果中可以看得出,线程池执行器变成了“ThreadPoolTaskExecutor”,并且大小为 3。
03、计划任务
“二哥,据说 Spring 可以通过 @Scheduled 来实现计划任务,你能给我详细说说怎么实现吗?”
“没问题啊。”
新建一个 ScheduledService 类,内容如下:
@Service
public class ScheduledService {
@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
System.out.println(
"固定时间段后执行任务 - " + System.currentTimeMillis() / 1000);
}
@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() {
System.out.println(
"固定的频率执行任务 - " + System.currentTimeMillis() / 1000);
}
@Scheduled(cron = "0/2 * * * * ?")
public void scheduleTaskUsingCronExpression() {
long now = System.currentTimeMillis() / 1000;
System.out.println(
"Cron 表达式执行任务 - " + now);
}
}
@Service 注解用于指定 ScheduledService 类为一个业务层的 Bean。@Scheduled 注解用于指定当前方法(返回类型为 void,无参)为一个任务执行方法,常见的用法有以下 3 种:
1)fixedDelay 用于确保任务执行的完成时间与任务下一次执行的开始时间之间存在 n 毫秒的延迟,下一次任务执行前,上一次任务必须执行完。
2)fixedRate 用于确保每 n 毫秒执行一次计划任务,即使最后一次调用可能仍在运行。
3)Cron 表达式比 fixedDelay 和 fixedRate 都要灵活,由 7 个部分组成,各部分之间用空格隔开,其完整的格式如下所示:
Seconds Minutes Hours Day-of-Month Month Day-of-Week Year
单词都很简单,就不用我翻译了。其中 Year 是可选项。常见的范例如下所示:
*/5 * * * * ? 每隔 5 秒执行一次
0 */1 * * * ? 每隔 1 分钟执行一次
0 0 23 * * ? 每天 23 点执行一次
0 0 1 * * ? 每天凌晨 1 点执行一次:
0 0 1 1 * ? 每月 1 号凌晨 1 点执行一次
0 0 23 L * ? 每月最后一天 23 点执行一次
0 0 1 ? * L 每周星期天凌晨 1 点执行一次
0 26,29,33 * * * ? 在 26 分、29 分、33 分执行一次
0 0 0,13,18,21 * * ? 每天的 0 点、13 点、18 点、21 点各执行一次
新建 ScheduledConfig 类,内容如下:
@Configuration
@EnableScheduling
@ComponentScan("high.scheduled")
public class ScheduledConfig {
}
@EnableScheduling 注解用于开启计划任务。@ComponentScan 注解用于扫描当前包下的类,如果它使用了注解(比如 @Service),就将其注册成为一个 Bean。
新建 ScheduledMain 类,内容如下:
public class ScheduledMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScheduledConfig.class);
}
}
程序运行结果如下:
固定的频率执行任务 - 1584666273
固定时间段后执行任务 - 1584666273
Cron 表达式执行任务 - 1584666274
固定的频率执行任务 - 1584666274
固定时间段后执行任务 - 1584666274
固定的频率执行任务 - 1584666275
固定时间段后执行任务 - 1584666275
Cron 表达式执行任务 - 1584666276
从结果中可以看得出,如果任务之间没有冲突的话,fixedDelay 任务之间的间隔和 fixedRate 任务之间的间隔是相同的,都是 1 秒;Cron 表达式任务与上一次任务之间的间隔为 2 秒。
“二哥,这篇文章中的示例代码你上传到 GitHub 了吗?”
“你到挺贴心啊,三妹。传送门~”
“二哥,你教得真不错,我完全学会了,一点也不枯燥。”
“那必须得啊,期待下一篇吧?”
“那是当然啊,期待,非常期待,望眼欲穿的感觉。”

请允许我热情地吐槽一下,这篇文章我不希望再被喷了,看在我这么辛苦搞原创(创意+干货+有趣)的份上,多鼓励鼓励好不好?别瞅了,点赞呗,你最美你最帅。
如果觉得文章对你有点帮助,请微信搜索「 沉默王二 」第一时间阅读,回复【666】【1024】更有我为你精心准备的 500G 高清教学视频(已分门别类),以及大厂技术牛人整理的面经一份。
趣学Spring:一文搞懂Aware、异步编程、计划任务的更多相关文章
- 彻底搞懂C#异步编程 async和await的原理
1.前提 熟练掌握Task并行编程. 2.用Task并行解释async和await异步 因为控制台有多线程操作的优化,因此这里选择winform来做示例. 测试代码如下所示: 有三个textbox,一 ...
- Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!
本文原作者: Wizey,作者博客:http://wenshixin.gitee.io,即时通讯网收录时有改动,感谢原作者的无私分享. 1.引言 典型的Web端即时通讯技术应用场景,主要有以下两种形式 ...
- 三文搞懂学会Docker容器技术(下)
接着上面一篇:三文搞懂学会Docker容器技术(上) 三文搞懂学会Docker容器技术(中) 7,Docker容器目录挂载 7.1 简介 容器目录挂载: 我们可以在创建容器的时候,将宿主机的目录与容器 ...
- 一文搞懂RAM、ROM、SDRAM、DRAM、DDR、flash等存储介质
一文搞懂RAM.ROM.SDRAM.DRAM.DDR.flash等存储介质 存储介质基本分类:ROM和RAM RAM:随机访问存储器(Random Access Memory),易失性.是与CPU直接 ...
- 基础篇|一文搞懂RNN(循环神经网络)
基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...
- 一文搞懂 Prometheus 的直方图
原文链接:一文搞懂 Prometheus 的直方图 Prometheus 中提供了四种指标类型(参考:Prometheus 的指标类型),其中直方图(Histogram)和摘要(Summary)是最复 ...
- 一文搞懂vim复制粘贴
转载自本人独立博客https://liushiming.cn/2020/01/18/copy-and-paste-in-vim/ 概述 复制粘贴是文本编辑最常用的功能,但是在vim中复制粘贴还是有点麻 ...
- 三文搞懂学会Docker容器技术(中)
接着上面一篇:三文搞懂学会Docker容器技术(上) 6,Docker容器 6.1 创建并启动容器 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] --na ...
- 一文搞懂所有Java集合面试题
Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...
随机推荐
- http2.0与WebSocket的关系是怎么样的
按照OSI网络分层模型,IP是网络层协议,TCP是传输层协议,而HTTP是应用层的协议.在这三者之间,SPDY和WebSocket都是与HTTP相关的协议,而TCP是HTTP底层的协议.WebSock ...
- Dubbo、MQ等
1,Dubbo.MQ 1)Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案 2)Dubbo采用全Spring配置方式,透明化接入应用,对应用没有 ...
- MFC中文件对话框类CFileDialog详解及文件过滤器说明
当前位置 : 首页 » 文章分类 : 开发 » MFC中文件对话框类CFileDialog详解及文件过滤器说明 上一篇 利用OpenCV从摄像头获得图像的坐标原点是在左下角 下一篇 Word中为 ...
- 从赴美IPO绝迹 看那些烧成泡沫的互联网企业
曾经,赴美上市是很多中国企业的终极梦想.然而在当下,随着中概股在美国股市股价的不断走低.中国赴美上市企业私有化速度的加快,大众才发现,原来美国股市并不是那么好混的.但不管怎样,赴美上市始终是一种荣耀. ...
- Ta说:2016微软亚洲研究院第二届博士生论坛
"聚合多元人才创造无尽可能,让每一位优秀博士生得到发声成长机会"可以说是这次微软亚洲研究院博士生论坛最好的归纳了.自去年首次举办以来,这项旨在助力青年研究者成长的项目迅速得到了 ...
- 下一代网际协议IPv6
下一代网际协议IPv6 一.解决 IP 地址耗尽的措施 从计算机本身发展以及从因特网规模和网络传输速率来看,现在 IPv4 已很不适用. 最主要的问题就是 32 位的 IP 地址不够用. 在 2019 ...
- iOS 开发富文本之TTTAttributedLabel 在某个特定位置的文字添加跳转,下划线,修改字体大小,颜色
@property(nonatomic , strong) TTTAttributedLabel * ttLabel; @property(nonatomic , strong) NSRange li ...
- js实现box(2)(3)这种调用方式的方法
box(2)(3)函数的调用方法有两种: 第一种: var box = function(num1){ return function(num2){ return num1+num2; }; }; a ...
- 【Java】机考常用知识
基本操作 数组 声明数组 方法一: int a[] = null; //声明一维数组 //int[] a = null; 也行,个人习惯 a = new int[10];//分配内存给一维数组 方法二 ...
- 微信WXSS样式文件
目录 WXSS官方文档 1. WXSS 1.1. 尺寸单位 1.2. 样式导入 1.3. 内联样式 1.4. 选择器 1.5. 全局样式与局部样式 WXSS官方文档 https://developer ...