**本文首发于公众号【看点代码再上班】,欢迎围观,第一时间获取最新文章。**
大家好,我是tin,这是我的第11篇原创文章
目录
为什么要用缓存
Spring Cache介绍
Spring Cache使用入门
Spring Cache依赖Spring的天然优势——AOP,我们只需要显式地在代码中调用第三方接口,在方法上加上注解,就可以实现把获取到的结果后把结果插入缓存内,在下一次查询的时候优先从缓存中读取数据。
接入Caffeine缓存实现框架
常用注解
送书啦
结语
为什么要用缓存
我们一定听说过"缓存无敌"的话,特别是在大型互联网公司,"查多写少"的场景屡见不鲜。网络上查到的很多诸如系统吞吐量提升50%、接口耗时降低80%、一个分钟级别的程序优化到毫秒级别等,多多少少和缓存有关。
举个例子:在我们程序中,很多配置数据(例如一个商品信息、一个白名单、一个第三方客户的回调接口),这些数据存在我们的DB上,数据量比较少,但是程序访问很频繁,这种情况下,将数据放一份到我们的内存缓存中将大大提升我们系统的访问效率,因为减少了数据库访问,有可能减少了数据库建连时间、网络数据传输时间、数据库磁盘寻址时间……
总的来说,下面这些场景都可以考虑使用缓存优化性能:
1、查数据库
2、读取文件
3、网络访问,特别是调用第三方服务查询接口
我们这里只讨论应用的本地缓存,因为像文件系统、CPU、数据库等本身也都存在缓存,但这些不在本文讨论范围。
Spring Cache介绍Spring Cache 是Spring 提供的一整套的缓存解决方案,它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如Caffeine、Guava Cache、Ehcache。
如果我们没有使用Spring Cache,而是直接使用Guava Cache,我们的代码可能得像下面这么写:
方便复制,我把源码贴出来:
package com.tin.example.service; import com.alibaba.fastjson.JSON; import com.google.common.base.Stopwatch; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.tin.example.library.BookEntity; import com.tin.example.library.BookService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class MyGuavaCacheService implements InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(MyGuavaCacheService.class); private static LoadingCacheCACHE; @Autowired private BookService bookService; public void query() { String bookNamePrefix = "00"; String bookNameSuffix = "号藏书"; for (int i = 0; i < 3; i++) { String bookName = bookNamePrefix + i + bookNameSuffix; for (int j = 0; j < 2; j++) { queryFromCache(bookName); } } //查看缓存状态 LOGGER.info("cache stats:{}", CACHE.stats().toString()); } public void queryFromCache(String bookName) { try { Stopwatch stopwatch = Stopwatch.createStarted(); BookEntity bookEntity = CACHE.get(bookName); LOGGER.info("query:{},cost:{}ms,book:{}", bookName, stopwatch.elapsed(TimeUnit.MILLISECONDS), JSON.toJSonString(bookEntity)); } catch (Exception e) { LOGGER.error("cache read error. bookName:{}", bookName, e); } } @Override public void afterPropertiesSet() throws Exception { CACHE = CacheBuilder.newBuilder() //并发级别=8,并发级别表示可以同时写缓存的线程数 .concurrencyLevel(8) //设置缓存容器的初始容量为50 .initialCapacity(50) //设置缓存最大容量为100,超过100之后就会按照LRU最近最少使用移除缓存项 .maximumSize(100) //设置写缓存后100毫秒后过期 .expireAfterWrite(100, TimeUnit.MILLISECONDS) //统计缓存情况,生产环境慎重使用 .recordStats() //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存 .build(new BookEntityCacheLoader()); } public class BookEntityCacheLoader extends CacheLoader { @Override public BookEntity load(String key) { try { return bookService.findByBookName(key); } catch (Exception e) { LOGGER.error("cache load error. key:{}", key); return null; } } } }
只有一个缓存这么写看着也还行,但如果有很多缓存,每个缓存都这样写,使用起来就复杂了,和我们的业务代码严重耦合。
这个时候就用到了Spring Cache,Spring Cache并不是缓存的实现,而是缓存使用的一种方式,其基于注解和Spring高级特性提供缓存读写以及失效刷新等各种能力。
Spring Cache默认支持几个缓存实现,如下图jar包(spring-context-support 5.3.14版本)所示:
这三个包只是Spring的support(类似于适配器),真正的缓存实现是需要手动依赖jar的,后文我会举例讲到。
Spring Cache使用入门EhCache:纯Java进程内缓存框架,也是Hibernate、MyBatis默认的缓存提供。
Caffeine:使用Java8对Guava缓存的重写版本,从Spring5开始,Spring默认删除了Guava而使用Caffeine,支持多种缓存过期策略。
jcache:实现了JSR107规范的三方缓存都可以通过此包得到适配。
Spring Cache依赖Spring的天然优势——AOP,我们只需要显式地在代码中调用第三方接口,在方法上加上注解,就可以实现把获取到的结果后把结果插入缓存内,在下一次查询的时候优先从缓存中读取数据。
使用Spring Cache也比较简单,简单总结就是3步:加依赖,开启缓存、加注解。
一、加依赖
maven:
org.springframework.boot spring-boot-starter-cache
gradle:
implementation 'org.springframework.boot:spring-boot-starter-cache'
二、开启缓存
需要在启动类加上@EnableCaching注解才能启动使用Spring Cache,比如:
package com.tin.example; import com.tin.example.service.MyGuavaCacheService; import com.tin.example.service.SpringCacheService; import com.tin.example.util.SpringContextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @EnableCaching public class Application { private static final Logger LOGGER = LoggerFactory.getLogger(Application.class); public static void main(String[] args) { SpringApplication.run(Application.class, args); LOGGER.info("容器启动成功... "); SpringCacheService springCacheService = SpringContextUtil.getBean(SpringCacheService.class); springCacheService.query(); // MyGuavaCacheService myGuavaCacheService = SpringContextUtil.getBean(MyGuavaCacheService.class); // myGuavaCacheService.query(); } }
三、加注解
在需要缓存返回结果的方法上加上注解@Cacheable即可,比如:
我们做个测试,假设查询一本书籍耗时100毫秒
package com.tin.example.library; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.List; @Service public class BookService extends AbstractLibraryService { @Autowired private BookStore bookStore; @Override public BookEntity findByBookName(String bookName) { //模拟查询耗时100毫秒 sleep4Millis(100); if (!bookStore.hasBook()) { return null; } ListallBook = bookStore.getBookStore(); for (BookEntity bookEntity : allBook) { if (bookEntity == null) { continue; } if (bookEntity.getBookName() != null && bookEntity.getBookName().contains(bookName)) { return bookEntity; } } return null; } @Cacheable("library") @Override public BookEntity findByBookNameWithSpringCache(String bookName) { return findByBookName(bookName); } }
加上Spirng Cache缓存后,可以明显地发现第二次查询同一本书耗时0ms
运行后的结果:
这说明缓存已经生效了。
接入Caffeine缓存实现框架上文讲到了Spring Cache支持三种缓存实现,在使用我们上面所说"三步"引入使用Spring Cache,我们究竟使用的是哪种缓存实现呢?
通过CacheManager打印看一下:
再运行程序,结果是这样的:
ConcurrentMapCache是Spring 内置默认的缓存实现。如果需要使用CaffeineCache,需要额外引入CaffeineCache包,同时生成一个CaffeineCacheManager的bean。
maven依赖:
CaffeineCacheManager生成:
package com.tin.example.caffeine; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CaffeineCacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .initialCapacity(100) .maximumSize(10000)) ; return cacheManager; } }
配置完成后再运行就是这样的结果了:
常用注解Spring Cache比较常用的几个注解:@Cacheable、 @CacheConfig、@CacheEvict、@CachePut、@Caching、@EnableCaching。spring-context依赖包下也能看到注解的定义。
除了CacheConfig只能用于类上,其余的都可以用在类或者方法上,用在方法上好理解,缓存方法结果,如果用在类上,就相当于对该类的所有可以缓存的方法(需要是public方法)加上注解。
@Cacheable
@Cacheble注解表示这个方法的结果可以被缓存,调用该方法前,会先检查对应的缓存key在缓存中是否已经有值,如果有就直接返回,不调用方法,如果没有,就会调用方法,同时把结果缓存起来。
@CacheConfig
有些配置可能又是一个类通用的,这种情况就可以使用@CacheConfig了,它是一个类级别的注解,可以在类级别上配置cacheNames、keyGenerator、cacheManager、cacheResolver等。
@CachePut
@CachePut注解修饰的方法,会把方法的返回值put到缓存里面缓存起来,它只是触发put的动作,和@Cacheable不同,不会读取缓存,put到缓存的值进程内其他场景的使用者就可以使用了。
@CacheEvict
@CacheEvict注解修饰的方法,会触发缓存的evict *** 作,清空缓存中指定key的值。
@Caching
@Caching能够支持多个缓存注解生效。
因为Java方法上相同类型注解只能有一个有效,在我们有些场景下需要多个注解 *** 作,特别是CacheEvict删除缓存,我们可能需要同时删除多份缓存值,这个后@Ocaching就有用途了。
送书啦你是不是也苦于不知道怎么看Spring源码?
这里有一本Spring源码解读书,拿去吧,对解读Spring源码有帮助。
扫下面二维码获取书籍(提取码:mf2d)
除了Spring源码解读书,还有其他的经典计算机书籍,欢迎关注公众号后发送「书籍」获取书库地址!书库书籍会越来越多,欢迎收藏。
结语我是tin,一个在努力让自己变得更优秀的普通攻城狮。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲加以修改。
坚持创作不容易,你的正反馈是我坚持输出的最强大动力,谢谢!
最后别忘了关注我哦!⏬⏬⏬
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)