案例
@Cacheable
是 Spring Framework 提供的一个注解,用于在方法执行前先检查缓存,如果缓存中已存在对应的值,则直接返回缓存中的值,而不执行该方法体。如果缓存中不存在对应的值,则执行方法体,并将方法的返回值存入缓存供下次使用。
在 Spring Boot 中,@Cacheable
注解通常与缓存管理器一起使用,可以轻松地在方法级别上实现缓存功能,避免不必要的重复计算或查询数据库操作,从而提高应用程序的性能和响应速度。
如何使用 @Cacheable
注解:
- 配置缓存管理器:首先,需要在 Spring Boot 应用中配置一个缓存管理器,例如使用 EhCache、Caffeine、Redis 等。这通常可以通过添加相应的依赖和配置来实现。
- 在方法上添加
@Cacheable
注解:将@Cacheable
注解添加到需要缓存的方法上,并指定缓存的名称或缓存管理器的名称,以及缓存的 key。
这种方式能够显著提高应用程序的性能,特别是在需要频繁访问相同数据的场景下,通过缓存可以避免重复的耗时操作。
案例-生成验证码
这里有一个词语
Caffenine 要记住
当前默认的缓存方案是Simple
缓存的使用案例——手机验证码
首先我们要封装实体类
在domain包下创建类SMSCode
用lombok进行封装
package com.example.demo.domain; import lombok.Data; @Data public class SMSCode { private String tele; private String code; }
在做一个业务层接口
放在service包下
有两个方法
第一个方法是生成验证码
第二个方法是一个校验作用
package com.example.demo.service; import com.example.demo.domain.SMSCode; import org.apache.ibatis.annotations.Mapper; public interface SMSCodeService { public String sendCodeToSMS(String tele); public boolean checkCode(SMSCode smsCode); }
做业务层的实现类
即为刚刚的接口书写实现类
package com.example.demo.service.impl; import com.example.demo.domain.SMSCode; import com.example.demo.service.SMSCodeService; import org.springframework.stereotype.Service; @Service public class SMSCodeServiceImpl implements SMSCodeService { @Override public String sendCodeToSMS(String tele) { return null; } @Override public boolean checkCode(SMSCode smsCode) { return false; } }
接下来书写表现层代码
Controller
package com.example.demo.controller; import com.example.demo.domain.SMSCode; import com.example.demo.service.SMSCodeService; import jdk.nashorn.internal.runtime.logging.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/sms") public class SMSCodeController { @Autowired private SMSCodeService smsCodeService; @GetMapping public String getCode(String tele){ String code =smsCodeService.sendCodeToSMS(tele); return code; } @PostMapping public boolean checkCode(SMSCode smsCode){ return smsCodeService.checkCode(smsCode); } }
接下来就是最核心的内容了
我们要在业务层去完善代码
我们是不是需要一个验证码
我们去创建一个工具类
去校验对应的验证码
创建一个工具类
注意的是
生成验证码的时候
要进行补全
package com.example.demo.utils; import org.springframework.stereotype.Component; @Component public class CodeUtils { public String generator(String tele){ int hash=tele.hashCode(); int encryption=20206666; long result=hash^encryption; long nowTime=System.currentTimeMillis(); result=result^nowTime; long code=result%1000000; code=code<0?-code:code; return String.format("%06d",code); } // //启动注释 // public static void main(String[] args) { // while(true) // System.out.println(new CodeUtils().generator("19850593532")); // } }
当然我们也可以拼接
把0 00 000 0000 00000放到一个数组里面去
然后判断生成验证码的长度 然后直接进行拼接
接下来我们要去补全业务层的实现类
我们把缓存注入 挂到实现类上面去
接下来我们要去检查缓存的依赖是否引入boot工程
看看启动响应类里面有没有打开缓存功能
检查完毕
我们直接给业务层实现类挂一个缓存就行
@Override @Cacheable(value = "smsCode",key="#tele") public String sendCodeToSMS(String tele) { String code=codeUtils.generator(tele); return code; }
启动我们的boot工程
我们打开postman
向服务器发起请求
依靠我们之前在表现层书写的get请求
@GetMapping public String getCode(String tele){ String code =smsCodeService.sendCodeToSMS(tele); return code; }
获取到了验证码
但是这边有个小问题
我们用同一个手机号发验证码
发起请求无论有多少次
获取到的验证码都是一样的
说明有缓存了
所以我们就思考到了
为什么我们每次靠手机号发送验验证码的时候会进入60秒的冷却等待时间
换个注解
仅仅往里面放缓存
@Override // @Cacheable(value = "smsCode",key="#tele") @CachePut(value = "smsCode",key="#tele") public String sendCodeToSMS(String tele) { String code=codeUtils.generator(tele); return code; }
这样就能获取到了
手机验证码 每次这个数值 都会变化
案例-校验功能
可以去CSDN学习下<artifactId>qcloudsms</artifactId>这个依赖
用到项目中,可以直接给手机发验证码(腾讯云SMS)
但是我们这边会有一个小问题
运行get的时候才会进行缓存
先执行代码在执行注解
注解根本就没有运行
注解没运行 返回null
注解根本就没加载
并没有执行spring容器管理
我们每次执行的时候都是空指针 返回值都是空
作为ioc容器管理的对象,其中注解生效。但如果不使用对象直接调用当然不生效
加了缓存的注解就相当于会给当前类生成一个代理对象, aop的思想, 在controller里调用方法和用this调用方法一个是走代理一个不走代理
同一个类中调用,没有使用ioc容器中的对象调用方法,注解没有被扫描,使用ioc容器的方法,注解才会被扫描
方法调用同实列的方法,代理失效
@Service也是个bean,但是写在service下用this调用,只是走普通方法的调用,没经过spring容器,@Cacheable也就没启动,所以取不到缓存的值,我是这样理解的
我想明白了,上面那个CachePut能使用是因为这个方法是在controller里面被调用的,用bean调用的,而这个是在service里面的方法间调用的,一个普通的类间的方法互相调用
我们的业务层核心逻辑
package com.example.demo.service.impl; import com.example.demo.domain.SMSCode; import com.example.demo.service.SMSCodeService; import com.example.demo.utils.CodeUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class SMSCodeServiceImpl implements SMSCodeService { @Autowired private CodeUtils codeUtils; @Override // @Cacheable(value = "smsCode",key="#tele") @CachePut(value = "smsCode",key="#tele") public String sendCodeToSMS(String tele) { String code=codeUtils.generator(tele); return code; } @Override public boolean checkCode(SMSCode smsCode) { String code=smsCode.getCode(); String cacheCode=codeUtils.get(smsCode.getTele()); return code.equals(cacheCode); } }
解决方案
我们要把get方法放到工具类里面去
package com.example.demo.utils; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; @Component public class CodeUtils { public String generator(String tele){ int hash=tele.hashCode(); int encryption=20206666; long result=hash^encryption; long nowTime=System.currentTimeMillis(); result=result^nowTime; long code=result%1000000; code=code<0?-code:code; return String.format("%06d",code); } @Cacheable(value = "smsCode",key="#tele") public String get(String tele){ return null; } }
这样我们就能用bean去调用
这个方法
就能让cacheCode
很巧合的加载进来
我们用postman获取