Since version 3.1, Spring Framework provides support for transparently adding caching into an existing Spring application.
Similar to the transaction support, the caching abstraction allows
consistent use of various caching solutions with minimal impact on the
code.
As from Spring 4.1, the cache abstraction has been significantly
improved with the support of JSR-107 annotations and more
customization options.
意思就是说从3.1开始Spring提供透明的缓存机制,Spring的缓存侵入性小,从4.1开始支持JSR-107注解,还可以定制。
让我们进入正题。
当缓存被使用的时候,执行java方法时会首先缓存中检查执行的方法是否被执行过,如果执行过,就返回缓存中的数据;如果没有,就去执行方法,并且把结果缓存起来,下一次执行方法的时候直接返回缓存中的数据。
翻译自Spring官方文档,英文水平有限,请包涵。
对于相同的输入,不论执行多少次方法总会返回同样的输出结果。
Spring的缓存服务是抽象的,这种抽象将开发人员从编写缓存逻辑中解放出来,Spring对抽象缓存提供了具体的实现,它是通过以下两个接口实现的的。
org.springframework.cache.Cache
org.springframework.cache.CacheManager
有以下几种实现
JDK java.util.concurrent.ConcurrentMap based caches,
EhCache,
Guava caches,
JSR-107 compliant caches
要使用抽象缓存,开发者需要注意以下两点:
Spring提供了一下的注解来定义抽象缓存
@Cacheable triggers cache population
@CacheEvict triggers cache eviction
@CachePut updates the cache without interfering with the method execution
@Caching regroups multiple cache operations to be applied on a method
@CacheConfig shares some common cache-related settings at class-level
使用@Cacheable来定义缓存
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
findBook 方法会缓存在 books这个缓存中
因为缓存都是key-value形式的,value就是返回值,而key可以有各种各样的,所以必须有一个key的生成策略,可以自定义。这里不详细介绍,介绍Spring中SpEL的key属性
请看示例
// 将isbn作为key
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
上面的程序代码中将isbn作为缓存books的key,返回值Book作为value。
并不一定每次都需要缓存,可以根据参数来确定是否缓存,Spring提供了condition参数,通过判断true或者false来决定是否执行方法
@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name)
如果name的长度小于32就执行方法,否则从缓存中取数据
还有一个unless它是在方法之后来确实是否将结果放入缓存
Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)
方法永远会被执行,并把结果放入缓存
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
通常用在更新的方法上
将缓存移除
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)
allEntries=true表明所有的books中的缓存将被移除
当一个类型有多个注解的时候使用
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
每次写的时候都需要指定cacheNames,如果都使用同一个cacheNames,可以使用@CacheConfig,将其放在类上
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
通过上面的分析可以知道缓存使用的两个关键,一个是定义缓存,另一个是配置缓存,上面主要讲了如何定义缓存,下面讲一下如何配置
首先,开启注解
<cache:annotation-driven />
我们知道Spring的缓存通过两个接口来实现
org.springframework.cache.Cache
org.springframework.cache.CacheManager
下面介绍一下如何配置CacheManager,和Cache
在Spring看来一切都是Bean,所以我们需要将CacheManager和Cache分别用Bean配置起来就ok了。
其中需要配置的主要为Cache,Cache是数据缓存的位置,可以有很多种。
CacheManager是cache管理器,spring提供了多种实现,配置很简单。
下面介绍一下JDK ConcurrentMap-based Cach和EhCache-based Cache的配置
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!-- 默认的缓存 -->
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<!-- 例子中一直使用的"books",也就是那个cacheNames="books" -->
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
</set>
</property>
</bean>
完整的xml
spring-cache.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd">
<context:component-scan base-package="com.gwc.springlearn"/>
<cache:annotation-driven/>
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!-- 默认的缓存 -->
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<!-- 例子中一直使用的"books",也就是那个cacheNames="books" -->
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
</set>
</property>
</bean>
</beans>
maven依赖
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gwc.springlearn</groupId>
<artifactId>SpringCache</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<!-- Spring -->
<spring-framework.version>4.2.2.RELEASE</spring-framework.version>
</properties>
<dependencies>
<!--spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!--spring -->
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--junit-->
</dependencies>
</project>
Book.java
package com.gwc.springlearn;
/** * Created by GWCheng on 2016/3/3. */
public class Book {
private String bookISBN;
private String bookName;
public Book(String bookISBN, String bookName) {
this.bookISBN = bookISBN;
this.bookName = bookName;
}
public String getBookISBN() {
return bookISBN;
}
public void setBookISBN(String bookISBN) {
this.bookISBN = bookISBN;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
BookNotFoundException.java
package com.gwc.springlearn;
/** * Created by GWCheng on 2016/3/3. */
public class BookNotFoundException extends Exception {
public BookNotFoundException(String msg){
super(msg);
}
}
CacheService.java
package com.gwc.springlearn;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
/** * Created by GWCheng on 2016/3/3. */
@Component
//这里定义了之后就不必在下面的每个方法上写 cacheNames="books" 了
@CacheConfig(cacheNames = "books")
public class CacheService {
@Cacheable(key="#isbn")
public Book findBook(String isbn) throws BookNotFoundException {
System.out.println("isbn="+isbn+" 方法调用");
Book book = null;
if (isbn == "123") {
book = new Book("123", "Thinking in Java");
} else if (isbn == "456") {
book = new Book("456", "Effective Java");
} else {
throw new BookNotFoundException("没有找到isbn对应的书");
}
return book;
}
}
package com.gwc.springlearn;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/** * Created by GWCheng on 2016/3/3. */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-cache.xml"})
public class TestCacheService {
@Autowired
private CacheService cacheService;
@Test
public void testCacheable() throws BookNotFoundException {
for (int i = 0; i < 10; i++) {
cacheService.findBook("123");
}
}
}
首先,将CacheService的 @Cacheable(key=”#isbn”)注释掉
运行测试用例
进行了十次方法调用,说明并没有缓存(因为我们没有开启缓存)。这个主要是用来做对比
CacheService的 @Cacheable(key=”#isbn”)开启
只调用了一次,说明已经缓存了。
尽管缓存开启了,可是condition不满足了,还是执行方法法
开启缓存,并且condition满足,进行了缓存
Effective Java 的长度为14
Thinking in Java的长度为16
加上unless条件,只有返回结果的bookName的长度大于15才缓存
isbn为123的返回Effective Java长度不够,所以没有缓存
isbn为456的返回Thinking in Java长度满足条件,所以进行了缓存
在CacheService中添加如下方法
@CachePut(key="#isbn")
public Book updateBook(String isbn,String bookName){
System.out.println("isbn=" + isbn + " bookName="+bookName+" 更新缓存的方法被调用");
Book book = new Book(isbn,bookName);
return book;
}
@CachePut表明该方法会将缓存中key为isbn的Book对象更新
修改测试用例
@Test
public void testCacheable() throws BookNotFoundException {
Book book = null;
for (int i = 0; i < 10; i++) {
book = cacheService.findBook("456");
}
System.out.println("bookName= "+book.getBookName());
cacheService.updateBook("456", "Spring 3.x企业应用开发实战");
book = cacheService.findBook("456");
System.out.println("bookName= "+book.getBookName());
}
结果如下
@CachePut确实更改了缓存中key为isbn的Book对象
在CacheService中添加如下方法
@CacheEvict(allEntries=true)
public void loadBooks(){
System.out.println("清除缓存的方法被调用");
}
@CacheEvict将清除cacheNames = “books”中的所有数据
更改测试用例
@Test
public void testCacheable() throws BookNotFoundException {
Book book = null;
for (int i = 0; i < 10; i++) {
book = cacheService.findBook("456");
}
//清除缓存
cacheService.loadBooks();
//缓存中没有了
cacheService.findBook("456");
//缓存中有
cacheService.findBook("456");
}
结果如下
说明@CacheEvict确实清除了缓存中的数据
Spring的缓存确实很方便,通过几个简单的注解就可以完成基本的缓存操作,而且配置简单,本文讲解了Spring抽象缓存的用法,其中包括@Cacheable, @CachePut ,@CacheEvict,@CacheConfig等注解,还有condition和unless参数的用法。
关于Spring与EhCache的整合参考
整合之道–Spring整合EhCache
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/