在 Java 中,通常對調用方法進行緩存控制,好比調用 findUserById(id)
,那麼應該在調用此方法以前先從緩存中查找,若是沒有再掉該方法如從數據庫中加載,接着添加到緩存中,下次調用時將會從緩存中獲取到數據。php
自 Spring 3.1 起,提供了相似於 @Transactional
註解事務的註解 Cache 支持,且提供了 Cache 抽象;使用Spring Cache 的好處:html
對於 Spring Cache 抽象,主要從如下幾個方面學習:java
本文所使用的框架版本:redis
<!-- 須要緩存的對象 -->
<bean id="bookService" class="com.ariclee.cache.xml.XmlBookService"/>
<!-- 聲明緩存 -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager" >
<cache:caching cache="books">
<!-- 讀取緩存: 應用到讀取數據的方法上,便可緩存的方法,如查找方法, 先從緩存中讀取,若是沒有再調用方法獲取數據, 而後把數據添加到緩存中 -->
<cache:cacheable method="findBook" key="#id"/>
<!-- 更新緩存: 應用到寫數據的方法上,如新增/修改方法 調用方法時會自動把相應的數據放入緩存 -->
<cache:cache-put method="saveBook" key="#book.id"/>
<!-- 刪除單個緩存: 應用到移除數據的方法上,如刪除方法 調用方法時會從緩存中移除相應的數據 -->
<cache:cache-evict method="delete" key="id"/>
<!-- 清空緩存:同上,注意屬性 all-entries 默認爲 false -->
<cache:cache-evict method="deleteAll" all-entries="true"/>
</cache:caching>
</cache:advice>
<!-- 緩存切面 -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.ariclee.cache.xml.XmlBookService.*(..))"/>
</aop:config>
<!-- 方式一:註冊緩存管理(JVM 緩存) -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
</set>
</property>
</bean>
<!-- 方式二:註冊緩存管理(JVM 緩存) -->
<bean id="cacheManager2" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager">
<constructor-arg value="books"/>
</bean>
複製代碼
聲明須要緩存的類,聲明緩存並配置,並使用名爲 books 的緩存,聲明方法 findBook
須要緩存,並使用 id 爲鍵。註冊 cacheManager 類,並使用 JVM 內存做爲實際緩存。spring
使用 xml 開啓包掃描數據庫
<mvc:annotation-driven/>
<context:component-scan base-package="com.ariclee.cache.annotation" >
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<context:component-scan base-package="com.ariclee.cache.annotation" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
複製代碼
使用 @Cacheable
註解,做用在 findBook 方法上,value
屬性值賦爲 books 意爲緩存的名字。express
@Component
public class AnnotationBookService {
private static Map<String, Book> respository = new HashMap<>();
static {
respository.put("1", new Book("Thinking In Java"));
respository.put("2", new Book("EfficetiveJava"));
}
// 同 xml
@Cacheable(value = "books")
public Book findBook(String id) {
return respository.get(id);
}
// 同 xml
@CachePut(value = "books", key = "#book.id")
public void saveBook(Book book) {
respository.put(book.getId(), book);
}
// 同 xml
@CacheEvict(value = "books", key = "id", allEntries = false)
public void delete(String id) {
respository.remove(id);
}
// 同 xml
@CacheEvict(value = "books", allEntries = true)
public void deleteAll() {
respository.clear();
}
}
複製代碼
使用註解和類配置緩存管理類,ConcurrentMapCacheManager
爲 spring 提供的在內存中管理緩存的簡單類。緩存
@EnableCaching
@Configuration
public class JvmAnnotationCacheConfigure implements CachingConfigurer {
@Bean
@Override
public CacheManager cacheManager() {
// 使用 JVM 內存
return new ConcurrentMapCacheManager("books");
}
// 省略...
}
複製代碼
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
複製代碼
使用 @EnableCaching
註解開啓緩存, yml 配置文件以下:mvc
spring:
cache:
type: simple
cache-names: books
複製代碼
其中 type 值爲 org.springframework.boot.autoconfigure.cache.CacheType
類屬性。app
須要引入 Redis 客戶端依賴,pom:
<!-- redis client -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.5.2</version>
</dependency>
複製代碼
application.xml
<!-- 註冊緩存管理(Redis) -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="127.0.0.1"/>
<property name="port" value="6379"/>
<property name="password" value="2940184"/>
<property name="timeout" value="2000"/>
</bean>
<!-- 聲明 RedisTemplate 類-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!-- 聲明緩存管理類 -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg ref="redisTemplate" />
<constructor-arg value="books" />
</bean>
複製代碼
聲明 Redis 鏈接工廠和 RedisTemplate。聲明緩存管理類,注入 redisTemplate,並設置緩存名爲 books。
建立 Redis 配置類
@Configuration
public class RedisConfigure {
// 聲明 redis 連接工廠類
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName("127.0.0.1");
factory.setPassword("123456");
factory.setPort(6379);
factory.setTimeout(2000);
factory.setUsePool(false);
factory.setPoolConfig(new JedisPoolConfig());
factory.setDatabase(1);
factory.setConvertPipelineAndTxResults(false);
return factory;
}
// 聲明 redis 操做模板類
@Bean
public RedisTemplate redisTemplate() {
RedisTemplate temp = new RedisTemplate();
temp.setConnectionFactory(this.jedisConnectionFactory());
return temp;
}
}
複製代碼
建立 Cache 配置類
@EnableCaching
@Configuration
public class RedisAnnotationCacheConfigure implements CachingConfigurer {
@Autowired
RedisTemplate redisTemplate;
@Bean
@Override
public CacheManager cacheManager() {
// 使用 Redis
return new RedisCacheManager(redisTemplate,
Collections.singletonList("books"));
}
// 省略...
}
複製代碼
spring:
cache:
cache-names: books
type: redis
redis:
use-key-prefix: true
redis:
host: 127.0.0.1
port: 6379
password: 2940184
timeout: 2000s
database: 1
複製代碼
須要引入 ehcache 緩存依賴,pom:
<!-- ehcache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.8</version>
</dependency>
複製代碼
application.xml
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
</bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcache"/>
</bean>
複製代碼
ehcache.xml(從 www.ehcache.org/ehcache.xml 拷貝)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">
<defaultCache maxEntriesLocalHeap="0" eternal="false" timeToIdleSeconds="1200" timeToLiveSeconds="1200">
<!--<terracotta/>-->
</defaultCache>
<cache name="books" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
複製代碼
@EnableCaching
@Configuration
public class EhCacheAnnotationCacheConfigure implements CachingConfigurer {
@Bean
@Override
public CacheManager cacheManager() {
EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
ClassPathResource resource = new ClassPathResource("ehcache.xml");
factoryBean.setConfigLocation(resource);
try {
factoryBean.afterPropertiesSet();
} catch (Exception e){
e.printStackTrace();
}
EhCacheCacheManager manager = new EhCacheCacheManager();
manager.setCacheManager(factoryBean.getObject());
return manager;
}
// 省略
}
複製代碼
pom:
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
複製代碼
在 application.yml 中將 type 修改成 ehcache,並將 ehcache.xml(同上)放到 resources 文件夾下:
spring:
cache:
cache-names: books
type: ehcache
ehcache:
config: classpath:ehcache.xml
複製代碼
xml 方式:
<!-- 只緩存參數名中包含 Java -->
<cache:cacheable method="findBookByName" condition="#name.contains('Java')" key="#name"/>
<!-- 不緩存結果集中帶有 default -->
<cache:cacheable method="findBookByName" unless="#result.name.contains('default')" key="#name"/>
複製代碼
註解方式:
// 同 xml
@Cacheable(value = "books", condition = "#name.contains('Java')")
public Book findBookByName(String name) {}
// 同 xml
@Cacheable(value = "books", unless = "#result.name.contains('default')")
public Book findBookByName(String name) {}
複製代碼
condition
屬性搭配在 Cacheable
註解時,表示 」當 condition 爲 true 讀緩存「,做用於入參,unless
屬性表示 "當 unless 爲 true 不寫緩存",做用於返回值。注意加以區別!屬性 condition
和 unless
還能夠做用於 CachePut
註解:
// 當 id 爲 10 時,寫入緩存
@CachePut(value = "books", key = "#book.id", condition = "#book.id == 10")
public void saveBook(Book book) {}
// 當 id 爲 10 時,不寫入緩存
@CachePut(value = "books", key = "#book.id", unless = "#book.id == 10")
public void saveBook(Book book) {}
複製代碼
好比新增書籍成功後,咱們要添加 id:book;name:book;的緩存鍵值對。此時就須要組合多個註解標籤:
xml 方式:
<cache:caching method="saveAndAddAllCacheMapping" cache="books">
<cache:cache-put key="#book.id"/>
<cache:cache-put key="#book.name"/>
</cache:caching>
複製代碼
註解方式:
@Caching(
put = {
@CachePut(value = "books", key = "#book.id"),
@CachePut(value = "books", key = "#book.name"),
}
)
public Book saveBook(Book book) {}
複製代碼
再如根據書籍名字查找時,在第一次放入緩存的時候,將 id:book 的緩存映射也加進去,則能夠經過如此組合註解實現:
@Caching(
cacheable = {
@Cacheable(value = "books", key = "#name")
},
put = {
@CachePut(value = "books", key = "#result.id",
@CachePut(value = "books", key = "#result.name")
}
)
public Book findBookByName(String name) {}
複製代碼
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root對象 | 當前被調用的方法名 | #root.methodName |
method | root對象 | 當前被調用的方法 | #root.method.name |
target | root對象 | 當前被調用的目標對象 | #root.target |
targetClass | root對象 | 當前被調用的目標對象類 | #root.targetClass |
args | root對象 | 當前被調用的方法的參數列表 | #root.args[0] |
caches | root對象 | 當前方法調用使用的緩存列表(如 @Cacheable(value={"cache1", "cache2"})),則有兩個 cache | #root.caches[0].name |
argument name | 執行上下文 | 當前被調用的方法的參數,如 findById(Long id) ,咱們能夠經過 #id 拿到參數 | #user.id |
result | 執行上下文 | 方法執行後的返回值(僅當方法執行以後的判斷有效,如‘unless’,'cache evict'的beforeInvocation=false) | #result |
Directly through your cache provider. The cache abstraction is… well, an abstraction not a cache implementation.
Spring Cache 是抽象框架沒有提供過時控制的功能,過時實現交給緩存功能提供者。如 org.springframework.data.redis.cache.RedisCacheManager
類提供 Map<String, Long> expires
屬性能夠設置指定名字的緩存過時時間。
須要實現 org.springframework.cache.interceptor.KeyGenerator
接口,複寫 generate
方法。如:
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
String key = this.generateKey(params);
System.out.println("生成緩存鍵:" + key);
return key;
}
private String generateKey(Object[] params) {
if (params == null || params.length < 1) {
return "";
}
StringJoiner stringJoiner = new StringJoiner(":");
for (Object obj : params) {
if (obj instanceof Book) {
Book temp = (Book) obj;
stringJoiner.add(temp.getId() + ":" + temp.getName());
}
else {
stringJoiner.add(obj.toString());
}
}
return stringJoiner.toString();
}
}
複製代碼
使用組合註解
@Caching(
put = {
@CachePut(value = "books", key = "#book.id"),
@CachePut(value = "books", key = "#book.name"),
}
)
public Book saveBook(Book book) {}
複製代碼
聲明一個組合註解
@Caching(
put = {
@CachePut(value = "books", key = "#book.id"),
@CachePut(value = "books", key = "#book.name"),
}
)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BookCaching {
}
複製代碼
優化後使用
@BookCaching
public Book saveBook(Book book) {}
複製代碼
參見 org.springframework.cache.concurrent.ConcurrentMapCacheManager
。