一,Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
-
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
-
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
-
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
-
使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
二,几个重要概念&缓存注解
名称 | 解释 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都调用方法,常用于更新 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
@CacheConfig | 统一配置本类的缓存注解的属性 |
@Cacheable/@CachePut/@CacheEvict 主要的参数
名称 | 解释 |
---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#id”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存/清除缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
unless | 否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”) |
allEntries (@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation (@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
三,SpEL上下文数据
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root对象 | 当前被调用的方法名 | #root.methodname |
method | root对象 | 当前被调用的方法 | #root.method.name |
target | root对象 | 当前被调用的目标对象实例 | #root.target |
targetClass | root对象 | 当前被调用的目标对象的类 | #root.targetClass |
args | root对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root对象 | 当前方法调用使用的缓存列表 | #root.caches[0].name |
Argument Name | 执行上下文 | 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 | #artsian.id |
result | 执行上下文 | 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) | #result |
注意:
1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如
@Cacheable(key = "targetClass + methodName +#p0")
2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:
@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")
SpEL提供了多种运算符
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&,||,!,and,or,not,between,instanceof |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],^[…],$[…] |
四,开始使用
1,项目结构
2,pom以及源码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.g7go</groupId>
<artifactId>spring-boot-starter-cache-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-starter-cache-demo</name>
<description>缓存</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
User.class
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private String id;
private String name;
private String address;
private String tel;
private Integer age;
}
UserService.class
public interface UserService {
/**
* 获取用户
*/
User findUser();
/**
* 更新用户信息
*/
void updateUser();
/**
* 清除缓存的用户信息
*/
void clearUser();
}
UserServiceImpl.class
@Service
@CacheConfig(cacheNames = "CacheConfigName")
public class UserServiceImpl implements UserService {
/**
* Cacheable[] cacheable() default {}; //声明多个@Cacheable
* CachePut[] put() default {}; //声明多个@CachePut
* CacheEvict[] evict() default {}; //声明多个@CacheEvict
* 插入用户
*/
@Caching(
put = {
@CachePut(value = "valueName", key = "#user.id"),
@CachePut(value = "valueName", key = "#user.name"),
@CachePut(value = "valueName", key = "#user.address")
}
)
@Override
public User saveUser(User user) {
System.out.println("插入用户...");
return user;
}
/**
* --@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存
* 命名空间:@Cacheable的value会替换@CacheConfig的cacheNames(两者必须有一个)
* --key是[命名空间]::[@Cacheable的key或者KeyGenerator生成的key](@Cacheable的key优先级高,KeyGenerator不配置走默认KeyGenerator SimpleKey [])
*/
@Override
@Cacheable(value = {"valueName", "valueName2"}, key = "'keyName1'")
public User findUser() {
System.out.println("执行方法...");
return new User("id1", "张三", "沧州", "123456", 22);
}
/**
* --@CachePut注解的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,
* 和 @Cacheable 不同的是,它每次都会触发真实方法的调用
* 简单来说就是用户更新缓存数据。但需要注意的是该注解的value 和 key 必须与要更新的缓存相同,也就是与@Cacheable 相同
* 默认先执行数据库更新再执行缓存更新
* 注意返回值必须是要修改后的数据
*/
@Override
@CachePut(value = "valueName", key = "'keyName1'")
public User updateUser() {
System.out.println("更新用户...");
return new User("id1", "李四", "北京", "123456", 22);
}
/**
* --@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空
* 触发缓存清除
* 默认先执行数据库删除再执行缓存删除
*/
@Override
@CacheEvict(value = "valueName", allEntries = true)
public void clearUser() {
System.out.println("清除缓存...");
}
}
CacheConfig.class
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Resource
private RedisConnectionFactory factory;
/**
* 自定义生成redis-key
*/
@Override
public KeyGenerator keyGenerator() {
return (o, method, objects) -> {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName()).append(".");
sb.append(method.getName()).append(".");
for (Object obj : objects) {
sb.append(obj.toString());
}
return sb.toString();
};
}
@Bean
public RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
}
@Override
public CacheResolver cacheResolver() {
return new SimpleCacheResolver(cacheManager());
}
@Override
public CacheErrorHandler errorHandler() {
// 用于捕获从Cache中进行CRUD时的异常的回调处理器。
return new SimpleCacheErrorHandler();
}
@Override
public CacheManager cacheManager() {
RedisCacheConfiguration cacheConfiguration =
defaultCacheConfig()
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
}
}
SpringBootStarterCacheDemoApplicationTests.class
@EnableCaching
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootStarterCacheDemoApplicationTests {
@Resource
private UserService userService;
@Test
public void saveUserTest() {
userService.saveUser(new User("id1", "赵六", "衡水", "123456", 22));
}
@Test
public void findUserTest() {
for (int i = 0; i < 3; i++) {
System.out.println("第" + i + "次");
User user = userService.findUser();
System.out.println(user);
}
}
@Test
public void updateUserTest() {
userService.updateUser();
User user = userService.findUser();
System.out.println(user);
}
@Test
public void clearUserTest() {
userService.clearUser();
User user = userService.findUser();
System.out.println(user);
}
}
五,测试
略