目录
一、概述
二、声明式基于注解的缓存
1、@Cacheable注解
(1) 默认缓存key的生成
(2) 声明式自定义key生成
(3) 默认缓存解析
(3) 自定义缓存解析
(4) 条件式缓存
2、@CachePut注解
3、@CacheEvict注解
4、@Caching注解
5、@CacheConfig注解
三、开启声明式缓存注解
四、使用自定义注解
一、概述
从3.1版本起,Spring框架就已经支持将缓存添加到现有的Spring应用中,和事务支持一样,缓存抽象允许在对代码影响最小的情况下一致性地使用各种缓存解决方案。
从Spring 4.1版本起,有了JSR-107
注解和更多定制化的选项支持后,缓存抽象有了重大的改进。
二、声明式基于注解的缓存
@Cacheable:触发缓存构建。
@CacheEvict:触发缓存销毁。
@CachePut:更新缓存。
@Caching:重组应用到方法上的多个缓存操作。
@CacheConfig:类级别共享缓存相关的通用设置。
1、@Cacheable注解
正如其名,@Cacheable注解用来区分方法执行结果是否应该被缓存,如果后续该方法再次被调用,方法的执行结果直接从缓存中获取,而不会调用实际的方法逻辑。示例如下:
@Cacheable("books") public Book findBook(ISBN isbn) {...}
当然我们也可以指定多个缓存名称,如果至少一个缓存被命中,那么关联的缓存结果就会返回,示例如下:
@Cacheable({"books", "isbns"}) public Book findBook(ISBN isbn) {...}
因为缓存都是key-value存储,每次缓存方法的调用都会被转义为缓存key的访问。Spring缓存抽象对于key的生成会采用KeyGenerator来生成,算法如下:
如果没有方法参数,返回SimpleKey.EMPTY。
如果该方法只有一个参数,返回参数实例。
如果方法不止一个参数,返回包含所有参数的SimpleKey实例。
这种key的生成策略适用于大部分场景,只要方法参数合理实现了hashCode()和equals()方法。
SimpleKeyGenerator源码如下:
public class SimpleKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { return generateKey(params); } /** * Generate a key based on the specified parameters. */ public static Object generateKey(Object... params) { if (params.length == 0) { return SimpleKey.EMPTY; } if (params.length == 1) { Object param = params[0]; if (param != null && !param.getClass().isArray()) { return param; } } return new SimpleKey(params); } }
备注:如果要实现自定义key生成策略,需要实现org.springframework.cache.interceptor.KeyGenerator接口。
(2) 声明式自定义key生成
目标方法可能会有多个参数,有些参数可能只应用于方法逻辑,而不适合用作key的生成,例如:
@Cacheable("books") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
对于这种情况,@Cacheable
注解有一个key
属性,通过该属性可以自定义Key生成。我们也可以使用SPEL(Spring表达式语言)去指定参数或者参数的嵌套属性,示例如下:
@Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#isbn.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
(3) 默认缓存解析
Spring缓存抽象通过CacheResolver
去解析操作级别的缓存,而CacheResolver
会用CacheManager
去获取缓存,接口定义如下:
@FunctionalInterface public interface CacheResolver { /** * Return the cache(s) to use for the specified invocation. * @param context the context of the particular invocation * @return the cache(s) to use (never {@code null}) * @throws IllegalStateException if cache resolution failed */ Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context); }
public interface CacheManager { /** * Get the cache associated with the given name. * <p>Note that the cache may be lazily created at runtime if the * native provider supports it. * @param name the cache identifier (must not be {@code null}) * @return the associated cache, or {@code null} if such a cache * does not exist or could be not created */ @Nullable Cache getCache(String name); /** * Get a collection of the cache names known by this manager. * @return the names of all caches known by the cache manager */ Collection<String> getCacheNames(); }
(3) 自定义缓存解析
默认缓存解析对于单CacheManager
应用适应很好,对于有多个缓存管理器的应用,我们可以对每个操作设置缓存管理器,如下:
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") public Book findBook(ISBN isbn) {...}
(4) 条件式缓存
有时方法缓存结果可能要取决于指定的参数,缓存注解通过支持SPEL
的condition
属性实现该功能,示例如下:
@Cacheable(cacheNames="book", condition="#name.length() < 32") public Book findBook(String name
备注:如果Book对象的hardback
属性为true则不缓存,为false才缓存。
当然,缓存抽象同时也支持java.util.Optional
,只有当Optional
中的值存在时,方法返回值才会被缓存。#result
代表方法的执行结果,上面的我们可以改写:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback") public Optional<Book> findBook(String name)
备注;#result引用的始终是Book对象,而不是Optional对象,因为返回值可能为空,所以我们应该使用安全导航操作符 => ?.
关于其它可以用的缓存SpEL表达式上下文,可以参考:Available Caching SpEL Evaluation Context。
2、@CachePut注解
这个注解主要用于更新缓存,也就说带有该注解的方法总是会执行,并且方法的返回值会刷新缓存。该注解和@Cacheable的参数相同,示例如下:
@CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor)
备注:@CachePut和@Cacheable的主要区别在于后者会通过缓存跳过方法的执行,而前者为了更新缓存会迫使方法执行。
3、@CacheEvict注解
这个注解主要用来清除缓存,与@Cacheable注解相反,方法的执行会触发从缓存中删除数据。@CacheEvit注解要求指定一个或多个缓存名。除此之外,该注解还有一个额外的属性allEntries,指定该属性值为true后会清除某个缓存名下的所有缓存key。示例如下:
@CacheEvict(cacheNames="books", allEntries=true) public void loadBooks(InputStream batch)
4、@Caching注解
有些情况下,相同类型多个注解,如@CacheEvict或者@CachePut需要被指定。@Caching注解允许多个嵌套@Cacheable、@CachePut、@CacheEvict注解用在同一个方法上。示例如下:
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) public Book importBooks(String deposit, Date date)
5、@CacheConfig注解
目前我们已经了解到缓存操作提供了很多定制化的选项,然而有些定制化选项如果应用到类中的所有操作可能会有些冗余,示例如下:
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
@CacheConfig是一个类级别的注解,这个注解可以共享缓存名称,自定义的KeyGenerator,自定义的CacheManager和自定义的CacheResolver。
方法操作级别的自定义选项总是会重写@CacheConfig中的自定义选项。下面是每个缓存操作自定义选项对应的3个级别,优先级从上至下越来越高。
全局配置的CacheManager,KeyGenerator等。
类级别,通过@CacheConfig指定。
方法操作级别。
三、开启声明式缓存注解
直接在配置类上加上#EnableCaching
即可,如下:
@Configuration @EnableCaching public class AppConfig { }
四、使用自定义注解
Spring缓存抽象允许我们用自定义注解去标识什么方法可以触发缓存构建或者消除。@Cacheable
, @CachePut
, @CacheEvict
and @CacheConfig
这些注解都可以作为元注解,其实即使可以修饰其它注解,示例如下:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Cacheable(cacheNames="books", key="#isbn") public @interface SlowService { }
@Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@SlowService public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
尽管@SlowService
注解并不是Spring原生注解,但Spring容器会在运行时识别并且知道它是用来干嘛的。
备注:后面我们会利用自定义注解实现自定义过期时间的缓存方案。