一、springBoot与缓存
1. JSR107
Java Caching定义了5个核心接口,分别是CachingProvider
, CacheManager
,Cache
, Entry
和 Expiry
CachingProvider
定义了创建、配置、获取、管理和控制多个CacheManager
,一个应用可以在运行期访问多个CachingProvider
CacheManager
定义了创建、配置、获取、管理和控制多个唯一命名的Cache
,这些Cache
存在于CacheManager
的上下文中,一个CacheManager
仅被一个CachingProvider
所拥有Cache
是一个类似Map
的数据结构并临时存储以Key
为索引的值,一个Cache
仅被一个CacheManager
所拥有,在后面的源码分析中我们可以发现Cache其实就是一个HashMap为主的一个类Entry
是一个存储在Cache
中的key-value
对这里的Entry和Map中的内部类是一个意思,所以在这里也更能确定Cache就是一个以Map为主要数据结构的类Expiry
每一个存储在Cache
中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy
设置。
可以用这样一幅图总结一下:
2.spring的缓存抽象
spring
提供了一系列的注解来方便我们的开发,今天我们主要是进行源代码的分析,我们以@Cacheable
注解为例;
二、开始前的准备
缓存其实就是在应用程序和数据库之间加的一个中间层,用来加速数据的检索;
所以我们今天的示例要先在数据库中创建一个User
表:
1.创建User表
2.创建一个springBoot应用
在idea
上创建springBoot
是很方便的这里就不演示了;
这些是我导入的依赖,Edit Starters
是一个idea
插件可以让你的springBoot项目在创建以后以勾选的方式添加依赖;
3.配置springBoot项目
主要就是一个开启缓存的注解
这里最后的debug=true
开启以后就可以看到sprngBoot程序启动的时候自动配置的信息;
接下来就是代码:
首先要有一个User
实体类:
@Data
public class User {
private int id;
private String name;
}
这里我使用了一个idea
的lombok
插件,可以让你不用再去写那些get set
方法了;
Mapper:这里我们为了方便采用注解开发(但是我本人还是喜欢xml方式去写sql)
@Mapper
public interface UserMapper {
@Select("select *from user where id = #{id}")
User getUserById(int id);
@Update("update user set name = #{name} where id = #{id}")
void updateUser(User user);
@Delete("delete from user where id = #{id}")
void deleteUser(int id);
@Insert("insert into user values(#{id}, #{name})")
void insertUser(User user);
}
这里我们就有了一个基本的增删改查的接口;
Service层:这里应该是一个接口对应一个实现,为了方便我们就不写接口了
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "user", key = "#root.methodName+'['+#id+']'")
public User getUser(int id) {
System.out.println("查询");
User user = userMapper.getUserById(id);
return user;
}
}
应为我们是一个web项目,所以我们还需要一个Controller:
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/getUser/{id}")
public User getUser(@PathVariable("id") int id) {
return userService.getUser(id);
}
}
4.演示
我们启动项目:
第一次访问后台并没有缓存所以我们访问了数据库有sql
语句
当我们第二次访问这个url
我们会发现我们并没有去访问数据库,甚至没有去调用UserService
中的getUser
方法
三、原理分析
在springBoot中所有的自动配置都是...AutoConfiguration
所以我们去搜CacheAutoConfiguration
这个类
在这个类中有一个静态内部类CacheConfigurationImportSelector
他有一个selectImport
方法是用来给容器中添加一些缓存要用的组件;
我们在这里打上断点,debug调试一下看看imports
中有哪些缓存组件
我们可以看到这里总共有十个缓存组件;我们随便去看一个
会发现在他的注解上表明了什么时候使用这个组件;
那么接下来我们来看看springBoot默认使用的缓存组件是什么;
(这里就不把所有的查询结果放出了)
我们最终会发现只有SimpleCacheConfiguration
是被使用的,所以也就说明默认情况下使用SimpleCacheConfiguration
;
然后我们进入到SimpleCacheConfiguration
中:
我们会发现他给springBoot容器添加了一个bean,是一个CacheManager
;
ConcurrentMapCacheManager
实现了CacheManager
接口
这里要说一个@Nullable
注解这个注解是说传入的参数可以为null
在写程序的时候你可以定义是否可为空指针。通过使用像@NotNull和@Nullable之类的annotation来声明一个方法是否是空指针安全的。现代的编译器、IDE或者工具可以读此annotation并帮你添加忘记的空指针检查,或者向你提示出不必要的乱七八糟的空指针检查。IntelliJ和findbugs已经支持了这些annotation。这些annotation同样是JSR 305的一部分,但即便IDE或工具中没有,这个annotation本身可以作为文档。看到@NotNull和@Nullable,程序员自己可以决定是否做空指针检查。顺便说一句,这个技巧对Java程序员来说相对比较新,要采用需要一段时间。
getCache
方法使用了双重锁校验(这种验证机制一般是用在单例模式中)
我们可以看到如果没有Cache
会调用
cache = this.createConcurrentMapCache(name);
这个方法会创建一个ConcurrentMapCache
这个就是我们说的Cache
;
在这个类里面有这样三个属性;
private final ConcurrentMap<Object, Object> store;
这个就是前文中的Entry
用来存放键值对;
在ConcurrentMapCache
中我们会看到一些操作Cache
的方法我选几个重要的
lookup
方法是根据key来找value的;
put
方法顾名思义是用来添加键值对的;
到这里基本上就结束了,接下来我们来详细分析一下@Cacheable
注解
四、@Cacheable分析
我们在上述的两个方法上打上断点;debug运行springBoot;
访问getUser接口;
我们会发现他来到了lookup
方法这里,说明注解的执行在被注解的方法前,然后这里我们会返回null;
我们放行到下一个注解会发现;调用了put方法
添加了Cache;然后我们第二次对getUser接口发起请求我们会发现打断点的两个方法没有被执行
因为在这里cache不为null了,直接被返回了;
以上就是我对于springBoot缓存的理解