SpingMVC 注解@RequestMapping、@SuppressWarnings、@Scheduled 定时器

目录

@RequestMapping 请求映射

@SuppressWarnings 抑制警告

@Scheduled 执行定时任务

多任务并行执行

cron 表达式

集群下防止重复执行


@RequestMapping 请求映射

1、@RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上,用于类上时,表示类中的所有响应请求的方法都是以该地址作为父路径。

2、@RequestMapping 注解有如下属性

属性描述举例
String name() default ""为该映射指定一个名称
String[] value() default {}指定请求的实际地址,value值前缀"/"可写可不写

@RequestMapping("/visits"),

@RequestMapping(value = {"visits1","visits2"})

String[] path() default {}等同 value
RequestMethod[] method() default {}指定请求的 method 类型,有 GET、POST、PUT、DELETE 等@RequestMapping(value = "visits",method = RequestMethod.GET)
String[] params() default {}指定 request 中必须包含某些参数值时才让该方法处理@RequestMapping(value = "test4", params = "token", method = RequestMethod.GET)
String[] headers() default {}指定 request中必须包含某些指定的 header 值,才能让该方法处理请求
String[] consumes() default {}指定处理请求的提交内容类型(Content-Type),例如 application/json, text/html
String[] produces() default {}指定返回的内容类型,仅当 request 请求头中的(Accept)类型中包含该指定类型才返回

 3、举例如下:

@RestController
@RequestMapping(value = "visits")
public class VisitsController {
    //http://localhost:8080/visits/test1
    @RequestMapping(value = "test1", method = RequestMethod.GET)
    public String visitsTest1(HttpServletRequest request) {
        return request.getRequestURL().toString();
    }
    //http://localhost:8080/visits/test2, http://localhost:8080/visits/test3 都会进入
    @RequestMapping(value = {"test2", "test3"}, method = RequestMethod.GET)
    public String visitsTest23(HttpServletRequest request) {
        return request.getRequestURL().toString();
    }
    //http://localhost:8080/visits/test4?token=98, 请求必须携带参数 token,否则匹配不上,进入不了方法
    @RequestMapping(value = "test4", params = "token", method = RequestMethod.GET)
    public String visitsTest4(HttpServletRequest request) {
        return request.getRequestURL().toString();
    }
    //http://localhost:8080/visits/test5/his, http://localhost:8080/visits/test5/5x,路径结尾必须有路径变量
    @RequestMapping(value = "test5/{type}", method = RequestMethod.POST)
    public String visitsTest5(@PathVariable(value = "type") String type, HttpServletRequest request) {
        return request.getRequestURL().toString() + "\t" + type;
    }
}

src/main/java/com/wmx/yuanyuan/controller/VisitsController.java · 汪少棠/yuanyuan - Gitee.com

4、随着现在 rest 风格的流行,人们更倾向与使用简洁的 @GetMapping、@PostMapping、@DeleteMapping、@PutMapping 等注解,它们的属性与 @RequestMapping 完全一样。

@GetMapping(value = "test1") 等价于 @RequestMapping(value = "test1", method = RequestMethod.GET)
@PostMapping(value = "test5/{type}") 等价于 @RequestMapping(value = "test5/{type}", method = RequestMethod.POST)
@DeleteMapping 等价于 @RequestMapping(value = "test1", method = RequestMethod.DELETE)
@PutMapping 等价于 @RequestMapping(value = "test1", method = RequestMethod.PUT)

@SuppressWarnings 抑制警告

1、java.lang.SuppressWarnings 注解主要用在取消一些编译器产生的警告对代码左侧行列的遮挡,比如这会挡住断点调试时打的断点。

2、通过源码可知 @SuppressWarnings 其注解目标为类、字段、构造函数、方法、方法参数、方法内的局部变量。

3、@SuppressWarnings({"value1","values2"...}),其中的 value 取值如下:

public void show2() {
    @SuppressWarnings("unused")
    int a;
}
@java.lang.SuppressWarnings("unused")
public void show1() {
    int a;
}
@SuppressWarnings(value = {"unused","unchecked"})
public void show3() {
    int a;
}

3、@SuppressWarnings 抑制的警告类型如下:

警告类型描述
all抑制所有警告
boxing禁止相对于装箱/取消装箱操作的警告
cast抑制与强制转换操作相关的警告
dep-ann禁止显示与已弃用的批注相关的警告
deprecation取消与否决相关的警告
fallthrough禁止显示与switch语句中缺少中断相关的警告
finally抑制与finally块相关的不返回的警告
hiding抑制相对于隐藏变量的局部变量的警告
incomplete-switch禁止显示与switch语句中缺少项有关的警告(枚举情况)
nls禁止显示与非nls字符串文本相关的警告
null抑制与空分析相关的警告
rawtypes对类参数使用泛型时,禁止显示与非特定类型相关的警告
restriction抑制与使用不鼓励或禁止的引用相关的警告
serial取消显示与可序列化类缺少串行版本uuid字段相关的警告
static-access禁止与不正确的静态访问相关的警告
synthetic-access禁止与来自内部类的未优化访问相关的警告
unchecked取消显示与未检查的操作相关的警告
unqualified-field-access取消与字段访问不合格相关的警告
unused禁止显示与未使用的代码相关的警告
Duplicates抑制 Found duplicate code(代码重复)的警告

@Scheduled 执行定时任务

1、@Scheduled 用在方法上,表示定时执行此方法,前提是这个类的实例需要由 Spring 容器管理,所以通常和 @Controller、@Service、@Repository、@Component 等注解一起使用。

2、如果是以前配置文件的方式,则需要在 spring 配置文件中配置扫描,而使用注解则更加简洁了。

1) Spring Boot 启动类上加上 @org.springframework.scheduling.annotation.EnableScheduling 注解,表示开启 @Scheduled
2)在 @Controller、@Service、@Repository、@Component 等组件中提供 @Scheduled 方法,表示定时执行此方法
3)默认情况下一个组件中的所有 @Scheduled 采用一个单线程执行,即一个 @Scheduled 执行完成后,另一个 @Scheduled 才能执行
4)如果想要并发执行组件中的所有 @Scheduled,则需要在类上加上 @org.springframework.scheduling.annotation.EnableAsync 注解,在方法上再加上 @org.springframework.scheduling.annotation.Async 注解

3、底层使用 Spring 的任务调度线程池 ThreadPoolTaskScheduler,其底层使用的为JDK自带的ScheduledExecutorService(其实现类为ScheduledThreadPoolExecutor),默认线程池是一个线程,即多任务调度会串行执行。

4、环境:Java JDK 1.8,Spring Boot 2.0.3,举例如下:

4.1 启动类:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling  //开启 @Scheduled 定时任务
public class WebAppApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebAppApplication.class, args);
    }
}

4.2 提供一个组件设置定时任务:

//1、EnableAsync:开启并发执行所有定时任务,这个不是必须的,可以不配置。
//2、EnableScheduling:开启定时任务
//3、默认情况下一个组件中的所有 @Scheduled 采用一个单线程执行,即一个 @Scheduled 执行完成后,另一个 @Scheduled 才能执行
@Component
@EnableAsync
public class SystemTimer {
   /**
     * 每隔约定时间执行一次。上一次任务执行完成如果没有超时,则会继续延迟,如果已经超时,下一次任务则会立即执行
     * 1、Async :并发执行此任务,这个不是必须的,可以不配置。
     * 2、开启异步执行时,不再有延迟的问题,因为时间一到,自动会新开线程执行任务.
     * 3、fixedRate、fixedDelay、cron 只能出现一个,不能同时设置。
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    @Async
    public void time1() {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = new Date();
            System.out.println("time1->" + Thread.currentThread().getName() + " -> " + dateFormat.format(date));
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 启动 initialDelay 毫秒后开始执行。
     * 每隔 fixedDelay 毫秒执行一次。下一次会在上一次任务执行完成后,继续延迟 fixedDelay 毫秒后再执行。
     * 1、fixedRate、fixedDelay、cron 只能出现一个,不能同时设置。
     */
    @Scheduled(initialDelay = 60 * 1000, fixedDelay = 60 * 1000)
    @Async
    public void time2() {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = new Date();
            System.out.println("time2->" + Thread.currentThread().getName() + " -> " + dateFormat.format(date));
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 每隔 fixedRate 毫秒执行一次。下一次会在上一次任务执行完成后执行,如果上一次任务执行超时,则下一次会立即执行
     * 1、fixedRate、fixedDelay、cron 只能出现一个,不能同时设置。
     */
    @Scheduled(fixedRate = 60 * 1000)
    @Async
    public void time3() {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = new Date();
            System.out.println("time3->" + Thread.currentThread().getName() + " -> " + dateFormat.format(date));
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

https://gitee.com/wangmx1993/web_app/blob/master/src/main/java/com/wmx/web_app/component/SystemTimer.java

多任务并行执行

1、@Scheduled 底层使用 Spring 的任务调度线程池 ThreadPoolTaskScheduler,其底层使用的为JDK自带的ScheduledExecutorService(其实现类为ScheduledThreadPoolExecutor),默认线程池是一个线程,即多任务调度会串行执行。

方式1:@EnableAsync + @async + @schedule 一起使用

1、注意此种方式多任务并行执行时,对于同一个任务,即使上一次执行还未完成,只要时间一到就会立即再次执行该任务,如果任务之间有先后执行顺序,则不推荐使用此种方式。

2、这种方式上一节已经介绍了,请参考上一节。

方式2:全局配置 ThreadPoolTaskScheduler 线程池线程数量

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
 * 多任务(@Scheduled)并行配置
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2023/4/8 12:14
 */
@Configuration
public class ScheduleConfig {
    /**
     * 多任务(@Scheduled)并行配置
     * 1、此时整个应用中全部执行任务的线程名称类似为:taskScheduler-1、taskScheduler-2、taskScheduler-3、......
     */
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 返回Java虚拟机可用的处理器数 * 2
        int poolSize = Runtime.getRuntime().availableProcessors() * 2;
        System.out.println("任务调度线程池大小:" + poolSize);
        // 核心线程池数量
        taskScheduler.setPoolSize(poolSize);
        return taskScheduler;
    }
}

https://gitee.com/wangmx1993/web_app/blob/master/src/main/java/com/wmx/web_app/config/ScheduleConfig.java

方式3:实现 SchedulingConfigurer 接口

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(
                new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2)
        );
    }
}

cron 表达式

1、Cron 表达式是一个字符串,用空格隔开,分为6或7个域,从左到右为:秒 分 小时 月份中的日期 月份 星期中的日期 年份。年份可写可不写。在线Cron表达式生成器

@Scheduled(cron = "0/3 * * * * ?")每 3 秒钟执行一次
@Scheduled(cron = "0 0/30 * * * ?")每 30 分钟在第 0 秒时刻执行一次
@Scheduled(cron = "0 0/30 0 * * * ?")在每天 0 点内,每 30 分钟在第 0 秒时刻执行一次
@Scheduled(cron = "40 0/30 * * * ?")每 30 分钟在第 40 秒时刻执行一次
@Scheduled(cron = "0 0 12 * * 2,6")每周1、周5在 12:0:0 时刻执行一次

2、介绍 Cron 表达式的文章网上太多了,不再累述:

cron表达式详解 - 沧海一粟hr - 博客园 https://www.jb51.net/article/138900.htm cron表达式_一直特立独行的猫-CSDN博客 Cron表达式 - imstrive - 博客园

集群下防止重复执行

1、当服务集群部署时,一旦到达指定时间,多个实例上的定时器就会同时执行任务,从而浪费资源,甚至造成重复数据或者程序异常。

2、可以考虑使用第三方的分布式定时框架,比如 Quartz,比较简单的方式是借助 redis 实现。

/**
     * 集群环境下防止重复执行,同一任务只允许一个实例执行
     * 1、执行任务的线程往 redis 中存储一个 key表示正在执行任务,其它实例则可以不用重复执行。
     * 2、缓存的 key 的过期时间可以根据任务执行的时间间隔来定:
     * * 2.1、比如任务是每天晚上 0 点执行,则过期时间设置为 1 个小时都行,因为第二天又是全新的。
     * * 2.2、间隔执行时,过期时间设置为间隔时间减去方法执行时间,这样即使不同实例不是同一时间启动,也能保证间隔时间内只会有一个实例执行。
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    @Async
    public void time4() {
        try {
            //定时任务执行的间隔时间(毫秒)
            long startTimeMillis = System.currentTimeMillis();
            long intervalTimeMillis = 1 * 60 * 1000;

            String time = DateUtil.date().toString();
            String cacheKey = "SystemTimer#time4";
            Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(cacheKey, instanceName, intervalTimeMillis, TimeUnit.MILLISECONDS);
            if (ifAbsent) {
                System.out.println("==============" + time + " 实例【" + instanceName + "】获得执行任务权限.");
                //可以将任务执行的实例情况存储到 redis 中,最多记录 100条.
                Long leftPush = redisTemplate.opsForList().leftPush(cacheKey + "#log", time + "【" + instanceName + "】");
                if (leftPush > 100) {
                    redisTemplate.opsForList().rightPop(cacheKey + "#log");
                }

                //模拟业务操作耗时.
                TimeUnit.MILLISECONDS.sleep(1000 + new SecureRandom().nextInt(3000));

                //任务执行完成时,防止下一个任务时间到了,上一个任务的 key 还没有过期。重新设置一下 key 的过期时间,因为上面 setIfAbsent 的过期时间并不包括任务执行时间.
                //在减去任务执行耗时的基础上,再多减去几秒,把 expire 操作的耗时也计划在内。如果过期时间低于1秒,就不再设置了。
                long expire = intervalTimeMillis - (System.currentTimeMillis() - startTimeMillis) - 3000;
                if (expire > 1000) {
                    redisTemplate.expire(cacheKey, expire, TimeUnit.MILLISECONDS);
                }
            } else {
                Object value = redisTemplate.opsForValue().get(cacheKey);
                System.out.println("==============" + time + " 实例【" + instanceName + "】未获得执行任务权限,任务当前正在被实例【" + value + "】执行。");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

src/main/java/com/wmx/wmxredis/schedule/SystemTimer.java · 汪少棠/wmx-redis - Gitee.com

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蚩尤后裔-汪茂雄

芝兰生于深林,不以无人而不芳。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值