Spring Cache 缓存注解
本篇文章代码示例在 Spring Cache 简略实现上的代码示例加以批改。
只有应用 public 定义的办法才能够被缓存,而 private 办法、protected 办法或者应用 default 修饰符的办法都不能被缓存。当在一个类上应用注解时,该类中每个公共办法的返回值都将被缓存到指定的缓存项中或者从中移除。
@Cacheable
@Cacheable 注解属性一览:
属性名
作用与形容
cacheNames/value
指定缓存的名字,缓存应用 CacheManager 治理多个缓存 Cache,这些 Cache 就是依据该属性进行辨别。对缓存的真正增删改查操作在 Cache 中定义,每个缓存 Cache 都有本人惟一的名字。
key
缓存数据时的 key 的值,默认是应用办法所有入参的值,能够应用 SpEL 表达式示意 key 的值。
keyGenerator
缓存的生成策略(键生成器),和 key 二选一,作用是生成键值 key,keyGenerator 可自定义。
cacheManager
指定缓存管理器(例如 ConcurrentHashMap、Redis 等)。
cacheResolver
和 cacheManager 作用一样,应用时二选一。
condition
指定缓存的条件(对参数判断,满足什么条件时才缓存),可用 SpEL 表达式,例如:办法入参为对象 user 则表达式能够写为 condition = “#user.age>18″,示意当入参对象 user 的属性 age 大于 18 才进行缓存。
unless
否定缓存的条件(对后果判断,满足什么条件时不缓存),即满足 unless 指定的条件时,对调用办法获取的后果不进行缓存,例如:unless = “result==null”,示意如果后果为 null 时不缓存。
sync
是否应用异步模式进行缓存,默认 false。
@Cacheable 指定了被注解办法的返回值是可被缓存的。其工作原理是 Spring 首先在缓存中查找数据,如果没有则执行办法并缓存后果,而后返回数据。
缓存名是必须提供的,能够应用引号、Value 或者 cacheNames 属性来定义名称。上面的定义展现了 users 缓存的申明及其注解的应用:
@Cacheable(“users”)//Spring 3.x@Cacheable(value = “users”)//Spring 从 4.0 开始新增了 value 别名 cacheNames 比 value 更达意, 举荐应用 @Cacheable(cacheNames = “users”)
键生成器
缓存的实质就是键 / 值对汇合。在默认状况下,缓存形象应用(办法签名及参数值)作为一个键值,并将该键与办法调用的后果组成键 / 值对。如果在 Cache 注解上没有指定 key,
则 Spring 会应用 KeyGenerator 来生成一个 key。
package org.springframework.cache.interceptor;import java.lang.reflect.Method;@FunctionalInterfacepublic interface KeyGenerator {Object generate(Object var1, Method var2, Object… var3);
}
Sping 默认提供了 SimpleKeyGenerator 生成器。Spring 3.x 之后废除了 3.x 的 DefaultKey
Generator 而用 SimpleKeyGenerator 取代,起因是 DefaultKeyGenerator 在有多个入参时只是简略地把所有入参放在一起应用 hashCode()办法生成 key 值,这样很容易造成 key 抵触。SimpleKeyGenerator 应用一个复合键 SimpleKey 来解决这个问题。通过其源码可得悉 Spring 生成 key 的规定。
/**
* SimpleKeyGenerator 源码的类门路参见{@link org.springframework.cache.interceptor.SimpleKeyGenerator}
*/
从 SimpleKeyGenerator 的源码中能够发现其生成规定如下(附 SimpleKey 源码):
- 如果办法没有入参,则应用 SimpleKey.EMPTY 作为 key(key = new SimpleKey())。
- 如果只有一个入参,则应用该入参作为 key(key = 入参的值)。
- 如果有多个入参,则返回蕴含所有入参的一个 SimpleKey(key = new SimpleKey(params))。
package org.springframework.cache.interceptor;import java.io.Serializable;import java.util.Arrays;import org.springframework.util.Assert;import org.springframework.util.StringUtils;public class SimpleKey implements Serializable {public static final SimpleKey EMPTY = new SimpleKey(new Object[0]); private final Object[] params; private final int hashCode; public SimpleKey(Object… elements) {
Assert.notNull(elements, “Elements must not be null”); this.params = new Object[elements.length];
System.arraycopy(elements, 0, this.params, 0, elements.length); this.hashCode = Arrays.deepHashCode(this.params);
} public boolean equals(Object other) {return this == other || other instanceof SimpleKey && Arrays.deepEquals(this.params, ((SimpleKey)other).params);
} public final int hashCode() { return this.hashCode;
} public String toString() { return this.getClass().getSimpleName() + ” [” + StringUtils.arrayToCommaDelimitedString(this.params) + “]”;
}
}
如需自定义键生成策略,能够通过实现 org.springframework.cache.interceptor.KeyGenerator 接口来定义本人理论须要的键生成器。示例如下,自定义了一个 MyKeyGenerator 类并且实现(implements)了 KeyGenerator 以实现自定义的键值生成器:
package com.example.cache.springcache;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cache.interceptor.KeyGenerator;import org.springframework.cache.interceptor.SimpleKey;import java.lang.reflect.Method;/**
* @author: 博客「成猿手册」
* @description: 为不便演示,这里自定义的键生成器只是在 SimpleKeyGenerator 根底上加了一些 logger 打印以区别自定义的 Spring 默认的键值生成器;
*/public class MyKeyGenerator implements KeyGenerator {private static final Logger logger = LoggerFactory.getLogger(MyKeyGenerator.class); @Override
public Object generate(Object o, Method method, Object… objects) {
logger.info(“ 执行自定义键生成器 ”); return generateKey(objects);
} public static Object generateKey(Object… params) {if (params.length == 0) {
logger.debug(“ 本次缓存键名称:{}”, SimpleKey.EMPTY); return SimpleKey.EMPTY;
} else {if (params.length == 1) {
Object param = params[0]; if (param != null && !param.getClass().isArray()) {
logger.debug(“ 本次缓存键名称:{}”, params); return param;
}
}
SimpleKey simpleKey = new SimpleKey(params);
logger.debug(“ 本次缓存键名称:{}”, simpleKey.toString()); return simpleKey;
}
}
}
同时在 Spring 配置文件中配置:
<!– 配置键生成器 Bean –><bean id = “myKeyGenerator” class=”com.example.cache.springcache.MyKeyGenerator” />
应用示例如下:
@Cacheable(cacheNames = “userId”,keyGenerator = “myKeyGenerator”)public User getUserById(String userId)
执行的打印后果如下:
first query…14:50:29.901 [main] INFO com.example.cache.springcache.MyKeyGenerator – 执行自定义键生成器 14:50:29.902 [main] DEBUG com.example.cache.springcache.MyKeyGenerator – 本次键名称:test00114:50:29.904 [main] INFO com.example.cache.springcache.MyKeyGenerator – 执行自定义键生成器 14:50:29.904 [main] DEBUG com.example.cache.springcache.MyKeyGenerator – 本次键名称:test001
query user by userId=test001
querying id from DB…test001
result object: com.example.cache.customize.entity.User@1a6c1270
second query…14:50:29.927 [main] INFO com.example.cache.springcache.MyKeyGenerator – 执行自定义键生成器 14:50:29.927 [main] DEBUG com.example.cache.springcache.MyKeyGenerator – 本次键名称:test001
result object: com.example.cache.customize.entity.User@1a6c1270
@CachePut
@CachePut 注解属性与 @Cacheable 注解属性相比少了 sync 属性。其余用法基本相同:
属性名
作用与形容
cacheNames/value
指定缓存的名字,缓存应用 CacheManager 治理多个缓存 Cache,这些 Cache 就是依据该属性进行辨别。对缓存的真正增删改查操作在 Cache 中定义,每个缓存 Cache 都有本人惟一的名字。
key
缓存数据时的 key 的值,默认是应用办法所有入参的值,能够应用 SpEL 表达式示意 key 的值。
keyGenerator
缓存的生成策略(键生成器),和 key 二选一,作用是生成键值 key,keyGenerator 可自定义。
cacheManager
指定缓存管理器(例如 ConcurrentHashMap、Redis 等)。
cacheResolver
和 cacheManager 作用一样,应用时二选一。
condition
指定缓存的条件(对参数判断,满足什么条件时才缓存),可用 SpEL 表达式,例如:办法入参为对象 user 则表达式能够写为 condition = “#user.age>18″,示意当入参对象 user 的属性 age 大于 18 才进行缓存。
unless
否定缓存的条件(对后果判断,满足什么条件时不缓存),即满足 unless 指定的条件时,对调用办法获取的后果不进行缓存,例如:unless = “result==null”,示意如果后果为 null 时不缓存。
如果一个办法应用了 @Cacheable 注解,当反复(n>1)调用该办法时,因为缓存机制,并未再次执行办法体,其后果间接从缓存中找到并返回,即获取还的是第一次办法执行后放进缓存中的后果。
但理论业务并不总是如此,有些状况下要求办法肯定会被调用,例如数据库数据的更新,系统日志的记录,确保缓存对象属性的实时性等等。
@CachePut 注解就确保办法调用即执行,执行后更新缓存。
示例代码清单:
package com.example.cache.springcache;import com.example.cache.customize.entity.User;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;/**
* @author: 博客「成猿手册」
* @description: com.example.cache.springcache
*/@Service(value = “userServiceBean2”)public class UserService2 {/**
* 申明缓存名称为 userCache
* 缓存键值 key 未指定默认为 userNumber+userName 组合字符串
*
* @param userId 用户 Id
* @return 返回用户对象
*/
@Cacheable(cacheNames = “userCache”) public User getUserByUserId(String userId) {// 办法外部实现不思考缓存逻辑,间接实现业务
return getFromDB(userId);
} /**
* 注解 @CachePut: 确保办法体内办法肯定执行, 执行完之后更新缓存;
* 应用与 {@link com.example.cache.springcache.UserService2#getUserByUserId(String)} 办法
* 雷同的缓存 userCache 和 key(缓存键值应用 spEl 表达式指定为 userId 字符串) 以实现对该缓存更新;
*
* @param user 用户参数
* @return 返回用户对象
*/
@CachePut(cacheNames = “userCache”, key = “(#user.userId)”) public User updateUser(User user) {return updateData(user);
} private User updateData(User user) {
System.out.println(“real updating db…” + user.getUserId()); return user;
} private User getFromDB(String userId) {
System.out.println(“querying id from db…” + userId); return new User(userId);
}
}
测试代码清单:
package com.example.cache.springcache;import com.example.cache.customize.entity.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;/**
* @author: 博客「成猿手册」
* @description: com.example.cache.springcache
*/public class UserMain2 {public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);
UserService2 userService2 = (UserService2) context.getBean(“userServiceBean2”); // 第一次查问, 缓存中没有, 从数据库查问
System.out.println(“first query…”);
User user1 = userService2.getUserByUserId(“user001”);
System.out.println(“result object: ” + user1);
user1.setAge(20);
userService2.updateUser(user1); // 调用即执行, 而后更新缓存
user1.setAge(21);
userService2.updateUser(user1);
System.out.println(“second query…”);
User user2 = userService2.getUserByUserId(“user001”);
System.out.println(“result object: ” + user2);
System.out.println(“result age: ” + user2.getAge());
}
}
测试打印后果如下:
first query…querying id from db…user001result object: com.example.cache.customize.entity.User@6d1ef78dreal updating db…user001
real updating db…user001
second query…
result object: com.example.cache.customize.entity.User@6d1ef78d
result age: 21
结果表明,执行了两次模仿调用数据库的办法。须要留神的是,在这个简略示例中,两次 setAge()办法并不可能证实的确更新了缓存:把 updateData()办法去掉也能够失去最终的用户年龄后果,因为 set 操作的依然是 getUserByName()之前获取的对象。
应该在实际操作中将 getFromDB 和 updateData 调整为更新数据库的具体方法,再通过加与不加 @CachePut 来比照最初的后果判断是否更新缓存。
@CacheEvict
@CacheEvict 注解属性一览:
属性名
作用与形容
cacheNames/value
指定缓存的名字,缓存应用 CacheManager 治理多个缓存 Cache,这些 Cache 就是依据该属性进行辨别。对缓存的真正增删改查操作在 Cache 中定义,每个缓存 Cache 都有本人惟一的名字。
key
缓存数据时的 key 的值,默认是应用办法所有入参的值,能够应用 SpEL 表达式示意 key 的值。
keyGenerator
缓存的生成策略(键生成器),和 key 二选一,作用是生成键值 key,keyGenerator 可自定义。
cacheManager
指定缓存管理器(例如 ConcurrentHashMap、Redis 等)。
cacheResolver
和 cacheManager 作用一样,应用时二选一。
condition
指定删除缓存的条件(对参数判断,满足什么条件时才删除缓存),可用 SpEL 表达式,例如:入参为字符 userId 的办法删除缓存条件设定为当入参不是 user001 就删除缓存,则表达式能够写为 condition = “!(‘user001’).equals(#userId)”。
allEntries
allEntries 是布尔类型的,用来示意是否须要革除缓存中的所有元素。默认值为 false, 示意不须要。当指定 allEntries 为 true 时,Spring Cache 将疏忽指定的 key, 革除缓存中的所有内容。
beforeInvocation
革除操作默认是在对应办法执行胜利后触发的(beforeInvocation = false),即办法如果因为抛出异样而未能胜利返回时则不会触发革除操作。应用 beforeInvocation 属性能够扭转触发革除操作的工夫。当指定该属性值为 true 时,Spring 会在调用该办法之前革除缓存中的指定元素。
@CacheEvict 注解是 @Cachable 注解的反向操作,它负责从给定的缓存中移除一个值。大多数缓存框架都提供了缓存数据的有效期,应用该注解能够显式地从缓存中删除生效的缓存数据。该注解通常用于更新或者删除用户的操作。上面的办法定义从数据库中删除 - 一个用户,而 @CacheEvict 注解也实现了雷同的工作,从 users 缓存中删除了被缓存的用户。
在下面的实例中增加删除办法:
@CacheEvict(cacheNames = “userCache”)public void delUserByUserId(String userId) {// 模仿理论业务中的删除数据操作
System.out.println(“deleting user from db…” + userId);
}
测试代码清单:
package com.example.cache.springcache;import com.example.cache.customize.entity.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;/**
* @author: 博客「成猿手册」
* @description: com.example.cache.springcache
*/public class UserMain3 {public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);
UserService2 userService2 = (UserService2) context.getBean(“userServiceBean2”);
String userId = “user001”; // 第一次查问, 缓存中没有, 执行数据库查问
System.out.println(“first query…”);
User user1 = userService2.getUserByUserId(userId);
System.out.println(“result object: ” + user1); // 第二次查问从缓存中查问
System.out.println(“second query…”);
User user2 = userService2.getUserByUserId(userId);
System.out.println(“result object: ” + user2); // 先移除缓存再查问, 缓存中没有, 执行数据库查问
userService2.delUserByUserId(userId);
User user3 = userService2.getUserByUserId(userId);
System.out.println(“result object: ” + user3);
}
}
执行的打印后果如下:
first query…
querying id from db…user001
result object: com.example.cache.customize.entity.User@6dee4f1b
second query…
result object: com.example.cache.customize.entity.User@6dee4f1b
deleting user from db…user001
querying id from db…user001
result object: com.example.cache.customize.entity.User@31bcf236
通过打印后果验证了 @CacheEvict 移除缓存的成果。须要留神的是,在雷同的办法上应用 @Caheable 和 @CacheEvict 注解并应用它们指向雷同的缓存没有任何意义,因为这相当于数据被缓存之后又被立刻移除了,所以须要防止在同一办法上同时应用这两个注解。
@Caching
@Caching 注解属性一览:
属性名
作用与形容
cacheable
取值为基于 @Cacheable 注解的数组,定义对办法返回后果进行缓存的多个缓存。
put
取值为基于 @CachePut 注解的数组,定义执行办法后,对返回方的办法后果进行更新的多个缓存。
evict
取值为基于 @CacheEvict 注解的数组。定义多个移除缓存。
总结来说,@Caching 是一个组注解,能够为一个办法定义提供基于 @Cacheable、@CacheEvict 或者 @CachePut 注解的数组。
示例定义了 User(用户)、Member(会员)和 Visitor(游客)3 个实体类,它们彼此之间有一个简略的层次结构:User 是一个抽象类,而 Member 和 Visitor 类扩大了该类。
User(用户抽象类)代码清单:
package com.example.cache.springcache.entity;/**
* @author: 博客「成猿手册」
* @description: 用户抽象类
*/public abstract class User {private String userId; private String userName; public User(String userId, String userName) {this.userId = userId; this.userName = userName;
} //todo: 此处省略 get 和 set 办法}
Member(会员类)代码清单:
package com.example.cache.springcache.entity;import java.io.Serializable;/**
* @author: 博客「成猿手册」
* @description: 会员类
*/public class Member extends User implements Serializable {public Member(String userId, String userName) {super(userId, userName);
}
}
Visitor(游客类)代码清单:
package com.example.cache.springcache.entity;import java.io.Serializable;/**
* @author: 博客「成猿手册」
* @description: 访客类
*/public class Visitor extends User implements Serializable {private String visitorName; public Visitor(String userId, String userName) {super(userId, userName);
}
}
UserService3 类是一个 Spring 服务 Bean, 蕴含了 getUser()办法。
同时申明了两个 @Cacheable 注解,并使其指向两个不同的缓存项: members 和 visitors。而后依据两个 @Cacheable 注解定义中的条件对办法的参数进行查看,并将对象存储在
members 或 visitors 缓存中。
UserService3 代码清单:
package com.example.cache.springcache;import com.example.cache.springcache.entity.Member;import com.example.cache.springcache.entity.User;import com.example.cache.springcache.entity.Visitor;import org.springframework.cache.annotation.Cacheable;import org.springframework.cache.annotation.Caching;import org.springframework.stereotype.Service;import java.util.HashMap;import java.util.Map;/**
* @author: 博客「成猿手册」
* @description: com.example.cache.springcache
*/@Service(value = “userServiceBean3”)public class UserService3 {private Map<String, User> users = new HashMap<>();
{// 初始化数据,模仿数据库中数据
users.put(“member001”, new Member(“member001”, “ 会员小张 ”));
users.put(“visitor001”, new Visitor(“visitor001”, “ 访客小曹 ”));
} @Caching(cacheable = { /*
该 condition 指定的 SpEl 表达式用来判断办法传参的类型
instanceof 是 Java 中的一个二元运算符,用来测试一个对象(援用类型)是否为一个类的实例
*/
@Cacheable(value = “members”, condition = “#user instanceof T(” + “com.example.cache.springcache.entity.Member)”), @Cacheable(value = “visitors”, condition = “#user instanceof T(” + “com.example.cache.springcache.entity.Visitor)”)
}) public User getUser(User user) {// 模仿数据库查问
System.out.println(“querying id from db…” + user.getUserId()); return users.get(user.getUserId());
}
}
UserService3 类是 - 一个 Spring 服务 Bean, 蕴含了 getUser()办法。同时申明了两个 @Cacheable 注解,并使其指向两个不同的缓存项: members 和 visitors。
而后依据两个 @Cacheable 注解定义中的条件对办法的参数进行查看,并将对象存储在
members 或 visitors 缓存中。
测试代码清单:
package com.example.cache.springcache;import com.example.cache.springcache.entity.Member;import com.example.cache.springcache.entity.User;import com.example.cache.springcache.entity.Visitor;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;/**
* @author: 博客「成猿手册」
* @description: com.example.cache.springcache
*/public class UserService3Test {public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);
UserService3 userService3 = (UserService3) context.getBean(“userServiceBean3”);
Member member = new Member(“member001”, null); // 会员第一次查问,缓存中没有,从数据库中查问
User member1 = userService3.getUser(member);
System.out.println(“member userName–>” + member1.getUserName()); // 会员第二次查问,缓存中有,从缓存中查问
User member2 = userService3.getUser(member);
System.out.println(“member userName–>” + member2.getUserName());
Visitor visitor = new Visitor(“visitor001”, null); // 游客第一次查问,缓存中没有,从数据库中查问
User visitor1 = userService3.getUser(visitor);
System.out.println(“visitor userName–>” + visitor1.getUserName()); // 游客第二次查问,缓存中有,从缓存中查问
User visitor2 = userService3.getUser(visitor);
System.out.println(“visitor userName–>” + visitor2.getUserName());
}
}
执行的打印后果如下:
querying id from db…member001
member userName–> 会员小张
member userName–> 会员小张
querying id from db…visitor001
visitor userName–> 访客小曹
visitor userName–> 访客小曹
@CacheConfig
@CacheConfig 注解属性一览:
属性名
作用与形容
cacheNames/value
指定类级别缓存的名字,缓存应用 CacheManager 治理多个缓存 Cache,这些 Cache 就是依据该属性进行辨别。对缓存的真正增删改查操作在 Cache 中定义,每个缓存 Cache 都有本人惟一的名字。
keyGenerator
类级别缓存的生成策略(键生成器),和 key 二选一,作用是生成键值 key,keyGenerator 可自定义。
cacheManager
指定类级别缓存管理器(例如 ConcurrentHashMap、Redis 等)。
cacheResolver
和 cacheManager 作用一样,应用时二选一。
后面咱们所介绍的注解都是基于办法的,如果在同一个类中须要缓存的办法注解属性都类似,则须要反复减少。Spring 4.0 之后减少了 @CacheConfig 类级别的注解来解决这个问题。
一个简略的实例如下所示:
package com.example.cache.springcache;import com.example.cache.springcache.entity.User;import org.springframework.cache.annotation.CacheConfig;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;/**
* @author: 博客「成猿手册」
* @description: com.example.cache.springcache
*/@CacheConfig(cacheNames = “users”,keyGenerator = “myKeyGenerator”)public class UserService4 {@Cacheable
public User findA(User user){//todo: 执行一些操作
}
@CachePut
public User findB(User user){//todo: 执行一些操作
}
}
能够看到,在 @CacheConfig 注解中定义了类级别的缓存 users 和自定义键生成器,
那么在 findA0 和 findB(办法中不再须要反复指定,而是默认应用类级别的定义。