本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!


聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

大家好,又见面了。

有诗云“纸上得来终觉浅,绝知此事要躬行”,在上一篇文章《手写本地缓存实战2—— 打造正规军,构建通用本地缓存结构》中,咱们一起证明并逐渐完结了一套简化版别的通用本地缓存结构,并在过程中逐渐分析了缓存规划要害要素的完结战略。本篇文章中,咱们一起来聊一聊缓存结构完结所需求遵从的标准。

为何需求标准

上一章中构建的最简化版别的缓存结构,虽然能够运用,但是也存在一个问题,便是它对外供给的完结接口都是结构依据自己的需求而自界说的。这样一来,项目集成了此缓存结构,后续假设想要替换缓存结构的时分,事务层面的改动会比较大。 —— 由于是自界说的结构接口,无法依据里氏替换准则来进行灵活的替换。

在业界各大厂商或许开源团队都会构建并供给一些自己完结的缓存结构或许组件,供给给开发者按需挑选运用。假设大家都是各自凭空捏造,必然导致事务中集成并运用某一缓存完结之后,想要替换缓存完结组件会难于登天。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

千古一帝秦始皇统一天下后,颁布了书同文、车同轨等一系列法规制度,使得一切的车辆都遵从统一的轴距,然后都能够在官道上正常的通行,大大提高了流通性。而正所谓“国有国法、行有行规”,为了确保缓存结构的通用性、提高项意图可移植性,JAVA职业也迫切需求这么一个缓存标准,来束缚各个缓存供给商给出的缓存结构都遵从相同的标准接口,事务中依照标准接口进行调用,无需与缓存结构进行深度耦合,使得缓存组件的替换成为一件简略点的工作。

在JAVA的缓存范畴,流传比较广泛的首要是JCache APISpring Cache两套标准,下面就一起来看下。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

虽迟但到的JSR107 —— JCache API

提到JAVA中的“职业规则”,JSR是一个绕不开的话题。它的全称为Java Specification Requests,意思是JAVA标准提案。在该标准标准中,有公布过一个关于JAVA缓存体系的标准界说,也即JSR 107标准(JCache API),首要明确了JAVA中依据内存进行目标缓存构建的一些要求,包括内存目标的创立查询更新删去一致性确保等方面内容。

JSR107标准早在2012年时草案就被提出,但却直到2014年才正式披露首个标准版别,也即JCache API 1.0.0版别,至此JAVA范畴总算是有个正式的关于缓存的官方标准要求。

揭秘JSR107 —— JCache API内容探究

JSR107标准详细的要求方式,都以接口的方式封装在javax.cache包中进行供给。咱们要完结的缓存结构需求遵从该标准,也便是需求引进javax.cache依靠包,并完结其间供给的相关接口即可。关于运用maven构建的项目中,能够在pom.xml中引进javax.cache依靠:

<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.1</version>
</dependency>

JCache API标准中,界说的缓存结构相关接口类之间的联系逻辑整理如下:

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

咱们要完结自己的本地缓存结构,也即需求完结上述各个接口。对上述各接口类的意义介绍阐明如下:

接口类 功用定位描述
CachingProvider SPI接口,缓存结构的加载入口。每个Provider中能够持有1个或许多个CacheManager目标,用来供给不同的缓存才能
CacheManager 缓存办理器接口,每个缓存办理器担任对详细的缓存容器的创立与办理,能够办理1个或许多个不同的Cache目标
Cache Cache缓存容器接口,担任存储详细的缓存数据,能够供给不同的容器才能
Entry Cache容器中存储的key-value键值对记载

作为通用标准,这儿将CachingProvider界说为了一个SPI接口Service Provider Interface,服务供给接口),首要是借助JDK自带的服务供给发现才能,来完结按需加载各自完结的功用逻辑,有点IOC的意味。这样规划有必定的好处:

  • 关于结构

需求遵从标准,供给上述接口的完结类。然后能够完结热插拔,与事务解耦。

  • 关于事务

先指定需求运用的SPI的详细完结类,然后事务逻辑中便无需感知缓存详细的完结,直接依据JCache API通用接口进行运用即可。后续假设需求替换缓存完结结构,只需求切换下运用的SPI的详细完结类即可。

依据上述介绍,一个依据JCache API完结的缓存结构在实践项目中运用时的目标层级联系可能会是下面这种场景(假定运用LRU策略存储部分信息、运用普通战略存储用户信息):

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

那么怎么去了解JCache API中几个接口类的联系呢?

几个简略的阐明:

  1. CachingProvider并无太多实践逻辑层面的功用,只是用来依据SPI机制,便利项目中集成插拔运用。内部持有CacheManager目标,实践的缓存办理才能,由CacheManager担任供给。

  2. CacheManager担任详细的缓存办理相关才能完结,实例由CachingProvider供给并持有,CachingProvider能够持有一个或许多个不同的CacheManager目标。这些CacheManager目标能够是相同类型,也能够是不同类型,比方咱们能够完结2种缓存结构,一种是依据内存的缓存,一种是依据磁盘的缓存,则能够别离供给两种不同的CacheManager,供事务按需调用。

  3. Cache是CacheManager担任创立并办理的详细的缓存容器,也能够有一个或许多个,如事务中会涉及到为用户列表和部分列表别离创立独立的Cache存储。此外,Cache容器也能够依据需求供给不同的Cache容器类型,以满足不同场景关于缓存容器的不同诉求,如咱们能够完结一个类似HashMap的普通键值对Cache容器,也能够供给一个依据LRU淘汰战略的Cache容器。

至此呢,咱们厘清了JCache API标准的大致内容。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

插叙 —— SPI何许人也

依照JSR107标准试编写缓存详细才能时,咱们需求完结一个SPI接口的完结类,然后由JDK供给的加载才能将咱们扩展的缓存服务加载到JVM中供运用。

提到API咱们都耳熟能详,也便是咱们常规而言的接口。但说起SPI或许许多小伙伴就有点陌生了。其实SPI也并非是什么新鲜玩意,它是JDK内置的一种服务的供给发现加载机制。依照JAVA的面向目标编码的思维,为了下降代码的耦合度、提高代码的灵活性,往往需求利用好笼统这一特性,比方一般会比较推荐依据接口进行编码、而尽量避免强依靠某个详细的功用完结类 —— 这样才能让构建出的体系具有更好的扩展性,更契合面向目标规划准则中的里式替换准则。SPI便是为了支撑这一诉求而供给的才能,它允许将接口详细的完结类交由事务或许三方进行独立构建,然后加载到JVM中以供事务进行运用。

为了这一点,咱们需求在resource/META-INF/services目录下新建一个文件,文件名即为SPI接口称号javax.cache.spi.CachingProvider,然后在文件内容中,写入咱们要注入进入的咱们自己的Provider完结类:

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

这样,咱们就完结了将咱们自己的MyCachingProvider功用注入到体系中。在事务运用时,能够经过Caching.getCachingProvider()获取到注入的自界说Provider

public static void main(String[] args) {
    CachingProvider provider =  Caching.getCachingProvider();
    System.out.println(provider);
}

从输出的成果能够看出,获取到了自界说的Provider目标:

com.veezean.skills.cache.fwk.MyCachingProvider@7adf9f5f

获取到Provider之后,便能够进一步的获取到Manager目标,从而事务层面层面能够正常运用。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

JCache API标准的完结

JSR作为JAVA范畴正统行规,制定的时分往往考虑到各种可能的灵活性与通用性。作为JSR中根正苗红的JCache API标准,也沿用了这一风格特色,结构接口的界说与完结也十分的丰富,简直能够扩展自界说任何你需求的处理战略。 —— 但恰是这一点,也让其整个结构的接口界说过于重量级。关于缓存结构完结者而言,遵从JCache API需求完结许多的接口,需求做许多额外的完结处理。

比方,咱们完结CacheManager的时分,需求完结如下这么多的接口:

public class MemCacheManager implements CacheManager {
    private CachingProvider cachingProvider;
    private ConcurrentHashMap<String, Cache> caches;
    public MemCacheManager(CachingProvider cachingProvider, ConcurrentHashMap<String, Cache> caches) {
        this.cachingProvider = cachingProvider;
        this.caches = caches;
    }
    @Override
    public CachingProvider getCachingProvider() {
    }
    @Override
    public URI getURI() {
    }
    @Override
    public ClassLoader getClassLoader() {
    }
    @Override
    public Properties getProperties() {
    }
    @Override
    public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(String s, C c) throws IllegalArgumentException {
    }
    @Override
    public <K, V> Cache<K, V> getCache(String s, Class<K> aClass, Class<V> aClass1) {
    }
    @Override
    public <K, V> Cache<K, V> getCache(String s) {
    }
    @Override
    public Iterable<String> getCacheNames() {
    }
    @Override
    public void destroyCache(String s) {
    }
    @Override
    public void enableManagement(String s, boolean b) {
    }
    @Override
    public void enableStatistics(String s, boolean b) {
    }
    @Override
    public void close() {
    }
    @Override
    public boolean isClosed() {
    }
    @Override
    public <T> T unwrap(Class<T> aClass) {
    }
}

长长的一摞接口等着完结,看着都令人上头,作为缓存供给商,便需求依照自己的才能去完结这些接口,以确保相关缓存才能是依照标准对外供给。也正是由于JCache API这种不接地气的表现,让其虽是JAVA 范畴的正统标准,却经常被束之高阁,沦落成为了一种名义标准。业界主流的本地缓存结构中,比较知名的当属Ehcache了(当然,Spring4.1中也增加了对JSR标准的支撑)。此外,Redis的本地客户端Redisson也有完结全套JCache API标准,用户能够依据Redisson调用JCache API的标准接口来进行缓存数据的操作。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

JSR107供给的注解操作办法

前面提到了作为供应商想要完结JSR107标准的时分会比较复杂,需求做许多自己的处理逻辑。但是关于事务运用者而言,JSR107仍是比较交心的。比方JSR107中就将一些常用的API办法封装为注解,利用注解来大大简化编码的复杂度,下降缓存关于事务逻辑的侵入性,使得事务开发人员能够愈加专心于事务自身的开发。

JSR107标准中常用的一些缓存操作注解办法整理如下面的表格:

注解 意义阐明
@CacheResult 将指定的keyvalue映射内容存入到缓存容器中
@CachePut 更新指定缓存容器中指定key值缓存记载内容
@CacheRemove 移除指定缓存容器中指定key值对应的缓存记载
@CacheRemoveAll 字面意义,移除指定缓存容器中的一切缓存记载
@CacheKey 作为接口参数前面修饰,用于指定特定的入参作为缓存key值的组成部分
@CacheValue 作为接口参数前面的修饰,用于指定特定的入参作为缓存value

上述注解首要是添加在办法上面,用于自动将办法的入参加回来成果之间进行一个映射与自动缓存,关于后续恳求假设射中缓存则直接回来缓存成果而无需再次履行办法的详细处理,以此来提高接口的响应速度与承压才能。

比方下面的查询接口上,经过@CacheResult注解能够将查询恳求与查询成果缓存起来进行运用:

@CacheResult(cacheName = "books")
public Book findBookByName(@CacheKey String bookName) {
    return bookDao.queryByName(bookName);
}

Book信息产生变更的时分,为了确保缓存数据的准确性,需求同步更新缓存内容。能够经过在更新办法上面添加@CachePut接口即可达成意图:

@CachePut(cacheName = "books")
public void updateBookInfo(@CacheKey String bookName, @CacheValue Book book) {
    bookDao.updateBook(bookName, book);
}

这儿别离适用了@CacheKey@CacheValue指定了需求更新的缓存记载key值,以及需求将其更新为的新的value值。

同样地,借助注解@CacheRemove能够完结对应缓存记载的删去:

@CacheRemove(cacheName = "books")
public void deleteBookInfo(@CacheKey String bookName) {
    bookDao.deleteBookByName(bookName)
}

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

爱屋及乌 —— Spring结构制定的Cache标准

JSR 107(JCache API)标准的诞生可谓是一路崎岖,拖拖拉拉直到2014年才发布了首个1.0.0版别标准。但是在JAVA界风头无两的Spring结构早在2011年就已经在其3.1版别中供给了缓存笼统层的标准界说,并借助Spring的优异规划与良好生态,迅速得到了各个软件开发集体的青睐,各大缓存厂商也陆续供给了契合Spring Cache标准的自家缓存产品。

Spring Cache并非是一个详细的缓存完结,而是和JSR107类似的一套缓存标准,依据注解并可完结与Spring的各种高档特性无缝集成,受到了广泛的追捧。各大缓存供给商简直都有依据Spring Cache标准进行完结的缓存组件。比方后面咱们会专门介绍的Guava CacheCaffeine Cache以及同样支撑JSR107标准的Ehcache等等。

得力于Spring在JAVA范畴无可撼动的位置,造就了Spring Cache已成为JAVA缓存范畴的“现实标准”,深有“功高盖主”的滋味。

Spring Cache运用不同缓存组件

假设要依据Spring Cache标准来进行缓存的操作,首先在项目中需求引进此标准的界说:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

这样,在事务代码中,就能够运用Spring Cache标准中界说的一些注解办法。前面有提过,Spring Cache只是一个标准声明,能够了解为一堆接口界说,而并没有供给详细的接口功用完结。详细的功用完结,由事务依据实践选型需求,引进相应缓存组件的jar库文件依靠即可 —— 这一点是Spring结构中极端遍及的一种做法。

假设咱们需求运用Guava Cache来作为咱们实践缓存才能供给者,则咱们只需求引进对应的依靠即可:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>

这样一来,咱们便完结了运用Guava cache作为存储服务供给者、且依据Spring Cache接口标准进行缓存操作。Spring作为JAVA范畴的一个适当优异的结构,得益于其优异的封装规划思维,使得替换缓存组件也显得十分简单。比方现在想要将上面的Guava cache替换为Caffeine cache作为新的缓存才能供给者,则事务代码中将依靠包改为Caffeine cache并简略的做一些细节装备即可:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.1</version>
</dependency>

这样一来,关于事务运用者而言,能够便利的进行缓存详细完结者的替换。而作为缓存才能供给商而言,自己能够容易的被同类产品替换掉,所以也鞭策自己去供给更好更强大的产品,稳固自己的位置,也由此促进整个生态的良性演进

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

Spring Cache标准供给的注解

需求留意的是,运用Spring Cache缓存前,需求先手动敞开关于缓存才能的支撑,能够经过@EnableCaching注解来完结。

除了 @EnableCaching ,在Spring Cache中还界说了一些其它的常用注解办法,整理归纳如下:

注解 意义阐明
@EnableCaching 敞开运用缓存才能
@Cacheable 添加相关内容到缓存中
@CachePut 更新相关缓存记载
@CacheEvict 删去指定的缓存记载,假设需求清空指定容器的全部缓存记载,能够指定allEntities=true来完结

详细的运用上,其实和JSR107标准中供给的注解用法类似。

当然了,JAVA范畴缓存现实标准位置虽已奠定,但是Spring Cache依旧是保持着一个兼收并蓄的姿势,并积极的兼容了JCache API相关标准,比方Spring4.1起项目中能够运用JSR107标准供给的相重视解办法来操作。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

小结回忆

好啦,关于JAVA中的JSR107标准以及Spring Cache标准,以及各自典型代表,咱们就聊到这儿。

那么,关于本文中提及的缓存标准的内容,你是否有自己的一些想法与见地呢?欢迎评论区一起交流下,等待和各位小伙伴们一起商讨、共同生长。

补充阐明

本文属于《深入了解缓存原理与实战规划》系列专栏的内容之一。该专栏围绕缓存这个宏大命题进行展开论述,全方位、体系性地深度分析各种缓存完结战略与原理、以及缓存的各种用法、各种问题应对战略,并一起讨论下缓存规划的哲学。

假设有爱好,也欢迎重视此专栏。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

我是悟道,聊技能、又不只是聊技能~

假设觉得有用,请点赞 + 重视让我感受到您的支撑。也能够重视下我的大众号【架构悟道】,获取更及时的更新。

等待与你一起讨论,一起生长为更好的自己。

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache