1、整章思维导图
https://gitmind.cn/app/doc/6801008999
2、异步任务
-
概述
在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在Spring 3.x之后,就已经内置了@Async来完美解决这个问题
-
核心注解
注解 | 说明 |
---|---|
@EnableAsync | 开启基于注解的异步任务支持 |
@Async | 标注为异步方法 |
- 无返回值异步任务调用
(1)创建无返回值的异步方法
@Async
public void sendSMS() throws Exception{
System.out.println("调用短信验证码业务方法。。。");
Long startTime = System.currentTimeMillis();
Thread.sleep(5000);
Long endTime = System.currentTimeMillis();
System.out.println("短信业务执行完成耗时:"+(endTime-startTime));
}
(2)在项目启动类上添加@EnableAsync开启基于注解的异步任务支持
(3)编写调用方法(在测试类中,线程不能正常运行,缺少资源文件,只能在controller里面测试了)
// 注入MyAsyncService类
@Autowired
MyAsyncService myAsyncService;
@GetMapping("/myAsync")
public String TestAsync() throws Exception {
Long startTime = System.currentTimeMillis();
myAsyncService.sendSMS();
Long endTime = System.currentTimeMillis();
System.out.println("主流程耗时:"+(endTime-startTime));
System.out.println("运行结束");
return "success";
}
(4)运行结果
当异步方法没有返回值时,这样主流程在执行异步方法时不会阻塞,而是继续向下执行主流程程序,直接向页面响应结果,而调用的异步方法会作为一个子线程单独执行,直到异步方法执行完成
- 有返回值异步任务调用
(1)创建有返回值的异步方法
@Async
public Future<Integer> processA() throws Exception{
System.out.println("开始分析并统计业务A数据。。。");
Long startTime = System.currentTimeMillis();
Thread.sleep(4000);
int count = 123456;
Long endTime = System.currentTimeMillis();
System.out.println("业务A执行耗时:"+(endTime-startTime));
return new AsyncResult<Integer>(count);
}
@Async
public Future<Integer> processB() throws Exception{
System.out.println("开始分析并统计业务B数据。。。");
Long startTime = System.currentTimeMillis();
Thread.sleep(5000);
int count = 654321;
Long endTime = System.currentTimeMillis();
System.out.println("业务B执行耗时:"+(endTime-startTime));
return new AsyncResult<Integer>(count);
}
(2)在项目启动类上添加@EnableAsync开启基于注解的异步任务支持
(3)编写调用方法(在测试类中,线程不能正常运行,缺少资源文件,只能在controller里面测试了)
@GetMapping("/Async")
public String TestAsync2() throws Exception{
Long startTime = System.currentTimeMillis();
Future<Integer> futureA = myAsyncService.processA();
Future<Integer> futureB = myAsyncService.processB();
int total = futureA.get() + futureB.get();
Long endTime = System.currentTimeMillis();
System.out.println("异步结果相加:"+total);
System.out.println("主流程耗时:"+(endTime-startTime));
System.out.println("运行结束");
return "success";
}
(4)运行结果
当异步方法有返回值时,主流程在执行异步方法是会有短暂阻塞,需要等待并获取异步方法的返回结果,这样主流程会在最后一个异步方法返回结果后跳出阻塞状态
3、定时任务
-
概述
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息。Spring为我们提供了异步执行任务调度的方式,提供TaskExecutor 、TaskScheduler 接口,而SpringBoot为我们提供了更方便的注解
-
核心注解
注解 | 说明 |
---|---|
@EnableScheduling | 开启基于注解方式的定时任务支持 |
@Scheduled | 配置定时任务的执行规则 |
- @Scheduled属性
属性 | 说明 |
---|---|
cron | 类似与cron的表达式,可以定制定时任务触发的秒、分钟、小时、月中的日、月、周中的日 |
zone | 指定cron表达式将被解析的时区,默认情况下,该属性是空字符串(即使用服务器的本地时区 |
fixedDelay | 表示上一次任务结束后指定时间后继续执行下一次任务(属性值为long类型) |
fixedDelayString | 表示上一次任务执行结束后指定时间后继续执行下一次任务(属性值为long类型的字符串的形式) |
fixedRate | 表示每隔指定时间执行一次任务(属性值为long类型) |
fixedRateString | 表示每隔指定时间执行一次任务(属性值为long类型的字符串形式) |
initialDelay | 表示在fixedRate或fixedDelay任务第一次执行之前要延迟的毫秒数(属性值为long类型) |
initialDelayString | 表示在fixedRate或fixedDelay任务第一次执行之前要延迟的毫秒数(属性值为long类型的字符串形式) |
- @Scheduled属性说明
(1)cron属性
cron属性是@Scheduled定时任务注解中最常用也是最复杂的一个属性,其属性值由类似于cron表达式的6位数组成(但某些cron表达式的字符,在这个注解中不能使用),可以详细地指定定时任务执行的秒、分、小时、日、月、星期
// 表示周一到周五每一分钟执行一次定时任务,第一位表示秒,第二位表示分,第三位表示小时,第四位表示月份中的日,第五位表示月份中的月,第六位表示星期,*代表任意时刻,MON-FRI代码周一到周五,每个字段值都用空格隔开
@Scheduled(cron="0 * * * * MON-FRI")
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 1-12 、月份对应英文前3个字母(大小写均可) | , - * / |
星期 | 0-7或SUN-SAT 0,7是SUN | , - * ? / L C # |
特殊字符 | 代表含义 | 示例 |
---|---|---|
, | 枚举 | @Scheduled(cron=“1,3,5 * * * * *”)表示任意时间的1、3、5秒都会执行 |
- | 区间 | @Scheduled(cron=“0 0-5 14 * * *”)表示每天下午2:00~2:05期间,每1分钟触发一次 |
* | 任意 | @Scheduled(cron=“0 0 12 * * *”)表示每天中午12:00触发一次 |
/ | 步长 | @Scheduled(cron=“0/5 * * * * *”)表示从任意时间的整秒开始,每隔5秒都会执行 |
? | 日/星期冲突匹配 | @Scheduled(cron=“0 * * 26 * ?”)表示每月的26日每一分钟都执行 |
L | 最后 | @Scheduled(cron=“0 0 * L * ?”)表示每月最后一日每一小时都执行 |
(2)zone属性
zone属性主要与cron属性配合使用,指定解析cron属性值的时区。通常情况下,不需要指定zone属性。cron属性值会自动以服务器所在区域作为本地时区进行表达式解析。例如,中国地区服务器的时区通常默认为Asia/Shanghai
(3)fixedDelay/fixedDelayString属性
fixedDelay和fixedDelayString属性的作用相似,用于在上一次任务执行完毕后,一旦到达指定时间就继续执行下一次任务。两个的区别在属性值的类型不同,fixedDelay属性值为long类型,而fixedDelayString属性值是数值字符串
// 在任务结束后开始计时
// 表示在程序启动后会立即执行一次定时任务,然后在任务结束后,相隔5秒重复执行定时任务
@Scheduled(fixedDelay=5000)
@Scheduled(fixedDelayString="5000")
(4)fixedRate/fixedRateString属性
fixedRate和fixedRateString属性的作用类似,指定每相隔一段时间重复执行一次定时任务,它们的主要区别是属性值的类型不同,其中fixedRate属性值为long类型,fixedRateString属性值为数值字符串
// 在任务开始是就开始计时,若相隔时间小于任务执行时间,则下一任务在前任务结束后立即执行
// 表示在程序启动后会立即执行一次定时任务,然后在任务结束后,相隔5秒重复执行定时任务
@Scheduled(fixedRate=5000)
@Scheduled(fixedRateRate="5000")
(5)initialDelay/initialDelayString属性
initialDelay和initialDelayString属性的作用类似,主要是与fixedDelay/fixedDelayString
或者fixedRate/fixedRateString属性配合使用,指定定时任务第一次执行的延迟时间,然后在按照各自相隔时间重复执行任务
// 在程序启动后,会延迟1秒后再执行第一次定时任务,然后以后每个任务相隔5秒重复执行
@Scheduled(initialDelay=1000,fixedDelay=5000)
@Scheduled(initialDelay=1000,fixedRate=5000)
- 定时任务示例
(1)编写定时任务业务处理方法
@Service
public class ScheduledTaskService {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Integer count1 = 1;
private Integer count2 = 1;
private Integer count3 = 1;
@Scheduled(fixedRate = 6000)
public void scheduledTaskImmediately(){
System.out.println(String.format("fixedRate第%s此执行,当前时间为:%s",count1++,dateFormat.format(new Date())));
}
@Scheduled(fixedDelay = 6000)
public void scheduledTaskAfterSleep() throws Exception{
System.out.println(String.format("fixedDelay第%s此执行,当前时间为:%s",count2++,dateFormat.format(new Date())));
Thread.sleep(10000);
}
@Scheduled(cron = "0 * * * * *")
public void scheduledTaskCron(){
System.out.println(String.format("cron第%s次执行,当前时间为:%s",count3++,dateFormat.format(new Date())));
}
}
(2)在项目启动类上添加@EnableScheduling开启基于注解的定时任务支持,不需要调用方法,任务会自动执行
4、邮件服务
- 添加邮件依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
- 添加邮件服务配置
spring:
mail:
host: smtp.qq.com
port: 587
username: 1239674647@qq.com
password: wgplfktfgdupigfc
default-encoding: utf-8
# 邮件服务超时时间配置
properties:
mail:
smtp:
connectiontimeout: 5000
timeout: 3000
writetimeout: 5000
- 发送纯文本邮件
@Autowired
private JavaMailSenderImpl mailSender;
@Value("${spring.mail.username}")
private String from;
public void sendSimpleEmail(String to,String subject,String text){
SimpleMailMessage message = new SimpleMailMessage();
// 发送者
message.setFrom(from);
// 接收者
message.setTo(to);
// 主题
message.setSubject(subject);
// 正文
message.setText(text);
try {
mailSender.send(message);
System.out.println("纯文本邮件发送成功");
}catch (MailException e){
System.out.println("纯文本邮件发送失败");
e.printStackTrace();
}
}
- 发送带附件和图片的邮件
/**
* 发送复杂邮件
* @param to 收件人地址
* @param subject 邮件标题
* @param text 邮件内容
* @param filePath 附件地址
* @param recId 静态资源唯一标识
* @param recPath 静态资源地址
*/
public void sendComplexEmail(String to,String subject,String text,String filePath,String recId,String recPath){
MimeMessage message = mailSender.createMimeMessage();
try {
// 将邮件消息封装进MimeMessageHelper类,并设置multipart多部件使用true
MimeMessageHelper helper = new MimeMessageHelper(message,true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
// 解析html为true
helper.setText(text,true);
// 设置邮件静态资源
FileSystemResource resource = new FileSystemResource(new File(recPath));
helper.addInline(recId,resource);
// 设置邮件附件
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring((filePath.lastIndexOf(File.separator)));
helper.addAttachment(fileName,file);
// 发送邮件
mailSender.send(message);
System.out.println("复杂邮件发送成功");
} catch (MessagingException e) {
System.out.println("复杂邮件发送失败");
e.printStackTrace();
}
}
- 发送模板邮件
(1)添加Thymeleaf模板引擎依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
(2)编写html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>发送模板邮件测试</title>
</head>
<body>
<div><span th:text="${msg}" style="color: aqua"></span></div>
</body>
</html>
(3)编写发送模板邮件服务
public void sendTemplateEmail(String to,String subject,String content){
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message,true);
helper.setFrom(from);
helper.setTo(to);
helper.setText(content,true);
mailSender.send(message);
System.out.println("模板邮件发送成功");
}catch (MessagingException e){
System.out.println("模板邮件发送失败");
e.printStackTrace();
}
}
(4)发送邮件
//thymeleaf解析引擎
@Autowired
TemplateEngine templateEngine;
@Test
public void sendTemplateEmailTest(){
String to = "1239674647@qq.com";
String subject = "【模板邮件】标题";
// 对变量赋值
Context context = new Context();
context.setVariable("msg","模板邮件测试");
// 使用TemplateEngine设置处理的模板页面
String emailContent = templateEngine.process("email",context);
sendEmailService.sendTemplateEmail(to,subject,emailContent);
}