笔记

万物寻其根,通其堵,便能解其困。
  博客园  :: 新随笔  :: 管理

关于Spring boot的异步处理(@Async)和定时任务(@Scheduled)

Posted on 2024-05-21 10:56  草妖  阅读(8)  评论(0)    收藏  举报

一、异步处理(@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~71 表示星期日,2 表示星期一,以此类推,也可以使用英文缩写 SUN、MON、TUE 等。例如,`2-6` 表示星期一到星期五触发,`*` 表示每个星期都触发。
7、年份(Year):可选字段,表示触发条件的年份。例如,`2023` 表示在 2023 年触发,`*` 表示每年都触发。
除了取值范围,Cron 表达式还支持一些特殊符号,用于指定特定的触发条件,例如:
- 星号(*):代表所有可能的取值,表示不限制该时间字段的取值范围。
- 问号(?):仅在日期和星期字段中使用,表示不指定具体的取值,可以任意匹配。
- 斜线(/):表示间隔触发,例如在分钟字段中,"*/5" 表示每隔 5 分钟触发一次。
- 逗号(,):用于指定多个取值,例如在小时字段中,"1,3,5" 表示在第 135 小时触发。
- 减号(-):用于指定一个范围,例如在月份字段中,"3-6" 表示三月到六月触发。

 

 

//