《docker 安装 MySQL 和 Redis》一文已介绍如何在 Docker 中安装 Redis,本文就看看 SpringBoot 如何整合 Redis。SpringBoot 提供了整合 Redis 的 starter,使用非常简单。
在 pom.xml 中添加 redis 的 starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
修改 application.yml 文件,添加 Redis 的配置:
spring: redis: host: 127.0.0.1 port: 6379 username: password: timeout: 5000 jedis: pool: max-active: 3 max-idle: 3 min-idle: 1 max-wait: -1
在 com.yygnb.demo.config
中创建 RedisConfig
,处理一些中文乱码问题。
com.yygnb.demo.config.RedisConfig
:
@Configuration public class RedisConfig { private final RedisTemplate redisTemplate; public RedisConfig(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * 解决redis插入中文乱码 * @return */ @Bean public RedisTemplate<Serializable, Object> redisTemplateInit() { StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer); return redisTemplate; } }
可封装一些 Redis 的常见操作。
com.yygnb.demo.utils.RedisUtils
:
@RequiredArgsConstructor @Component public class RedisUtils { private final RedisTemplate redisTemplate; /** * 指定缓存失效时间 * @param key * @param time 单位 秒 */ public void expire(Serializable key, long time) { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } } /** * 根据key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } /** * 普通缓存获取 * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Serializable value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } }
操作太多,这里简单放了几个常见的方法。
新建 controller 测试 Redis 的操作。
com.yygnb.demo.controller.RedisDemoController
:
@Tag(name = "Redis测试接口") @RequiredArgsConstructor @RestController @RequestMapping("/redis") public class RedisDemoController { private final RedisUtils redisUtils; @Operation(summary = "存值") @PostMapping() public void save(@RequestBody Map<String, Object> map) { Set<String> keys = map.keySet(); for (String key : keys) { redisUtils.set(key, map.get(key)); } } @Operation(summary = "取值") @GetMapping() public Object get(String key) { return redisUtils.get(key); } }
异步任务在后端开发中很常见,如生成报表,前端调用后端一个接口,如果数据量较大,后端生成报表会非常耗时,这时候如果是同步任务,等后端报表已经生成后,估计已经请求超时了。通常情况下,后端触发一个异步任务,成功触发任务后,后端就返回前端,无需等待报表生成成功。类似场景如同时给用户发送邮件和短信。
分别编写模拟发送邮件和短信的 Service,这里只是演示使用,故 Service 省略了接口定义,直接编写实现类:
com.yygnb.demo.service.impl.EmailService
:
@Slf4j @Service public class EmailService { public void sendEmail(String msg) { log.info("开始发送邮件: {}", msg); int i = new Random().nextInt(5); try { Thread.sleep(i * 1000); log.info("邮件发送成功"); } catch (InterruptedException e) { log.error("邮件发送失败"); e.printStackTrace(); } } }
发送短信的 Service 与邮件 service 类似。
com.yygnb.demo.service.impl.SmsService
:
@Slf4j @Service public class SmsService { public void sendSms(String msg) { log.info("开始发送短信: {}", msg); int i = new Random().nextInt(5); try { Thread.sleep(i * 1000); log.info("短信发送成功"); } catch (InterruptedException e) { log.error("短信发送失败"); e.printStackTrace(); } } }
创建 DemoService,在 DemoService 中调用上面两个 Service:
@Slf4j @RequiredArgsConstructor @Service public class DemoServiceImpl implements DemoService { private final EmailService emailService; private final SmsService smsService; @Override public void send(String msg) { log.info("发别发送短信和邮件"); smsService.sendSms(msg); emailService.sendEmail(msg); log.info("Demo Service 结束"); } }
在 DemoController 中添加接口:
@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("demo") public class DemoController { private final DemoService demoService; @GetMapping("async") public void asyncDemo(String msg) { demoService.send(msg); } }
请求该接口:
http://localhost:9099/demo/async?msg=hello
由于现在是同步执行,需要短信和邮件两个service都执行完后才会返回结果。而且输出的日志顺序是固定的:
接下来进行异步任务的改造。
首先在启动类上添加注解 @EnableAsync
开启异步任务。
@EnableAsync @MapperScan("com.yygnb.demo.mapper") @SpringBootApplication public class DemoApplication { ... }
在需要异步执行的方法上添加注解 @Async
。在 sendSms
和 sendEmail
两个方法上添加该注解:
... @Async public void sendEmail(String msg) { ... } ...
调用异步任务的方法与异步任务的方法,不能在同一个 Service 中,即上面的 demo中,send 方法与 sendSms 不能在同一个 Service中。
异步任务本质上是在子线程中运行的。可以自定义线程池。
创建线程池配置的实体类:
com.yygnb.demo.config.ThreadPoolInfo
:
@Data @Component @ConfigurationProperties(prefix = "thread-pool") public class ThreadPoolInfo { private int corePoolSize = 1; private int maxPoolSize = Integer.MAX_VALUE; private int keepAliveSeconds = 60; private int queueCapacity = Integer.MAX_VALUE; private String threadNamePrefix = "thread-"; }
com.yygnb.demo.config.AsyncConfig
:
@RequiredArgsConstructor @Configuration public class AsyncConfig { private final ThreadPoolInfo info; @Bean("asyncExecutor") public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(info.getCorePoolSize()); executor.setMaxPoolSize(info.getMaxPoolSize()); executor.setQueueCapacity(info.getQueueCapacity()); executor.setThreadNamePrefix(info.getThreadNamePrefix()); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }
在 application.yml 中配置线程池:
thread-pool: core-pool-size: 3 max-pool-size: 5 thread-name-prefix: yyg-async-
重启服务,访问上面的接口,日志如下:
感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,还请三连支持一下,点赞、关注、收藏,作者会持续与大家分享更多干货