<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
spring.cache.type=Simple
@EnableCaching
注解来开启缓存注解。//开启缓存注解
@EnableCaching
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
}
import com.example.demo.bean.User;
import com.example.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
@CacheConfig(cacheNames = {"user"})
//统一指定value的值,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。
public class UserServiceImpl implements UserService {
private static final Map<Integer, User> users = new HashMap<>(3);
static {
users.put(1, new User(1,"bym"));
users.put(2, new User( 2,"wx"));
users.put(3, new User( 3,"sqp"));
}
@Override
//@Cacheable(key = "targetClass + methodName +#p0")
//@Cacheable(value="users", key="#p0")
//@Cacheable(key = "#id")
@Cacheable
public User getUser(int id) {
log.info("缓存中没有,从数据中取...");
return users.get(id);
}
/**
* 在更新数据的同时,缓存也被更新
* @param user
* @return
*/
@Override
@CachePut(cacheNames = "space", key = "#user.id")
public User updateUser(User user) {
log.info("更新数据...");
users.put(user.getId(), user);
return user;
}
//清除一条缓存,key为要清空的数据
@CacheEvict(value = "space", key = "#id")
public void delete(int id) {
users.remove(id);
}
//方法调用后清空所有缓存
@CacheEvict(value = "space", allEntries = true)
public void deleteAll() {
users.clear();
}
//方法调用前清空所有缓存
@CacheEvict(value = "space", beforeInvocation = true)
public void deleteAllBefore() {
users.clear();
}
/**
* @Caching是 @Cacheable、@CachePut、@CacheEvict注解的组合
* 以下注解的含义:
* 1.当使用指定名字查询数据库后,数据保存到缓存
* 2.现在使用id、age就会直接查询缓存,而不是查询数据库
*/
}
注意:这里的User实体类要实现Serializable序列化接口。
public class User implements Serializable
,否则会报异常。
@Cacheable
作用在方法上,声明了方法的结果是可缓存的。
- 如果缓存存在,则目标方法不会被调用,直接取出缓存。可以为方法声明多个缓存,如果至少有一个缓存有缓存项,则其缓存项将被返回。
- 如果缓存不存在,则进入实际业务方法,将业务方法返回的结果缓存下来。
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";//key的生成器。key/keyGenerator二选一使用
String cacheManager() default "";//指定缓存管理器
String cacheResolver() default "";//或者指定获取解析器
String condition() default "";//条件符合则缓存
String unless() default "";//条件符合则不缓存
boolean sync() default false; //是否使用异步模式
cacheNames
和value
是一个意思,都表示指定缓存的命名空间(或名称),注解中必须指定命名空间。(本文指定用cacheNames)@Cacheable
@Cacheable("user")
@Cacheable({"user","info"})
@Cacheable(cacheNames="user",key="xx")
@Cacheable(cacheNames={"user","info"},key="xx")
key
指缓存的key。KeyGenerator的规则:
1.如果方法只有一个参数,这个参数就是Key。
2.如果没有参数,则Key是SimpleKey.EMPTY。
3.如果有多个Key,则返回包含多个参数的SimpleKey。
Spring使用SimpleKeyGenerator来实现上述key的生成。
源码:
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
public class SimpleKeyGenerator implements KeyGenerator {
public SimpleKeyGenerator() {
}
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
} else {
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
}
我们还可以实现自己的KeyGenerator方法:
@Cacheable(cacheNames="users",keyGenerator="myKg)
public User getUser(int id) {...}
myKg实现了KeyGenerator接口,然后从user中获取到id作为缓存的Key。
@Override
public Object generate(Object target, Method method, Object... params) {
User user = (User)params[0];
return user.getId();
}
通常情况下,直接使用SpEL表达式来指定Key比自定义KeyGenerator更简单。
2)可按照SpEL表达式编写。
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
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")
public User method1(int id) {...}
@Cacheable(value="users", key="#p0")
public User method2(int id,String name) {...}
@Cacheable(value="users", key="#user.id")
public User method3(User user) {...}
3.SpEL提供了多种运算符
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&, |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],$[…] |
@CachePut
作用在方法上,总是会执行方法体,使用方法体返回的结果更新缓存。属性和Cacheable一样。需要注意的是该注解的value 和 key 必须与要更新的缓存相同,也就是与@Cacheable 相同。
@CacheEvict
作用在方法上,能够根据一定的条件删除缓存项或清空缓存。
这里需要注意两个属性:
属性 | 解释 | 示例 |
---|---|---|
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | @CachEvict(value=”testcache”,beforeInvocation=true) |
注意:CacheEvict只具备删除缓存功能,不具备加载缓存功能,只有相应的@Cacheable方法被调用后,才会加载最新缓存项。
缓存系统中不经常改变的业务数据一旦发生改变时,通常会更新此业务相关的所有缓存 。
//方法调用后清空所有缓存
@CacheEvict(value = {"menu","menuTree"}, allEntries = true)
public void addMenu(Menu menu) {
users.clear();
}
在更新菜单后,菜单树和菜单缓存都需要清空以便下次获取的时候重新构造。
@Caching
作用在方法上,可以混合以上三种注解,比如一个修改同时需要失效对应的用户缓存和用户扩展信息缓存。
@Caching(evict = {
@CacheEvict(value = "user",key = "#user.id"),
@CacheEvict(value = "userExt",key = "#ext.id")
})
public void updateUser(User user,UserExt ext) {...}
到目前为止,所有的Cache注解都需要提供Cache名称,如果每个Service方法上都包含一个Cache名称,可能写起来重复,注解@CacheConfig
作用于类上,可以为此类的方法的缓存注解提供默认值,包括缓存的默认名称和KeyGenerator。
用法:
@CacheConfig("users")
等。
这时可省略方法上的value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
spring.cache.type=Redis
spring.redis.host=192.168.2.185
spring.redis.port=6379
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
其注解使用方式与Simple方式一致。
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class CacheConfig extends CachingConfigurerSupport {
//缓存管理器
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 生成一个默认配置,通过config对象即可对缓存进行自定义配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
//此配置可以使redis的值不为乱码。
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
// 设置缓存的默认过期时间,也是使用Duration设置
// config = config.entryTtl(Duration.ofMinutes(10))
// .disableCachingNullValues(); // 不缓存空值
// 设置一个初始化的缓存空间set集合
Set<String> cacheNames = new HashSet<>();
cacheNames.add("space");
cacheNames.add("user");
// 对每个缓存空间应用不同的配置
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
// 通过Duration可以自己实现以什么时间为单位
configMap.put("space", config.entryTtl(Duration.ofMinutes(1)));
configMap.put("user", config.entryTtl(Duration.ofSeconds(10)));
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(factory))
.initialCacheNames(cacheNames)
// 注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
.withInitialCacheConfigurations(configMap)
.cacheDefaults(config).build();
}
// 修改redis默认的缓存序列化策略(jdk序列化),
// 这里我们注入了一个RedisTemplate 设置了里面的序列化,然后呢把他注入到redisCacheManger里就可以了。
// @Bean
// public RedisTemplate
// RedisTemplate
// template.setConnectionFactory(connectionFactory);
// //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
// Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
// ObjectMapper mapper = new ObjectMapper();
// mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// serializer.setObjectMapper(mapper);
// template.setValueSerializer(serializer);
// //使用StringRedisSerializer来序列化和反序列化redis的key值
// template.setKeySerializer(new StringRedisSerializer());
// template.afterPropertiesSet();
// return template;
// }
}
ehcache 对分布式支持不够好,多个节点不能同步,通常和redis一块使用
ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。
redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,
处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。
ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。
ehcache3.x与2.x的差距还是非常大的,主要区别在于3.x后使用了java的缓存规范JSR107!!!
<dependency>
<groupId>javax.cachegroupId>
<artifactId>cache-apiartifactId>
dependency>
<dependency>
<groupId>org.ehcachegroupId>
<artifactId>ehcacheartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
config: classpath:/ehcache.xml
如果在这个目录下这个配置可以不用写,但ehcache.xml
必须有。spring:
cache:
type: jcache
jcache:
config: classpath:/cache/ehcache.xml
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
<cache-template name="heap-cache">
<resources>
<heap unit="entries">2000heap>
<offheap unit="MB">100offheap>
resources>
cache-template>
<cache alias="myuser" uses-template="heap-cache">
<expiry>
<ttl unit="seconds">40ttl>
expiry>
cache>
config>
然后使用的时候@CacheConfig(cacheNames = {"myuser"})
中的cacheNames
的名字,xml中的alias必须也有,不然会报找不到缓存名。
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
config: classpath:/ehcache.xml
如果在这个目录下这个配置可以不用写,但ehcache.xml
必须有。spring:
cache:
type: ehcache
ehcache:
config: classpath:/cache/ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir" />
<defaultCache maxElementsInMemory="10000" eternal="false"
timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="true" />
<cache name="myCache" maxElementsInMemory="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="600" overflowToDisk="true" />
ehcache>
同样,使用的时候@CacheConfig(cacheNames = {"myuser"})
中的cacheNames
的名字,xml中的alias必须也有,不然会报找不到缓存名。
Simple方式与Redis方式
《Spring Boot 2 精髓——从构建小系统到架构分布式大系统》
一起来学SpringBoot(十)缓存的使用
Spring Boot2.0+Redis+Ehcache实现二级缓存
spring boot + spring cache 实现两级缓存(redis + ehcache)
Spring Boot 使用Redis和Ehcache结合zookeeper实现二级缓存