spring cache

释放双眼,带上耳机,听听看~!

概述

Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。
其特点总结如下:

  • 通过少量的配置 annotation 注释即可使得既有代码支持缓存
  • 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
  • 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
  • 支持 AspectJ,并通过其实现任何方法的缓存支持
  • 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性

实现

  1. maven配置


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1        <dependency>
2            <groupId>aspectj</groupId>
3            <artifactId>aspectjrt</artifactId>
4            <version>1.5.4</version>
5        </dependency>
6        <dependency>
7            <groupId>org.aspectj</groupId>
8            <artifactId>aspectjweaver</artifactId>
9            <version>1.8.10</version>
10        </dependency>
11        <dependency>
12            <groupId>org.springframework</groupId>
13            <artifactId>spring-context</artifactId>
14            <version>${spring.version}</version>
15        </dependency>
16
  1. 本项目采用纯java的配置,以下是配置spring cache的代码,缓存管理采用spring自带SimpleCacheManager,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2@Configuration
3@EnableCaching //开启缓存注解
4public class SpringCacheConfig {
5
6    /**
7     * 使用SimpleCacheManager管理缓存
8     * @return SimpleCacheManager
9     */
10    @Bean
11    public SimpleCacheManager cacheManager() {
12        SimpleCacheManager cacheManager = new SimpleCacheManager();
13        cacheManager.setCaches(Arrays.asList(
14                //新建2个缓存实例
15                new ConcurrentMapCache[]{
16                        new ConcurrentMapCache("default"),
17                        new ConcurrentMapCache("test"),
18                        new ConcurrentMapCache("users")}));
19        return cacheManager;
20    }
21

@Cacheable、@CachePut、@CacheEvict 注释介绍
通过上面的例子,我们可以看到 spring cache 主要使用两个注释标签,即 @Cacheable、@CachePut 和 @CacheEvict,我们总结一下其作用和配置方法。
*表 1. @Cacheable 作用和配置方法
@Cacheable 的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存*
@Cacheable 主要的参数

value
缓存的名称,在 spring 配置文件中定义,必须指定至少一个
例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
例如:@Cacheable(value=”testcache”,key=”#userName”)
condition
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

@CachePut 作用和配置方法

value
缓存的名称,在 spring 配置文件中定义,必须指定至少一个
例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
例如:@Cacheable(value=”testcache”,key=”#userName”)
condition
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

表 3. @CacheEvict 作用和配置方法

value
缓存的名称,在 spring 配置文件中定义,必须指定至少一个
例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
例如:@Cacheable(value=”testcache”,key=”#userName”)
condition
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries
是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
例如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存
@CachEvict(value=”testcache”,beforeInvocation=true)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1@Service("userService")
2public class UserService {
3
4    @Autowired
5    private UserRepository userRepository;
6
7    @Autowired
8    private CacheManager cacheManager;
9
10    private Logger logger = LoggerFactory.getLogger(UserService.class);
11
12    @Cacheable(cacheNames = "users")
13    public List<User> getUsers(){
14        logger.info("getUsers for userService");
15        Map<String, Object> param = new HashMap();
16        return userRepository.getUserList(param);
17    }
18
19    @CachePut(cacheNames = "users")
20    public User saveUser(User user){
21        logger.info("save user at userRepository !");
22        return userRepository.save(user);
23    }
24
25

基本原理

和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。我们来看一下下面这个图:

spring cache

上图显示,当客户端“Calling code”调用一个普通类 Plain Object 的 foo() 方法的时候,是直接作用在 pojo 类自身对象上的,客户端拥有的是被调用者的直接的引用。
而 Spring cache 利用了 Spring AOP 的动态代理技术,即当客户端尝试调用 pojo 的 foo()方法的时候,给他的不是 pojo 自身的引用,而是一个动态生成的代理类

spring cache

如上图所示,这个时候,实际客户端拥有的是一个代理的引用,那么在调用 foo() 方法的时候,会首先调用 proxy 的 foo() 方法,这个时候 proxy 可以整体控制实际的 pojo.foo() 方法的入参和返回值,比如缓存结果,比如直接略过执行实际的 foo() 方法等,都是可以轻松做到的。

扩展性

直到现在,我们已经学会了如何使用开箱即用的 spring cache,这基本能够满足一般应用对缓存的需求,但现实总是很复杂,当你的用户量上去或者性能跟不上,总需要进行扩展,这个时候你或许对其提供的内存缓存不满意了,因为其不支持高可用性,也不具备持久化数据能力,这个时候,你就需要自定义你的缓存方案了,还好,spring 也想到了这一点。
我们先不考虑如何持久化缓存,毕竟这种第三方的实现方案很多,我们要考虑的是,怎么利用 spring 提供的扩展点实现我们自己的缓存,且在不改原来已有代码的情况下进行扩展。
首先,我们需要提供一个 CacheManager 接口的实现,这个接口告诉 spring 有哪些 cache 实例,spring 会根据 cache 的名字查找 cache 的实例。另外还需要自己实现 Cache 接口,Cache 接口负责实际的缓存逻辑,例如增加键值对、存储、查询和清空等。利用 Cache 接口,我们可以对接任何第三方的缓存系统,例如 EHCache、OSCache,甚至一些内存数据库例如 memcache 或者 h2db 等。下面我举一个简单的例子说明如何做。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1package cacheOfAnno;
2
3 import java.util.Collection;
4
5 import org.springframework.cache.support.AbstractCacheManager;
6
7 public class MyCacheManager extends AbstractCacheManager {
8   private Collection<? extends MyCache> caches;
9
10   /**
11   * Specify the collection of Cache instances to use for this CacheManager.
12   */
13   public void setCaches(Collection<? extends MyCache> caches) {
14     this.caches = caches;
15   }
16
17   @Override
18   protected Collection<? extends MyCache> loadCaches() {
19     return this.caches;
20   }
21
22 }
23

上面的自定义的 CacheManager 实际继承了 spring 内置的 AbstractCacheManager,实际上仅仅管理 MyCache 类的实例。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
1package cacheOfAnno;
2
3 import java.util.HashMap;
4 import java.util.Map;
5
6 import org.springframework.cache.Cache;
7 import org.springframework.cache.support.SimpleValueWrapper;
8
9 public class MyCache implements Cache {
10   private String name;
11   private Map<String,Account> store = new HashMap<String,Account>();;
12
13   public MyCache() {
14   }
15
16   public MyCache(String name) {
17     this.name = name;
18   }
19
20   @Override
21   public String getName() {
22     return name;
23   }
24
25   public void setName(String name) {
26     this.name = name;
27   }
28
29   @Override
30   public Object getNativeCache() {
31     return store;
32   }
33
34   @Override
35   public ValueWrapper get(Object key) {
36     ValueWrapper result = null;
37     Account thevalue = store.get(key);
38     if(thevalue!=null) {
39       thevalue.setPassword("from mycache:"+name);
40       result = new SimpleValueWrapper(thevalue);
41     }
42     return result;
43   }
44
45   @Override
46   public void put(Object key, Object value) {
47     Account thevalue = (Account)value;
48     store.put((String)key, thevalue);
49   }
50
51   @Override
52   public void evict(Object key) {
53   }
54
55   @Override
56   public void clear() {
57   }
58 }
59

上面的自定义缓存只实现了很简单的逻辑,但这是我们自己做的,也很令人激动是不是,主要看 get 和 put 方法,其中的 get 方法留了一个后门,即所有的从缓存查询返回的对象都将其 password 字段设置为一个特殊的值,这样我们等下就能演示“我们的缓存确实在起作用!”了。

注意和限制

基于 proxy 的 spring aop 带来的内部调用问题

上面介绍过 spring cache 的原理,即它是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题,如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效,我们来演示一下。

可见,结果是每次都查询数据库,缓存没起作用。要避免这个问题,就是要避免对缓存方法的内部调用,或者避免使用基于 proxy 的 AOP 模式,可以使用基于 aspectJ 的 AOP 模式来解决这个问题。
@CacheEvict 的可靠性问题
我们看到,@CacheEvict 注释有一个属性 beforeInvocation,缺省为 false,即缺省情况下,都是在实际的方法执行完成后,才对缓存进行清空操作。期间如果执行方法出现异常,则会导致缓存清空不被执行。我们演示一下

非 public 方法问题
和内部调用问题类似,非 public 方法如果想实现基于注释的缓存,必须采用基于 AspectJ 的 AOP 机制,这里限于篇幅不再细述。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

Bootstrap框架之排版

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索