Spring 和 缓存
缓存可以存储经常会用到的信息,这样每次需要的时候,这些信息都是立即可用的.
尽管Spring自身并没有实现缓存解决方案,但是它对缓存功能提供了声明式的支持,能够和如 ehcache、redis 实现集成.
实现机制
基于 Spring AOP
Spring AOP 通过 后置处理器的方式,在Spring bean 实例化阶段为该bean 生成对应的代理对象,从而实现功能增强的结果.
缓存注解的介绍
Spring 对缓存的实现有好几个方式,但是这几个方式都需要借助于几个基本的注解,这里对这几个注解进行介绍.
这些注解都是用于修饰方法的.
@Cacheable
该注解修饰的方法,表示对该方法的返回结果进行缓存,每次调用优先从缓存中寻找方法返回值,如果缓存不存在再调用该方法,此时返回值会被放入缓存中
适用场景:单纯查询方法的缓存
/**
* value 是缓存规则的名字,可以指定多个,指定多个就会有多个缓存
* key 是缓存的名字,可以用 Spel 表达式直接从方法入参获取,当然也可以写为固定值
* condition 是条件判断,满足条件的才会被缓存
* sync 是否同步,true 为支持,Spring 4.3 后开始支持
*/
//@Cacheable(value = {"ehcache_test"},key = "#user.id",condition = "#user.id>100",sync=true)
@Cacheable(value = {"ehcache_test"},key = "#user.id",condition = "#user.id>100")
@Override
public String aMethod(User user) {
System.out.println("invoke aMethod");
return Math.random()+"";
}
@CacheEvict
该注解修饰的方法,在调用时会清除指定的缓存,可以一次指定多个
适用场景:单纯清除指定缓存规则、key的缓存
/**
* value 是缓存规则的名字,可以指定多个,指定多个就会有多个缓存
* key 缓存的名字,可以用 Spel 表达式直接从方法入参获取
* condition 是条件判断,满足条件的才会起作用
* allEntries 默认为只有 key 值匹配的才会被清楚缓存,设置为true则所有的缓存都失效,不可与key同时指定
* beforeInvocation 是否在方法调用前清楚缓存,默认为false 即只有方法完全调用成功才可以
*/
@CacheEvict(value = { "ehcache_test" },key = "#user.id",condition = "#user.id>100")
@Override
public void bMethod(User user) {
System.out.println("invoke bMethod()");
}
@CachePut
表明 Spring 应该将方法返回值放到缓存中.在方法的调用前并不会检查缓存,方法始终会被调用
适用场景:需要强制更新缓存内容的场景
/**
* value 是缓存规则的名字,可以指定多个,指定多个就会有多个缓存
* key 缓存的名字,可以用 Spel 表达式直接从方法入参获取
* condition 是条件判断,满足条件的才会起作用
*/
@CachePut(value = { "ehcache_test" },key = "#user.id",condition="#user.id>100")
@Override
public String cMethod(User user) {
System.out.println("invoke cMethod");
return Math.random()+"";
}
@Caching
是一个分组的注解,能够同时应用多个其它的缓存注解
适用场景:需要一次配置多个缓存配置的情况——这种场景多见于一次修改导致多个缓存失效的情况
/**
* @Caching 可以同时配置多个@Cacheable、@CacheEvict、@CachePut
* 内部规则就是所有分规则的集合
* 需要注意的是
*/
@Caching(cacheable = {@Cacheable(value={"ehcache_test"},key="#p0.id"),@Cacheable(value={"ehcache_test"},key="#p0.id")},
evict = {@CacheEvict(value={"ehcache_test","demo"},key= "#p0.id")},
put = {@CachePut(value={"ehcache_test"},key="#p0.id")})
@Override
public String dMethod(User user) {
System.out.println("invoke dMethod");
return Math.random()+"";
}
缓存注解的相关问题
key 值的取值
源码中有如下内容,key值可由Spel表达式动态的获取,默认没有设置的情况下,会将方法中所有的参数作为key
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.
* <p>Default is "", meaning all method parameters are considered as a key.
*/
String key() default "";
具体选择来说,可以分为三类,基本涵盖了 方法入参、方法名、方法类
-
第一类是直接以 #方法入参(或子属性)作为key
key="#id"
key="#user.id" -
第二类是使用Spel的 #p0(或子属性) 作为key
key="#p0"
key="#p0.id" -
第三类是使用 #root 相关属性作为key,可以将“#root”省略,因为Spring默认使用的就是root对象的属性
属性名称 | 描述 | 示例 |
---|---|---|
methodName | 当前方法名 | #root.methodName |
method | 当前方法 | #root.method.name |
target | 当前被调用的对象 | #root.target |
targetClass | 当前被调用的对象的class | #root.targetClass |
args | 当前方法参数组成的数组 | #root.args[0] |
caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
并发问题
一般而言,缓存都是用于缓解数据库的压力,这样一来,当缓存为空,而高并发查询发生时,可能会瞬间搞跨数据库,这个 问题怎么解决?
Spring 4.3 开始,在 @Cachable 注解中增加了 sync 属性,当sync=true 时,只有一次方法调用结束后才会允许其它访问进来,从而避免了对数据库对并发访问
/**
* @Cacheable 适用场景:单纯查询方法的缓存
* value 是缓存规则的名字,可以指定多个,指定多个就会有多个缓存
* key 是缓存的名字,可以用 Spel 表达式直接从方法入参获取
* condition 是条件判断,满足条件的才会被缓存
*/
@Cacheable(value = {"ehcache_test"},key = "#user.id",condition = "#user.id>100",sync=true)
@Override
public String aMethod(User user) {
System.out.println("invoke aMethod");
return Math.random()+"";
}
注解使用案例
这里在未来会逐步替换为各具体场景的案例
/**
* @Cacheable 适用场景:单纯查询方法的缓存
* value 是缓存规则的名字,可以指定多个,指定多个就会有多个缓存
* key 是缓存的名字,可以用 Spel 表达式直接从方法入参获取
* condition 是条件判断,满足条件的才会被缓存
* sync 是否同步,true 为支持,Spring 4.3 后开始支持
*/
//@Cacheable(value = {"ehcache_test"},key = "#user.id",condition = "#user.id>100",sync=true)
//@Cacheable(value = {"ehcache_test"},key = "#user.id",condition = "#user.id>100")
@Cacheable(value = {"ehcache_test"},key = "#p0.id",condition = "#p0.id>100")
@Override
public String aMethod(User user) {
System.out.println("invoke aMethod");
return Math.random()+"";
}
/**
* @CacheEvict 适用场景:该注解修饰的方法,在调用时会清除指定的缓存,可以一次指定多个
* value 是缓存规则的名字,可以指定多个,指定多个就会有多个缓存
* key 缓存的名字,可以用 Spel 表达式直接从方法入参获取
* condition 是条件判断,满足条件的才会起作用
* allEntries 默认为只有 key 值匹配的才会被清楚缓存,设置为true则所有的缓存都失效,不可与key同时指定
* beforeInvocation 是否在方法调用前清楚缓存,默认为false 即只有方法完全调用成功才可以
*/
@CacheEvict(value = { "ehcache_test" },key = "#user.id",condition = "#user.id>100")
@Override
public void bMethod(User user) {
System.out.println("invoke bMethod()");
}
/**
* @CachePut 适用场景
* value 是缓存规则的名字,可以指定多个,指定多个就会有多个缓存
* key 缓存的名字,可以用 Spel 表达式直接从方法入参获取
* condition 是条件判断,满足条件的才会起作用
*/
@CachePut(value = { "ehcache_test" },key = "#user.id",condition="#user.id>100")
@Override
public String cMethod(User user) {
System.out.println("invoke cMethod");
return Math.random()+"";
}
/**
* 这个方法看看就行
* @Caching 可以同时配置多个@Cacheable、@CacheEvict、@CachePut
* 内部规则就是所有分规则的集合
* 需要注意的是
*/
@Caching(cacheable = {@Cacheable(value={"ehcache_test"},key="#p0.id"),@Cacheable(value={"ehcache_test"},key="#p0.id")},
evict = {@CacheEvict(value={"ehcache_test","demo"},key= "#p0.id")},
put = {@CachePut(value={"ehcache_test"},key="#p0.id")})
@Override
public String dMethod(User user) {
System.out.println("invoke dMethod");
return Math.random()+"";
}
/**
* #root.method.name 可以省略 #root.
*/
//@Cacheable(value="ehcache_test",key="#root.method.name")
@Cacheable(value="ehcache_test",key="method.name")
@Override
public String eMethod(Long id) {
System.out.println("invoke eMethod");
return id+"";
}
/**
* #root.caches[index] 可以省略 #root
* #root.args[index] 可以省略 #root
*/
//@Cacheable(value="ehcache_test",key="caches[0]")
@Cacheable(value="ehcache_test",key="args[0]")
@Override
public String fMethod(Long id) {
System.out.println("invoke fMethod");
return id+"";
}
参考资料
[1]、https://blog.csdn.net/newbie_907486852/article/details/81478046 基础概念
[2]《Spring 实战 4》基础概念和项目整合
[3]、https://www.cnblogs.com/fashflying/p/6908028.html 基础概念
[4]、https://blog.csdn.net/clementad/article/details/52452119 并发问题