Caffeine是使用Java8对Guava缓存的重写版本,在Spring Boot 2.0中将取代Guava。如果出现Caffeine,CaffeineCacheManager将会自动配置。使用spring.cache.cache-names属性可以在启动时创建缓存,并可以通过以下配置进行自定义(按顺序):
- spring.cache.caffeine.spec: 定义的特殊缓存
- com.github.benmanes.caffeine.cache.CaffeineSpec: bean定义
- com.github.benmanes.caffeine.cache.Caffeine: bean定义
例如,以下配置创建一个foo和bar缓存,最大数量为500,存活时间为10分钟:
spring.cache.cache-names=foo,bar
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
除此之外,如果定义了com.github.benmanes.caffeine.cache.CacheLoader,它会自动关联到CaffeineCacheManager。由于该CacheLoader将关联被该缓存管理器管理的所有缓存,所以它必须定义为CacheLoader<Object, Object>,自动配置将忽略所有泛型类型。
1.引入依赖
<properties> <caffeine.cache.version>2.7.0</caffeine.cache.version> </properties> <dependencies> <!-- Spring boot Cache--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!--for caffeine cache--> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>${caffeine.cache.version}</version> </dependency> </dependencies>
2.configuration
1 @EnableCaching 2 @Configuration 3 public class CaffeineCacheConfig { 4 public static final Integer CAFFEINE_MAXSIZE = PropertyUtil.getInt("caffeine.maxsize", "10000"); 5 public static final Integer CAFFEINE_EXPIRE_TIME = PropertyUtil.getInt("caffeine.expire.time", "3"); 6 7 /** 8 * 创建基于Caffeine的Cache Manager 9 * @return 10 */ 11 @Bean("caffeineCacheManager") 12 public CacheManager CaffeineCacheManager() { 13 CaffeineCacheManager cacheManager = new CaffeineCacheManager(); 14 15 cacheManager.setCaffeine(Caffeine.newBuilder().recordStats() 16 .expireAfterWrite(CAFFEINE_EXPIRE_TIME, TimeUnit.SECONDS) 17 .maximumSize(CAFFEINE_MAXSIZE)); 18 19 return cacheManager; 20 } 21 22 }
使用@EnableCaching注解让Spring Boot开启对缓存的支持
Caffeine配置说明:
- initialCapacity=[integer]: 初始的缓存空间大小
- maximumSize=[long]: 缓存的最大条数
- maximumWeight=[long]: 缓存的最大权重
- expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
- expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
- refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
- weakKeys: 打开key的弱引用
- weakValues:打开value的弱引用
- softValues:打开value的软引用
- recordStats:开发统计功能
注意:
- expireAfterWrite和expireAfterAccess同事存在时,以expireAfterWrite为准。
- maximumSize和maximumWeight不可以同时使用
- weakValues和softValues不可以同时使用
3.service
1 @Service("caffeineCacheService") 2 public class CaffeineCacheServiceImpl { 3 @Autowired 4 CacheManager caffeineCacheManager; 5 6 private final static String DEFAULT_CACHE = "default"; 7 8 public <T> T getValue(Object key) { 9 if(key == null) return null; 10 11 Cache cache = caffeineCacheManager.getCache(DEFAULT_CACHE); 12 if(cache != null) { 13 Cache.ValueWrapper wrapper = cache.get(key); 14 if (wrapper != null) 15 return (T) wrapper.get(); 16 } 17 18 return null; 19 } 20 21 public <T> T getValue(String cacheName, Object key) { 22 if(cacheName == null || key == null) return null; 23 24 Cache cache = caffeineCacheManager.getCache(cacheName); 25 if(cache != null) { 26 Cache.ValueWrapper wrapper = cache.get(key); 27 if (wrapper != null) 28 return (T) wrapper.get(); 29 } 30 31 return null; 32 } 33 34 public void putValue(Object key, Object value) { 35 if(key == null || value == null) return; 36 37 Cache cache = caffeineCacheManager.getCache(DEFAULT_CACHE); 38 if(cache != null) { 39 cache.put(key, value); 40 } 41 } 42 43 public void putValue(String cacheName, Object key, Object value) { 44 if(cacheName == null || key == null || value == null) return; 45 46 Cache cache = caffeineCacheManager.getCache(cacheName); 47 if(cache != null) { 48 cache.put(key, value); 49 } 50 } 51 }
其中get(key)只是返回了ValueWrapper,具体value需要get方法。我看了caffeineCacheManager.getCache方法,按理说在cachemap中找不到cache的时候会新建一个cache并放入map中再返回,但是看了源码方法上标注了@Nullable,为了代码严谨,选择了判断null。
4.实例
1 private static Integer uuid = 0; 2 @Cacheable(value = DEFAULT_CACHE, key = "#pin") 3 public Integer getUUid(String pin) { 4 /* 5 if(getValue(pin) != null) { 6 return getValue(pin); 7 }*/ 8 9 return uuid++; 10 }
附:spring cache相关注解介绍 @Cacheable、@CachePut、@CacheEvict
@Cacheable
@Cacheable是用来声明方法是可缓存的。将结果存储到缓存中以便后续使用相同参数调用时不需执行实际的方法。直接从缓存中取值。最简单的格式需要制定缓存名称。
例如:
1 @Cacheable("books") 2 public Book findBook(ISBN isbn) {...}
在上面的代码片段中,findBook方法与名为books的缓存想关联。每次调用该方法时,将在缓存中检查该请求是否已执行,以免重复执行。虽然在大多数情况下,只有一个缓存被声明,注释允许指定多个名称,以便使用多个缓存。这种情况下,在执行方法之前,每个缓存都会检查之前执行的方法,只要有一个缓存命中,即直接从缓存中返回相关的值。
即使没有实际执行缓存方法,所有其他不包含该值的缓存也将被更新。
例如:
1 @Cacheable({"books", "isbns"}) 2 public Book findBook(ISBN isbn) {...}
默认key生成:
默认key的生成按照以下规则:
- 如果没有参数,则使用0作为key
- 如果只有一个参数,使用该参数作为key
- 如果又多个参数,使用包含所有参数的hashCode作为key
自定义key的生成:
当目标方法参数有多个时,有些参数并不适合缓存逻辑
比如:
1 @Cacheable("books") 2 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
其中checkWarehouse,includeUsed并不适合当做缓存的key.针对这种情况,Cacheable 允许指定生成key的关键属性,并且支持支持SpringEL表达式。(推荐方法)
再看一些例子:
1 @Cacheable(cacheNames="books", key="#isbn") 2 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 3 4 @Cacheable(cacheNames="books", key="#isbn.rawNumber") 5 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 6 7 @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") 8 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 9 10 @Cacheable(cacheNames="books", key="#map['bookid'].toString()") 11 public Book findBook(Map<String, Object> map)
缓存的同步 sync:
在多线程环境下,某些操作可能使用相同参数同步调用。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中。
例:
1 @Cacheable(cacheNames="foos", sync="true") 2 public Foo executeExpensiveOperation(String id) {...}
属性condition:
有时候,一个方法可能不适合一直缓存(例如:可能依赖于给定的参数)。属性condition支持这种功能,通过SpEL 表达式来指定可求值的boolean值,为true才会缓存(在方法执行之前进行评估)。
例:
1 @Cacheable(cacheNames="book", condition="#name.length < 32") 2 public Book findBook(String name)
此外,还有一个unless 属性可以用来是决定是否添加到缓存。与condition不同的是,unless表达式是在方法调用之后进行评估的。如果返回false,才放入缓存(与condition相反)。 #result指返回值 例:
1 @Cacheable(cacheNames="book", condition="#name.length < 32", unless="#result.name.length > 5"") 2 public Book findBook(String name)
@CachePut
如果缓存需要更新,且不干扰方法的执行,可以使用注解@CachePut。@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
1 @CachePut(cacheNames="book", key="#isbn") 2 public Book updateBook(ISBN isbn, BookDescriptor descriptor)
注意:应该避免@CachePut 和 @Cacheable同时使用的情况。
@CacheEvict
spring cache不仅支持将数据缓存,还支持将缓存数据删除。此过程经常用于从缓存中清除过期或未使用的数据。
@CacheEvict要求指定一个或多个缓存,使之都受影响。此外,还提供了一个额外的参数allEntries 。表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素。
1 @CacheEvict(cacheNames="books", allEntries=true) 2 public void loadBooks(InputStream batch)
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
1 @CacheEvict(cacheNames="books", beforeInvocation=true) 2 public void loadBooks(InputStream batch)
@CacheConfig
有时候一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用@CacheConfig
1 @CacheConfig("books") 2 public class BookRepositoryImpl implements BookRepository { 3 4 @Cacheable 5 public Book findBook(ISBN isbn) {...} 6 }
@CacheConfig是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager 和CacheResolver。
该操作会被覆盖。
开启缓存注解
java类配置:
1 @Configuration 2 @EnableCaching 3 public class AppConfig { 4 }
XML 配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> </beans>