Spring从3.1开始定义了一系列抽象接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们进行缓存开发。Spring Cache 只负责维护抽象层,具体的实现由你的技术选型来决定。将缓存处理和缓存技术解除耦合。
Spring cache 抽象由spring-context相关组件实现。非Spring Boot 项目可通过引入该模块进行集成。
Spring Boot 项目可引入以下依赖:
org.springframework.boot
spring-boot-starter-cache
同时可能需要引入你采用的缓存中间件客户端;比如 Ehcache、redis等。
Spring cache 提供了一系列的注解,将我们从编程开发中解放出来。让我们更加关注于业务开发。
该注解是启用Spring cache 的开关。必须开启才能使用Spring cache相关功能。
作用于缓存接口上,来对该接口下的一些重复配置(缓存名称、key生成器、缓存管理器、缓存处理器)进行归纳处理。其他属性可参考Cacheable。
可以标记在一个方法或者类上。方法级只针对该方法。类上则针对类内所有的方法。在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果。
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
//和value注解差不多,二选一
String[] cacheNames() default {};
// 该次缓存的key
String key() default "";
//key的生成器。key/keyGenerator二选一使用
String keyGenerator() default "";
//指定缓存管理器 一般使用默认
String cacheManager() default "";
//或者指定获取解析器 一般使用默认
String cacheResolver() default "";
//条件符合则缓存 使用的比较多 支持SpEL
String condition() default "";
//条件符合则不缓存 使用的比较多 支持SpEL
String unless() default "";
//是否使用异步模式
boolean sync() default false;
}
后面有个别注解属性跟这个基本相同不进行重复介绍。
1.#参数名”或者“#p参数index”
@Cacheable(value="users", key="#id")
public User find(Integer id) {
returnnull;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
returnnull;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
returnnull;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
returnnull;
}
2.Spring还为我们提供了一个root对象可以用来生成key
Spring默认的SimpleKeyGenerator是不会将函数名组合进key中的
@Bean
public KeyGenerator KeyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
@Cacheable(value="gomeo2oCache", keyGenerator = "keyGenerator")
public ResultDTO method(User user);
public @interface CachePut {
String[] value(); //缓存的名字,可以把数据写到多个缓存
String key() default ""; //缓存key,如果不指定将使用默认的KeyGenerator生成,后边介绍
String condition() default ""; //满足缓存条件的数据才会放入缓存,condition在调用方法之前和之后都会判断
String unless() default ""; //用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了
}
该注解容易跟@Cacheable混淆。两者都可以执行缓存的“放入”操作,不同于@Cacheable,@CachePut每在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中,如果该K存在则进行更新。其他属性可参考Cacheable。
public @interface CacheEvict {
String[] value(); //请参考@CachePut
String key() default ""; //请参考@CachePut
String condition() default ""; //请参考@CachePut
boolean allEntries() default false; //是否移除所有数据
boolean beforeInvocation() default false;//是调用方法之前移除/还是调用之后移除
@CachEvict主要针对方法配置,能够根据一定的条件对特定的缓存进行清空。该注解有两个特别的属性:
该注解是个组合注解。有时候我们需要在一个方法上同时使用多个相同注解但是java是不支持一个注解在同一个方法上多次使用。这时就可以使用该注解进行组合。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
return null;
}
运行流程
流程中需要注意的就是2/3/4步:
如果有@CachePut操作,即使有@Cacheable也不会从缓存中读取;问题很明显,如果要混合多个注解使用,不能组合使用@CachePut和@Cacheable;官方说应该避免这样使用(解释是如果带条件的注解相互排除的场景)。
Spring允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如我们有如下这么一个使用@Cacheable进行标注的自定义注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}
@MyCacheable
public User findById(Integer id) {
System.out.println("find user by id: " + id);
User user = new User();
user.setId(id);
user.setName("Name" + id);
return user;
}