一、异步处理(@Async)
1、Pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.namejr</groupId> <artifactId>FirstSBDemo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.14</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.yml</include> <include>**/*.xml</include> <include>**/*.json</include> <include>**/*.txt</include> <include>**/*.mp3</include> </includes> <filtering>false</filtering> </resource> <resource> <!-- 注册webapp目录为资源目录 --> <directory>src/main/webapp</directory> <targetPath>META-INF/resources</targetPath> <includes> <include>**/**</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includeSystemScope>true</includeSystemScope> <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments> </configuration> </plugin> <plugin> <!-- 配置jar包打包工具 --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <configuration> <webResources> <resource> <directory>${project.basedir}/libs</directory> <targetPath>WEB-INF/lib</targetPath> <includes> <include>**/*.jar</include> </includes> </resource> </webResources> </configuration> </plugin> </plugins> </build> </project>
2、配置启动类StartApplication.java
package com.namejr; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @SpringBootApplication @EnableWebMvc @ServletComponentScan({"com.namejr.base","com.namejr.controller","com.namejr.service"}) public class StartApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(StartApplication.class, args); } protected SpringApplicationBuilder configuer(SpringApplicationBuilder builder) { return super.configure(builder); } }
3、配置线程池处理
package com.namejr.base; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 配置异步线程池 * **/ @Configuration public class AsyncTaskPoolConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { return new ThreadPoolExecutor(2,4,1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); } }
4、创建异步任务
package com.namejr.serviceImpl; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; @Component @EnableAsync public class AsyncTaskServiceImpl { @Async public void executeAsyncTask(String tname){ for (int i=0;i<10;i++){
// 通过内部的try{}catch(){}进行记录异常,而不使用"AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler"重写统一配置
try{Thread.sleep(500);}catch (Exception ignored){}
System.out.println("("+tname+")正在执行异步任务:"+i);
}
}
}
5、接口访问
package com.namejr.controller; import com.namejr.serviceImpl.AsyncTaskServiceImpl; import com.namejr.serviceImpl.PublicServiceImpl; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/api/public") @Validated public class PublicController { @Autowired private PublicServiceImpl pServiceImpl; @Autowired private AsyncTaskServiceImpl atServiceImpl; @RequestMapping(value = "/getServerTime", method = RequestMethod.GET,produces = "application/json;charset=UTF-8") public String getServerTime() { return DateTime.now().toString("yyyy-MM-dd HH:mm:ss"); } @RequestMapping(value = "/runAsyncTask", method = RequestMethod.GET,produces = "application/json;charset=UTF-8") public void runAsyncTask() { for (int i=0;i<5;i++){ atServiceImpl.executeAsyncTask("Thread-"+i); } } }
输出:127.0.0.1:8080/api/public/runAsyncTask
(Thread-1)正在执行异步任务:0 (Thread-0)正在执行异步任务:0 (Thread-0)正在执行异步任务:1 (Thread-1)正在执行异步任务:1 (Thread-1)正在执行异步任务:2 (Thread-0)正在执行异步任务:2 (Thread-0)正在执行异步任务:3 (Thread-1)正在执行异步任务:3 (Thread-0)正在执行异步任务:4 (Thread-1)正在执行异步任务:4 (Thread-0)正在执行异步任务:5 (Thread-1)正在执行异步任务:5 (Thread-1)正在执行异步任务:6 (Thread-0)正在执行异步任务:6 (Thread-0)正在执行异步任务:7 (Thread-1)正在执行异步任务:7 (Thread-0)正在执行异步任务:8 (Thread-1)正在执行异步任务:8 (Thread-0)正在执行异步任务:9 (Thread-1)正在执行异步任务:9 (Thread-2)正在执行异步任务:0 (Thread-3)正在执行异步任务:0 (Thread-3)正在执行异步任务:1 (Thread-2)正在执行异步任务:1 (Thread-3)正在执行异步任务:2 (Thread-2)正在执行异步任务:2 (Thread-2)正在执行异步任务:3 (Thread-3)正在执行异步任务:3 (Thread-2)正在执行异步任务:4 (Thread-3)正在执行异步任务:4 (Thread-2)正在执行异步任务:5 (Thread-3)正在执行异步任务:5 (Thread-3)正在执行异步任务:6 (Thread-2)正在执行异步任务:6 (Thread-2)正在执行异步任务:7 (Thread-3)正在执行异步任务:7 (Thread-3)正在执行异步任务:8 (Thread-2)正在执行异步任务:8 (Thread-3)正在执行异步任务:9 (Thread-2)正在执行异步任务:9 (Thread-4)正在执行异步任务:0 (Thread-4)正在执行异步任务:1 (Thread-4)正在执行异步任务:2 (Thread-4)正在执行异步任务:3 (Thread-4)正在执行异步任务:4 (Thread-4)正在执行异步任务:5 (Thread-4)正在执行异步任务:6 (Thread-4)正在执行异步任务:7 (Thread-4)正在执行异步任务:8 (Thread-4)正在执行异步任务:9
通过重写“AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler” 进行统一配置
PublicController.java(补充内容标红)
@RequestMapping(value = "/runAsyncTask", method = RequestMethod.GET,produces = "application/json;charset=UTF-8") public void runAsyncTask() throws Exception{ for (int i=0;i<5;i++){ atServiceImpl.executeAsyncTask("Thread-"+i); } }
AsyncTaskPoolConfig.java(补充内容标红)
package com.namejr.base; import com.alibaba.fastjson.JSON; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import sun.security.krb5.internal.PAData; import java.lang.reflect.Method; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 配置异步线程池 * **/ @Configuration public class AsyncTaskPoolConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { return new ThreadPoolExecutor(2,4,1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); } /** 处理异常 * 注:其实这个可以不用,只要在@Async的函数中做好“try{}catch(){}”记录就好了。不过如果不想每个异步方法都创建“try{}catch(){}”,那么可以通过这种方式进行处理。 * */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){ return new AsyncUncaughtExceptionHandler() { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { // 进行错误的日志记录 System.out.println("错误方法:"+method.getName()+",错误参数:"+ JSON.toJSONString(params)+",错误内容:"+ex.getMessage()); } }; } }
AsyncTaskServiceImpl.java(补充内容标红)
package com.namejr.serviceImpl; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; @Component @EnableAsync public class AsyncTaskServiceImpl { @Async public void executeAsyncTask(String tname) throws Exception{ for (int i=0;i<10;i++){ try{ Thread.sleep(500); }catch (Exception ignored){} System.out.println("("+tname+")正在执行异步任务:"+i); } // 因为重新了getAsyncUncaughtExceptionHandler,所以这里进行抛一个一次看看能否记录 throw new Exception("抛出异常..."); } }
二、定时任务(@Scheduled)
1、配置文件application.properties
server.port=8080 logging.config=classpath:logback-spring.xml fsbdemo.config.scheduled.cron = 0/5 * * * * ? fsbdemo.config..scheduled.fixedRate=5000 fsbdemo.config..scheduled.fixedDelay=10000 fsbdemo.config..scheduled.initialDelay=1000
2、启动定时任务
package com.namejr.serviceImpl; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component @EnableScheduling public class ScheduledTaskServiceImpl { @Value("${fsbdemo.config..scheduled.fixedRate}") private String tempFixedRate; @Value("${fsbdemo.config..scheduled.fixedDelay}") private String tempfixedDelay; @Value("${fsbdemo.config..scheduled.initialDelay}") private String tempinitialDelay; // 等同于直接写死的时间:@Scheduled(fixedRate = 5000) @Scheduled(fixedRateString = "${fsbdemo.config..scheduled.fixedRate}") public void fixedRateDemo(){ System.out.println("每隔"+tempFixedRate+"毫秒执行一次:"+ DateTime.now().toString("yyyy-MM-dd HH:mm:ss")); } // 等同于直接写死的时间:@Scheduled(initialDelay = 1000,fixedDelay = 10000) @Scheduled(initialDelayString = "${fsbdemo.config..scheduled.initialDelay}",fixedDelayString = "${fsbdemo.config..scheduled.fixedDelay}") public void initialDelayDemo(){ System.out.println("首次执行等待"+tempinitialDelay+"毫秒,后续依旧上次执行完毕后,经过"+tempfixedDelay+"毫秒再次执行:"+ DateTime.now().toString("yyyy-MM-dd HH:mm:ss")); } // 等同于直接写死的时间:@Scheduled(fixedDelay = 10000) @Scheduled(fixedDelayString = "${fsbdemo.config..scheduled.fixedDelay}") public void fixedDelayDemo(){ System.out.println("上次执行完毕后,经过"+tempfixedDelay+"毫秒再次执行:"+ DateTime.now().toString("yyyy-MM-dd HH:mm:ss")); } // 等同于直接写死的时间:@Scheduled(cron = "0/5 * * * * ?") @Scheduled(cron = "${fsbdemo.config.scheduled.cron}") public void cronDemo(){ System.out.println("设置指定的时间执行:"+ DateTime.now().toString("yyyy-MM-dd HH:mm:ss")); } }
输出:
每隔5000毫秒执行一次:2024-05-21 11:19:13 上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:13 首次执行等待1000毫秒,后续依旧上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:14 设置指定的时间执行:2024-05-21 11:19:15 每隔5000毫秒执行一次:2024-05-21 11:19:18 设置指定的时间执行:2024-05-21 11:19:20 每隔5000毫秒执行一次:2024-05-21 11:19:23 上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:23 首次执行等待1000毫秒,后续依旧上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:24 设置指定的时间执行:2024-05-21 11:19:25 每隔5000毫秒执行一次:2024-05-21 11:19:28 设置指定的时间执行:2024-05-21 11:19:30 每隔5000毫秒执行一次:2024-05-21 11:19:33 上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:33 首次执行等待1000毫秒,后续依旧上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:34 设置指定的时间执行:2024-05-21 11:19:35 每隔5000毫秒执行一次:2024-05-21 11:19:38 设置指定的时间执行:2024-05-21 11:19:40 每隔5000毫秒执行一次:2024-05-21 11:19:43 上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:43 首次执行等待1000毫秒,后续依旧上次执行完毕后,经过10000毫秒再次执行:2024-05-21 11:19:44 设置指定的时间执行:2024-05-21 11:19:45 已与地址为 ''127.0.0.1:20845',传输: '套接字'' 的目标虚拟机断开连接 进程已结束,退出代码为 -1
注:关于cron的格式问题,可查看:Spring 定时任务@Scheduled 注解中的 Cron 表达式_@scheduled(cron-CSDN博客,以下是仅作笔记处理
Cron 表达式的语法格式如下:秒 分 时 日 月 星期 年份 其中,每个时间字段都有对应的取值范围和特殊符号。下面是每个时间字段的详细说明: 1、秒(Seconds):取值范围为 0~59。例如,`0/5` 表示每隔 5 秒触发一次,`*` 表示每秒都触发。 2、分钟(Minutes):取值范围为 0~59。例如,`0/5` 表示每隔 5 分钟触发一次,`*` 表示每分钟都触发。 3、小时(Hours):取值范围为 0~23。例如,`0/2` 表示每隔 2 小时触发一次,`*` 表示每小时都触发。 4、日期(Day of Month):取值范围为 1~31。例如,`1,15` 表示每月的 1 日和 15 日触发,`*` 表示每天都触发。 5、月份(Month):取值范围为 1~12,也可以使用英文缩写 JAN、FEB、MAR 等。例如,`1,6` 表示一月和六月触发,`*` 表示每个月都触发。 6、 星期(Day of Week):取值范围为 1~7,1 表示星期日,2 表示星期一,以此类推,也可以使用英文缩写 SUN、MON、TUE 等。例如,`2-6` 表示星期一到星期五触发,`*` 表示每个星期都触发。 7、年份(Year):可选字段,表示触发条件的年份。例如,`2023` 表示在 2023 年触发,`*` 表示每年都触发。 除了取值范围,Cron 表达式还支持一些特殊符号,用于指定特定的触发条件,例如: - 星号(*):代表所有可能的取值,表示不限制该时间字段的取值范围。 - 问号(?):仅在日期和星期字段中使用,表示不指定具体的取值,可以任意匹配。 - 斜线(/):表示间隔触发,例如在分钟字段中,"*/5" 表示每隔 5 分钟触发一次。 - 逗号(,):用于指定多个取值,例如在小时字段中,"1,3,5" 表示在第 1、3、5 小时触发。 - 减号(-):用于指定一个范围,例如在月份字段中,"3-6" 表示三月到六月触发。
//