一、springBoot與緩存
1. JSR107
Java Caching定義了5個核心接口,分別是CachingProvider
, CacheManager
,Cache
, Entry
和 Expiry
CachingProvider
定義了創建、配置、獲取、管理和控制多個CacheManager
,一個應用可以在運行期訪問多個CachingProvider
CacheManager
定義了創建、配置、獲取、管理和控制多個唯一命名的Cache
,這些Cache
存在於CacheManager
的上下文中,一個CacheManager
僅被一個CachingProvider
所擁有Cache
是一個類似Map
的數據結構並臨時存儲以Key
爲索引的值,一個Cache
僅被一個CacheManager
所擁有,在後面的源碼分析中我們可以發現Cache其實就是一個HashMap爲主的一個類Entry
是一個存儲在Cache
中的key-value
對這裏的Entry和Map中的內部類是一個意思,所以在這裏也更能確定Cache就是一個以Map爲主要數據結構的類Expiry
每一個存儲在Cache
中的條目有一個定義的有效期。一旦超過這個時間,條目爲過期的狀態。一旦過期,條目將不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy
設置。
可以用這樣一幅圖總結一下:
2.spring的緩存抽象
spring
提供了一系列的註解來方便我們的開發,今天我們主要是進行源代碼的分析,我們以@Cacheable
註解爲例;
二、開始前的準備
緩存其實就是在應用程序和數據庫之間加的一箇中間層,用來加速數據的檢索;
所以我們今天的示例要先在數據庫中創建一個User
表:
1.創建User表
2.創建一個springBoot應用
在idea
上創建springBoot
是很方便的這裏就不演示了;
這些是我導入的依賴,Edit Starters
是一個idea
插件可以讓你的springBoot項目在創建以後以勾選的方式添加依賴;
3.配置springBoot項目
主要就是一個開啓緩存的註解
這裏最後的debug=true
開啓以後就可以看到sprngBoot程序啓動的時候自動配置的信息;
接下來就是代碼:
首先要有一個User
實體類:
@Data
public class User {
private int id;
private String name;
}
這裏我使用了一個idea
的lombok
插件,可以讓你不用再去寫那些get set
方法了;
Mapper:這裏我們爲了方便採用註解開發(但是我本人還是喜歡xml方式去寫sql)
@Mapper
public interface UserMapper {
@Select("select *from user where id = #{id}")
User getUserById(int id);
@Update("update user set name = #{name} where id = #{id}")
void updateUser(User user);
@Delete("delete from user where id = #{id}")
void deleteUser(int id);
@Insert("insert into user values(#{id}, #{name})")
void insertUser(User user);
}
這裏我們就有了一個基本的增刪改查的接口;
Service層:這裏應該是一個接口對應一個實現,爲了方便我們就不寫接口了
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "user", key = "#root.methodName+'['+#id+']'")
public User getUser(int id) {
System.out.println("查詢");
User user = userMapper.getUserById(id);
return user;
}
}
應爲我們是一個web項目,所以我們還需要一個Controller:
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/getUser/{id}")
public User getUser(@PathVariable("id") int id) {
return userService.getUser(id);
}
}
4.演示
我們啓動項目:
第一次訪問後臺並沒有緩存所以我們訪問了數據庫有sql
語句
當我們第二次訪問這個url
我們會發現我們並沒有去訪問數據庫,甚至沒有去調用UserService
中的getUser
方法
三、原理分析
在springBoot中所有的自動配置都是...AutoConfiguration
所以我們去搜CacheAutoConfiguration
這個類
在這個類中有一個靜態內部類CacheConfigurationImportSelector
他有一個selectImport
方法是用來給容器中添加一些緩存要用的組件;
我們在這裏打上斷點,debug調試一下看看imports
中有哪些緩存組件
我們可以看到這裏總共有十個緩存組件;我們隨便去看一個
會發現在他的註解上表明瞭什麼時候使用這個組件;
那麼接下來我們來看看springBoot默認使用的緩存組件是什麼;
(這裏就不把所有的查詢結果放出了)
我們最終會發現只有SimpleCacheConfiguration
是被使用的,所以也就說明默認情況下使用SimpleCacheConfiguration
;
然後我們進入到SimpleCacheConfiguration
中:
我們會發現他給springBoot容器添加了一個bean,是一個CacheManager
;
ConcurrentMapCacheManager
實現了CacheManager
接口
這裏要說一個@Nullable
註解這個註解是說傳入的參數可以爲null
在寫程序的時候你可以定義是否可爲空指針。通過使用像@NotNull和@Nullable之類的annotation來聲明一個方法是否是空指針安全的。現代的編譯器、IDE或者工具可以讀此annotation並幫你添加忘記的空指針檢查,或者向你提示出不必要的亂七八糟的空指針檢查。IntelliJ和findbugs已經支持了這些annotation。這些annotation同樣是JSR 305的一部分,但即便IDE或工具中沒有,這個annotation本身可以作爲文檔。看到@NotNull和@Nullable,程序員自己可以決定是否做空指針檢查。順便說一句,這個技巧對Java程序員來說相對比較新,要採用需要一段時間。
getCache
方法使用了雙重鎖校驗(這種驗證機制一般是用在單例模式中)
我們可以看到如果沒有Cache
會調用
cache = this.createConcurrentMapCache(name);
這個方法會創建一個ConcurrentMapCache
這個就是我們說的Cache
;
在這個類裏面有這樣三個屬性;
private final ConcurrentMap<Object, Object> store;
這個就是前文中的Entry
用來存放鍵值對;
在ConcurrentMapCache
中我們會看到一些操作Cache
的方法我選幾個重要的
lookup
方法是根據key來找value的;
put
方法顧名思義是用來添加鍵值對的;
到這裏基本上就結束了,接下來我們來詳細分析一下@Cacheable
註解
四、@Cacheable分析
我們在上述的兩個方法上打上斷點;debug運行springBoot;
訪問getUser接口;
我們會發現他來到了lookup
方法這裏,說明註解的執行在被註解的方法前,然後這裏我們會返回null;
我們放行到下一個註解會發現;調用了put方法
添加了Cache;然後我們第二次對getUser接口發起請求我們會發現打斷點的兩個方法沒有被執行
因爲在這裏cache不爲null了,直接被返回了;
以上就是我對於springBoot緩存的理解