目录
@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();
}
}
}
多任务并行执行
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;
}
}
方式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