您的位置:首页 > 编程语言 > Java开发

Spring文档学习--缓存(Cache Abstraction)

2016-03-03 21:13 561 查看

1 Spring 对缓存的介绍

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注解,还可以定制。

让我们进入正题。

2 什么是缓存

当缓存被使用的时候,执行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


要使用抽象缓存,开发者需要注意以下两点:

缓存定义

定义需要缓存的方法和策略 主要使用注解

缓存配置

缓存存取的位置 主要配置Cache和CacheManager

3 定义缓存

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来定义缓存

@Cacheable("books")
public Book findBook(ISBN isbn) {...}


findBook 方法会缓存在 books这个缓存中

key

因为缓存都是key-value形式的,value就是返回值,而key可以有各种各样的,所以必须有一个key的生成策略,如果不指定key,Spring会帮我们生成。

默认生成策略如下

If no params are given, return SimpleKey.EMPTY.

If only one param is given, return that instance.

If more the one param is given, return a SimpleKey containing all parameters.

如果方法没有参数,则使用SimpleKey.EMPTY作为key。

如果只有一个参数的话则使用该参数作为key。

如果参数多于一个的话则使用所有参数的hashCode作为key。



从源码来看如果没有参数会报错,最好还是在有参数的方法上缓存。

下面介绍Spring中SpEL的key属性

可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。

请看示例

// 将isbn作为key
@Cacheable(cacheNames="books", key="#isbn")
//或者 @Cacheable(cacheNames="books", key="#p0")
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

方法永远会被执行,并把结果放入缓存

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)


通常用在更新的方法上

@CacheEvict

将缓存移除

@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)


allEntries=true表明所有的books中的缓存将被移除

@Caching

当一个类型有多个注解的时候使用

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)


第一个@CacheEvict(“primary”)使用默认值清除名称为primary的缓存,第二个@CacheEvict(cacheNames=”secondary”, key=”#p0”)表示importBooks(String deposit, Date date)的第一个参数作为key清除掉名称为secondary的缓存

@CacheConfig

每次写的时候都需要指定cacheNames,如果都使用同一个cacheNames,可以使用@CacheConfig,将其放在类上

@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}


4 配置缓存

通过上面的分析可以知道缓存使用的两个关键,一个是定义缓存,另一个是配置缓存,上面主要讲了如何定义缓存,下面讲一下如何配置

首先,开启注解

<cache:annotation-driven />


我们知道Spring的缓存通过两个接口来实现

org.springframework.cache.Cache
org.springframework.cache.CacheManager


下面介绍一下如何配置CacheManager,和Cache

在Spring看来一切都是Bean,所以我们需要将CacheManager和Cache分别用Bean配置起来就ok了。

其中需要配置的主要为Cache,Cache是数据缓存的位置,可以有很多种。

JDK ConcurrentMap-based Cach

EhCache-based Cache

Guava Cache

GemFire-based Cache

JSR-107 Cache

CacheManager是cache管理器,spring提供了多种实现,配置很简单。

下面介绍一下JDK ConcurrentMap-based Cach和EhCache-based Cache的配置

JDK ConcurrentMap-based Cach

<!-- 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”)注释掉

运行测试用例



进行了十次方法调用,说明并没有缓存(因为我们没有开启缓存)。这个主要是用来做对比

测试@Cacheable

CacheService的 @Cacheable(key=”#isbn”)开启



只调用了一次,说明已经缓存了。

测试 condition



尽管缓存开启了,可是condition不满足了,还是执行方法法



开启缓存,并且condition满足,进行了缓存

测试 unless

Effective Java 的长度为14

Thinking in Java的长度为16

加上unless条件,只有返回结果的bookName的长度大于15才缓存



isbn为123的返回Effective Java长度不够,所以没有缓存



isbn为456的返回Thinking in Java长度满足条件,所以进行了缓存

测试 @CachePut

在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对象

测试 @CacheEvict

在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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring 缓存