1. 程式人生 > >淺談快取-註解驅動的快取 Spring cache介紹

淺談快取-註解驅動的快取 Spring cache介紹

在我們平常的工作當中,有好多場景需要用到快取技術,如redis,ehcache等來加速資料的訪問。作為淺談快取的第一篇筆者不想談論具體的快取技術,我更想介紹一下Spring中每次閱讀都會使我心中泛起波瀾的一個東西,那就是基於註解的快取技術。我們先看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.簡單翻譯一下,基於註解的快取技術是Spring3.1版本引入的,其目的是為了給一個已有的Spring應用最小侵入的引入快取。其原理與Spring的事務類似都是基於Spring的AOP技術。他本質上不是一個具體的快取實現方案(例如redis EHCache等),而是一個對快取使用的抽象,通過在既有程式碼中新增少量的annotation,即能達到快取方法返回物件的效果。她支援開箱即用的快取臨時儲存方案,也支援整合主流的專業快取,如:redis,EHCache。好,廢話少說,下面筆者通過幾個簡單例子來總結看看Spring Cache的優點,並通過原始碼對其原理做一個簡單介紹。
 注意:筆者的程式碼都是基於Spring boot 1.5.9.release版本,程式碼的風格也都是Spring boot式的。先建一個基於Spring Boot 的web專案,本文的程式設計用例都會在此專案中完成。專案的pom.xml檔案如下。<?xml version="1.0" encoding="UTF-8"?>    <modelVersion>4.0.0</modelVersion>    <groupId>com.snow.cache</groupId>    <artifactId>snow-cache</artifactId>
    <version>0.0.1-SNAPSHOT</version>    <packaging>jar</packaging>    <name>snow-cache</name>    <description>Demo for Spring Cache</description>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>        <relativePath/> <!-- lookup parent from repository --> </parent>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>        <java.version>1.8</java.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>partI.傳統快取的用法在聊Spring Cache之前,我們先來看看我們以前是怎麼使用快取的。這裡筆者不打算引入任何第三方快取而是自己實現一個簡易的快取。案例:電商的場景中,商品資訊需要經常被使用者瀏覽,不做快取資料庫肯定吃不消。我們就來對商品資訊的查詢做快取,以商品資訊的編號為key,商品資訊物件為value,當以相同的編號查詢商品時,若快取中有,直接從快取中返回結果,否則從資料庫中查詢,並將查詢結果放入快取。先定義一個產品實體類程式碼如下:package com.snow.cache;import java.io.Serializable;/**** @author snowwolf-louis* @date 18/4/10*/public class Product implements Serializable{    private Long id;    private String name;    private String desc;    public Long getId() {        return id; }    public void setId(Long id) {        this.id = id; }    public String getName() {        return name; }    public void setName(String name) {        this.name = name; }    public String getDesc() {        return desc; }    public void setDesc(String desc) {        this.desc = desc; }    @Override public String toString() {        return "Product{" +                "id=" + id +                ", name='" + name + '\'' +                ", desc='" + desc + '\'' +                '}'; }}在定義一個快取管理器,負責快取邏輯,支援物件的增加,修改和刪除。程式碼如下:package com.snow.cache.tradition;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/**** @author snowwolf-louis* @date 18/4/10*/public class SimpleCacheManager<T> {    /**     * 用ConcurrentHashMap做為快取     */ private Map<String,T> cache = new ConcurrentHashMap<String, T>(); /**     * 從快取中獲取給定key的值     * @param key * @return 返回快取中指定key的值,若無則返回null     */ public T get(String key){        return cache.get(key); }    /**     * 將資料(key,value)放入快取     * @param key * @param value */ public void put(String key, T value){        cache.put(key,value); }    /**     * 清除快取中指定key的的資料     * @param key */ public void evict(String key){        if (cache.containsKey(key)){            cache.remove(key); }    }    /**     * 清空快取     */ public void clearAll(){        cache.clear(); }}將我們自定義的快取管理器交給Spring容器管理package com.snow.cache.tradition.config;import com.snow.cache.Product;import com.snow.cache.tradition.SimpleCacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/*** 傳統快取用法的配置類* @author snowwolf-louis* @date 18/4/10*/@Configurationpublic class SimpleCacheConfig {    @Bean public SimpleCacheManager<Product> cacheManager(){        return new SimpleCacheManager<Product>(); }}對商品的業務操作介面package com.snow.cache.tradition.service;import com.snow.cache.Product;/*** 對商品的業務操作介面* @author snowwolf-louis* @date 18/4/10*/public interface ProductService {    /**     * 根據id查詢商品資訊     * @param id * @return */ public Product getProductById(Long id);}對商品的業務操作實現package com.snow.cache.tradition.service;import com.snow.cache.Product;import com.snow.cache.tradition.SimpleCacheManager;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.Objects;/*** @author snowwolf-louis* @date 18/4/10*/@Servicepublic class ProductServiceImpl implements ProductService {    @Autowired private SimpleCacheManager<Product> cacheManager; @Override public Product getProductById(Long id) {        if (Objects.isNull(id)){            throw new NullPointerException("id不能為空"); }        Product product = null; //首先從快取查詢 product = cacheManager.get(id.toString());        if (product != null) {            return product; }        //快取中沒有在從資料庫中查詢,我這裡就不從資料庫中查了,而是呼叫一個私有方法模擬從資料庫中查 product = getproductFromDB(id); //將資料庫中查到資料裝載進快取 if (product != null && product.getId() != null){            cacheManager.put(product.getId().toString(),product); }        return product; }    private Product getproductFromDB(Long id) {        try {            Thread.sleep(10L); } catch (InterruptedException e) {            e.printStackTrace(); }        System.out.println("從資料庫中查詢商品資訊"); Product product = new Product(); product.setId(id); product.setName("iphone 11"); product.setDesc("iphone 11,smartisan R1");        return product; }}好,快取設計好了,我們編寫一個測試類package com.snow.cache.tradition;import com.snow.cache.Product;import com.snow.cache.tradition.config.SimpleCacheConfig;import com.snow.cache.tradition.service.ProductService;import com.snow.cache.tradition.service.ProductServiceImpl;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTest(classes = {Product.class,SimpleCacheConfig.class,ProductServiceImpl.class})public class SimpleCacheTests { @Autowired private ProductService productService; @Test public void getProductByIdTest(){ long start1 = System.currentTimeMillis(); System.out.println("第一次查詢商品..."); Product product1 = productService.getProductById(1L);      long end1 = System.currentTimeMillis(); System.out.println("第一次查詢所需時間:" + (end1 - start1) + "ms"); System.out.println("第一次查詢的商品資訊為:" + product1);      long start2 = System.currentTimeMillis(); System.out.println("第二次查詢商品..."); Product product2 = productService.getProductById(1L);      long end2 = System.currentTimeMillis(); System.out.println("第二次查詢所需時間:" + (end2 - start2) + "ms"); System.out.println("第二次查詢的商品資訊為:" + product2); }}測試類中,我們兩次呼叫商品查詢介面結果如下第一次查詢商品...從資料庫中查詢商品資訊第一次查詢所需時間:11ms第一次查詢的商品資訊為:Product{id=1, name='iphone 11', desc='iphone 11,smartisan R1'}第二次查詢商品...第二次查詢所需時間:0ms第二次查詢的商品資訊為:Product{id=1, name='iphone 11', desc='iphone 11,smartisan R1'}從結果中可以看出第一次查詢商品是從資料庫中查詢商品資訊的耗時11ms,第二次查詢是走的快取耗時不足1ms,兩次返回結果也一摸一樣,足見我們的快取生效了。但是我們也不難發現傳統方式的問題。1.快取程式碼和業務程式碼耦合度太高,如上面的例子中,ProductServiceImpl中的getProductById()方法有太多關於快取的邏輯,不便於維護和變更。2.不靈活,這種快取方案不支援按照某種條件的快取,比如只有某類商品才需要快取,這種需求會導致程式碼的變更。3.快取的儲存這塊寫的比較死,不能靈活的切換為使用第三方的快取模組。partII.Spring Cache 示例 & 原理:以上我們程式碼中常見的問題,Spring Cache都很好的解決了。下面我們來看下Spring Cache的例子。我們的快取管理器使用Spring Cache提供的org.springframework.cache.concurrent.ConcurrentMapCacheManager其內部使用org.springframework.cache.concurrent.ConcurrentMapCache作為快取的儲存容器,底層實際上也是java.util.concurrent.ConcurrentHashMap好下面我將此快取管理器交給Spring管理package com.snow.cache.spring.config;import org.springframework.cache.CacheManager;import org.springframework.cache.annotation.CachingConfigurerSupport;import org.springframework.cache.annotation.EnableCaching;import org.springframework.cache.concurrent.ConcurrentMapCacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.concurrent.ConcurrentMap;/***Spring Cache提供的開箱即用的快取方案(使用{@link ConcurrentMap }作為快取的儲存容器)** @author snowwolf-louis* @date 18/4/12*/@Configuration@EnableCachingpublic class ConcurrentMapCacheConfig extends CachingConfigurerSupport {    @Bean    @Override public CacheManager cacheManager() {        return new ConcurrentMapCacheManager(); }}對商品的業務操作介面:package com.snow.cache.spring.service;import com.snow.cache.Product;/*** 對商品的業務操作介面* @author snowwolf-louis* @date 18/4/10*/public interface ProductService {    /**     * 根據id查詢商品資訊     * @param id * @return */ public Product getProductById(Long id);}對商品的業務操作實現package com.snow.cache.spring.service;import com.snow.cache.Product;import com.snow.cache.tradition.SimpleCacheManager;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;import java.util.Objects;/*** @author snowwolf-louis* @date 18/4/10*/@Servicepublic class ProductServiceImpl implements ProductService {    @Override    @Cacheable(value = "product", key = "#id")    public Product getProductById(Long id) {        if (Objects.isNull(id)) {            throw new NullPointerException("id不能為空"); }        //快取中沒有在從資料庫中查詢,我這裡就不從資料庫中查了,而是呼叫一個私有方法模擬從資料庫中查 return getproductFromDB(id); }    private Product getproductFromDB(Long id) {        try {            Thread.sleep(10L); } catch (InterruptedException e) {            e.printStackTrace(); }        System.out.println("從資料庫中查詢商品資訊"); Product product = new Product(); product.setId(id); product.setName("iphone 11"); product.setDesc("iphone 11,smartisan R1");        return product; }}我們可以看到上述getProductById方法上面加了一行這個註解@Cacheable(value = "product", key = "#id")而原先對快取的操作的一些程式碼在這裡全都沒有了,程式碼變得非常的乾淨僅僅和所需的業務相關。下面就測試一下這個快取是否生效了。測試類如下:package com.snow.cache.spring;import com.snow.cache.Product;import com.snow.cache.spring.config.ConcurrentMapCacheConfig;import com.snow.cache.spring.service.ProductService;import com.snow.cache.spring.service.ProductServiceImpl;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTest(classes = {ConcurrentMapCacheConfig.class, Product.class, ProductServiceImpl.class})public class SpringCacheTests {   @Autowired private ProductService productService; @Test public void getProductByIdTest(){      long start1 = System.currentTimeMillis(); System.out.println("第一次查詢商品..."); Product product1 = productService.getProductById(1L);      long end1 = System.currentTimeMillis(); System.out.println("第一次查詢所需時間:" + (end1 - start1) + "ms"); System.out.println("第一次查詢的商品資訊為:" + product1);      long start2 = System.currentTimeMillis(); System.out.println("第二次查詢商品..."); Product product2 = productService.getProductById(1L);      long end2 = System.currentTimeMillis(); System.out.println("第二次查詢所需時間:" + (end2 - start2) + "ms"); System.out.println("第二次查詢的商品資訊為:" + product2); }}執行結果:第一次查詢商品...從資料庫中查詢商品資訊第一次查詢所需時間:59ms第一次查詢的商品資訊為:Product{id=1, name='iphone 11', desc='iphone 11,smartisan R1'}第二次查詢商品...第二次查詢所需時間:1ms第二次查詢的商品資訊為:Product{id=1, name='iphone 11', desc='iphone 11,smartisan R1'}從結果中可以看出第一次查詢是走的資料庫,執行時間為59ms(每次執行會有差異,不同的機器也會有所差異),第二次查詢商品沒有走資料庫,執行時間1ms。兩次查詢結果一樣。證明我們使用的快取起作用了,是不是很神奇?好到這裡我們傳統方式面臨的耦合性太高的問題就完美的解決了。下面我們在來看第二個問題:如果我想要快取商品的id>50的商品怎麼辦?其實很簡單隻要將註解@Cacheable(value = "product", key = "#id", condition = "#id.longValue() > 50L)加在getProductById的方法上即可condition = "#id.longValue() > 50L” 按條件快取所加的條件。筆者就不測試該示例了,筆者會在本文的partIII部分介紹@Cacheable @CacheEvict @CachePut的用法。至於第三個問題(傳統的快取的儲存這塊寫的比較死,不能靈活的切換為使用第三方的快取模組)我們先來看下這個Spring Cache 內建的開箱即用的快取的類圖
從上圖可以看出Spring Cache對快取儲存的頂層介面為Cache,快取管理的頂層介面為CacheManagerCache原始碼:package org.springframework.cache;import java.util.concurrent.Callable;/*** 定義快取通用操作的介面** @author Costin Leau* @author Juergen Hoeller* @author Stephane Nicoll* @since 3.1*/public interface Cache {   /**    * 返回快取的名稱    */ String getName(); /**    * 返回真實的快取    */ Object getNativeCache(); /**    * 返回給定key在快取中對應的value的包裝類ValueWrapper的物件    * 如何快取中不存在key的鍵值對則返回null    * 如果快取中key的對應值為null,則返回一個ValueWrapper包裝類的物件    * @param key     * @return     * @see #get(Object, Class)    */ ValueWrapper get(Object key); /**    * 返回快取中key對應的value,並將其值強轉為type    * @param key     * @param type     * @return     * @throws 如果快取著找到key對應的值,但是將其轉化為指定的型別失敗將丟擲IllegalStateException    * @since 4.0    * @see #get(Object)    */ <T> T get(Object key, Class<T> type); /**    * 如果快取中存在key對應的value則return ,如果不存在則呼叫valueLoader生成一個並將其放入快取    * @param key    * @return     * @throws 如果{@code valueLoader} 丟擲異常則將其包裝成ValueRetrievalException丟擲    * @since 4.3    */ <T> T get(Object key, Callable<T> valueLoader); /**    *將資料對(key,value)放入快取(如果快取中存在這對資料,原來的資料會被覆蓋)    * @param key     * @param value     */ void put(Object key, Object value); /**    * 如果快取中不存在(key,value)對映對,則將其放入快取

相關推薦

快取註解驅動快取 Spring cache介紹

在我們平常的工作當中,有好多場景需要用到快取技術,如redis,ehcache等來加速資料的訪問。作為淺談快取的第一篇筆者不想談論具體的快取技術,我更想介紹一下Spring中每次閱讀都會使我心中泛起波瀾的一個東西,那就是基於註解的快取技術。我們先看Spring參考文件中的一句

Spring cache資料(三。註釋驅動Spring cache 快取介紹

概述 Spring 3.1 引入了激動人心的基於註釋(annotation)的快取(cache)技術,它本質上不是一個具體的快取實現方案(例如 EHCache 或者 OSCache),而是一個對快取使用的抽象,通過在既有程式碼中新增少量它定義的各種 annotation,即能夠達到快取

圖片載入的三級快取(一)

之前被人問及過,圖片的三級快取是什麼啊,來給我講講,圖片三級快取,好高大尚的名字,聽著挺厲害,應該是很厲害的技術,當時不會啊,也就沒什麼了,沒有說出來唄,前一階端用到了BitmapUtils的圖片快取框架,索性就自己找些知識點來研究一些圖片的三級快取是什麼吧。真

Oracle資料庫中的快取-Cache (IO)

Cache和Buffer是兩個不同的概念,簡單的說,Cache是加速“讀”,而buffer是緩衝“寫”,前者解決讀的問題,儲存從磁碟上讀出的資料,後者是解決寫的問題,儲存即將要寫入到磁碟上的資料。在很多情況下,這兩個名詞並沒有嚴格區分,常常把讀寫混合型別稱為buffer

快取篇(三)- Spring Cache框架

前兩篇我們講了Guava和JetCache,他們都是快取的具體實現,今天給大家分析一下Spring框架本身對這些快取具體實現的支援和融合,使用Spring Cache將大大的減少我們的Spring專案中快取使用的複雜度,提高程式碼可讀性。本文將從以下幾個方面來認識Spring Cache框架 背

Linux PCI裝置驅動

轉自 http://www.uml.org.cn/embeded/201205152.asp 淺談Linux PCI裝置驅動(一) 要弄清楚Linux PCI裝置驅動,首先要明白,所謂的Linux

Linux PCI裝置驅動(下)

我們在 淺談Linux PCI裝置驅動(上)中(以下簡稱 淺談(一) )介紹了PCI的配置暫存器組,而Linux PCI初始化就是使用了這些暫存器來進行的。後面我們會舉個例子來說明Linux PCI裝置驅動的主要工作內容(不是全部內容),這裡只做文字性的介紹而不會涉及具體程式碼的分析,因為要

Linux PCI裝置驅動(上)

有學員建議寫寫PCI驅動,今天就找到一篇,文章很長,這基本上是全網對PCI講的比較詳細的部落格了,分成上下兩篇,這是上部分,未完待續。 要弄清楚Linux PCI裝置驅動,首先要明白,所謂的Linux PCI裝置驅動實際包括Linux PCI裝置驅動和裝置本身驅動兩部分。 不知道讀者

Struts2的屬性驅動和模型驅動

一直在用Struts2實現MVC,因為相比於Struts1中大量使用request.getparameter在頁面上獲取值,struts2則提供了屬性驅動和模型驅動處理了這一問題。通過這兩個驅動,我們

使用 Spring 2.5 基於註解驅動Spring MVC

概述 繼 Spring 2.0 對 Spring MVC 進行重大升級後,Spring 2.5 又為 Spring MVC 引入了註解驅動功能。現在你無須讓 Controller 繼承任何介面,無需在 XML 配置檔案中定義請求和 Controller 的對映

linux的LCD驅動

轉載請註明出處:http://blog.csdn.net/ruoyunliufeng/article/details/24499251 一.硬體基礎     1.硬體框圖     2.LCD控制器     瞭解硬體最直接的辦法就是看手冊,在這裡我只會簡單介紹下LCD

Linux PCI裝置驅動(二)

我們在 淺談Linux PCI裝置驅動(一)中(以下簡稱 淺談(一) )介紹了PCI的配置暫存器組,而Linux PCI初始化就是使用了這些暫存器來進行的。後面我們會舉個例子來說明Linux PCI裝置驅動的主要工作內容(不是全部內容),這裡只做文字性的介紹而不會涉及具體程

SpringBoot核心註解原理

SpringBoot核心註解原理 今天跟大家來探討下SpringBoot的核心註解@SpringBootApplication以及run方法,理解下springBoot為什麼不需要XML,達到零配置 首先我們先來看段程式碼 @SpringBootApplication public cla

一個快取使用的思考:Spring Cache VS Caffeine 原生 API

最近在學習本地快取發現,在 Spring 技術棧的開發中,既可以使用 Spring Cache 的註解形式操作快取,也可用各種快取方案的原生 API。那麼是否 Spring 官方提供的就是最合適的方案呢?那麼本文將通過一個案例來為你揭曉。 Spring Cache Since version 3.1, th

DDD(領域驅動設計)

![領域驅動設計](https://img2020.cnblogs.com/blog/1133883/202011/1133883-20201114163143505-517119101.png) # 背景(Why) 2003 年埃裡克·埃文斯(Eric Evans)發表了《領域驅動設計》(Domain-

ArcGIS GP服務 :一、框架介紹

GP全稱是Geoprocessing,可以對原有的功能進行補充,也就是說只要在桌面上實現的事情,在Server都可以實現。 首先來看一下Geoprocessing 框架,我使用的是ArcGIS10.2版本。 第一、ArcToolBox,自帶的系統的工具,只能複製和貼上,不能手工的建立。

Android之Activity Decor View建立流程介紹

6 Activity DecorView建立流程介紹 上頭已經完整的介紹了Activity的啟動流程,Activity是如何繫結Window,Window的décor view是如何通過ViewRootImpl與WMS建立關聯的,也就是說,整個框架已經有了,唯一缺的就是Ac

Android之Activity觸控事件傳輸機制介紹

8 Activity觸控事件傳輸機制介紹 當我們觸控式螢幕幕的時候,程式會收到對應的觸控事件,這個事件是在app端去讀取的嗎?肯定不是,如果app能讀取,那會亂套的,所以app不會有這個許可權,系統按鍵的讀取以及分發都是通過WindowManagerService來完成

spring中AOP以及spring中AOP的註解方式

早就 好的 面向 XML ram ati alt 返回 增強   AOP(Aspect Oriented Programming):AOP的專業術語是"面向切面編程" 什麽是面向切面編程,我的理解就是:在不修改源代碼的情況下增強功能.好了,下面在講述aop註解方式的情況下順

分散式鎖--基於快取(Redis,memcached,tair)實現篇

淺談分散式鎖--基於快取(Redis,memcached,tair)實現篇: 一、Redis分散式鎖 1、Redis實現分散式鎖的原理:     1.利用setnx命令,即只有在某個key不存在情況才能set成功該key,這樣就達到了多個程序併發去set