原創文章,轉載請標註出處:《SpringBoot基礎系列-SpringCache使用》html
SpringCache自己是一個緩存體系的抽象實現,並無具體的緩存能力,要使用SpringCache還須要配合具體的緩存實現來完成。java
雖然如此,可是SpringCache是全部Spring支持的緩存結構的基礎,並且全部的緩存的使用最後都要歸結於SpringCache,那麼一來,要想使用SpringCache,仍是要仔細研究一下的。spring
SpringCache緩存功能的實現是依靠下面的這幾個註解完成的。編程
@CacheConfig:定義公共設置,位於class之上數組
@EnableCaching @Configuration public class CacheConfig { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default"))); return cacheManager; } }
注意:在SpringBoot中使用SpringCache能夠由自動配置功能來完成CacheManager的註冊,SpringBoot會自動發現項目中擁有的緩存系統,而註冊對應的緩存管理器,固然咱們也能夠手動指定。緩存
使用該註解和以下XML配置具備同樣的效果:app
<beans> <cache:annotation-driven/> <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager> <property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean> <property name="name" value="default"/> </bean> </set> </property> </bean> </beans>
下面來看看@EnableCaching的源碼:less
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(CachingConfigurationSelector.class) public @interface EnableCaching { // 用於設置使用哪一種代理方式,默認爲基於接口的JDK動態代理(false), // 設置爲true,則使用基於繼承的CGLIB動態代理 boolean proxyTargetClass() default false; // 用於設置切面織入方式(設置面向切面編程的實現方式), // 默認爲使用動態代理的方式織入,固然也能夠設置爲ASPECTJ的方式來實現AOP AdviceMode mode() default AdviceMode.PROXY; // 用於設置在一個切點存在多個通知的時候各個通知的執行順序,默認爲最低優先級, // 其中數字卻大優先級越低,這裏默認爲最低優先級,int LOWEST_PRECEDENCE = // Integer.MAX_VALUE;,倒是整數的最大值 int order() default Ordered.LOWEST_PRECEDENCE; } public enum AdviceMode { PROXY, ASPECTJ } public interface Ordered { int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; int LOWEST_PRECEDENCE = Integer.MAX_VALUE; int getOrder(); }
由上面的源碼能夠看出,緩存功能是依靠AOP來實現的。ide
該註解用於標註於方法之上用於標識該方法的返回結果須要被緩存起來,標註於類之上標識該類中全部方法均須要將結果緩存起來。函數
該註解標註的方法每次被調用前都會觸發緩存校驗,校驗指定參數的緩存是否已存在(已發生過相同參數的調用),若存在,直接返回緩存結果,不然執行方法內容,最後將方法執行結果保存到緩存中。
@Service @Log4j2 public class AnimalService { @Autowired private AnimalRepository animalRepository; //... // @Cacheable("animalById") @Cacheable(value = "animalById", key = "#id") public ResponseEntity<Animal> getAnimalById(final int id){ return ResponseEntity.ok(animalRepository.selectById(id)); } //... }
上面的實例中兩個@Cacheable配置效果實際上是同樣的,其中value指定的緩存的名稱,它和另外一個方法cacheName效果同樣,通常來講這個緩存名稱必需要有,由於這個是區別於其餘方法的緩存的惟一方法。
這裏咱們介紹一下緩存的簡單結構,在緩存中,每一個這樣的緩存名稱的名下都會存在着多個緩存條目,這些緩存條目對應在使用不一樣的參數調用當前方法時生成的緩存,全部一個緩存名稱並非一個緩存,而是一系列緩存。
另外一個key用於指定當前方法的緩存保存時的鍵的組合方式,默認的狀況下使用全部的參數組合而成,這樣能夠有效區分不一樣參數的緩存。固然咱們也能夠手動指定,指定的方法是使用SPEL表達式。
這裏我麼來簡單看看其源碼,瞭解下其餘幾個方法的做用:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { // 用於指定緩存名稱,與cacheNames()方法效果一致 @AliasFor("cacheNames") String[] value() default {}; // 用於指定緩存名稱,與value()方法效果一致 @AliasFor("value") String[] cacheNames() default {}; // 用於使用SPEL手動指定緩存鍵的組合方式,默認狀況使用全部的參數來組合成鍵,除非自定義了keyGenerator。 // 使用SPEL表達式能夠根據上下文環境來獲取到指定的數據: // #root.method:用於獲取當前方法的Method實例 // #root.target:用於獲取當前方法的target實例 // #root.caches:用於獲取當前方法關聯的緩存 // #root.methodName:用於獲取當前方法的名稱 // #root.targetClass:用於獲取目標類類型 // #root.args[1]:獲取當前方法的第二個參數,等同於:#p1和#a1和#argumentName String key() default ""; // 自定義鍵生成器,定義了該方法以後,上面的key方法自動失效,這個鍵生成器是: // org.springframework.cache.interceptor.KeyGenerator,這是一個函數式接口, // 只有一個generate方法,咱們能夠經過自定義的邏輯來實現自定義的key生成策略。 String keyGenerator() default ""; // 用於設置自定義的cacheManager(緩存管理器),能夠自動生成一個cacheResolver // (緩存解析器),這一下面的cacheResolver()方法設置互斥 String cacheManager() default ""; // 用於設置一個自定義的緩存解析器 String cacheResolver() default ""; // 用於設置執行緩存的條件,若是條件不知足,方法返回的結果就不會被緩存,默認無條件所有緩存。 // 一樣使用SPEL來定義條件,可使用的獲取方式同key方法。 String condition() default ""; // 這個用於禁止緩存功能,若是設置的條件知足,就不執行緩存結果,與上面的condition不一樣之處在於, // 該方法執行在當前方法調用結束,結果出來以後,所以,它除了可使用上面condition所能使用的SPEL // 表達式以外,還可使用#result來獲取方法的執行結果,亦便可以根據結果的不一樣來決定是否緩存。 String unless() default ""; // 設置是否對多個針對同一key執行緩存加載的操做的線程進行同步,默認不一樣步。這個功能須要明確肯定所 // 使用的緩存工具支持該功能,不然不要濫用。 boolean sync() default false; }
如何自定義一個KeyGenerator呢?
public class AnimalKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder("animal-"); sb.append(target.getClass().getSimpleName()).append("-").append(method.getName()).append("-"); for (Object o : params) { String s = o.toString(); sb.append(s).append("-"); } return sb.deleteCharAt(sb.lastIndexOf("-")).toString(); } }
該註解用於更新緩存,不管結果是否已經緩存,都會在方法執行結束插入緩存,至關於更新緩存。通常用於更新方法之上。
@Service @Log4j2 public class AnimalService { @Autowired private AnimalRepository animalRepository; //... @CachePut(value = "animalById", key = "#animal.id") public ResponseEntity<Animal> updateAnimal(final Animal animal){ Wrapper<Animal> animalWrapper = new UpdateWrapper<>(); ((UpdateWrapper<Animal>) animalWrapper).eq("id",animal.getId()); animalRepository.update(animal, animalWrapper); return ResponseEntity.ok(this.getAnimalById(animal.getId())); } //... }
這裏指定更新緩存,value一樣仍是緩存名稱,這裏更新的是上面查詢操做的同一緩存,並且key設置爲id也與上面的key設置對應。
如此設置以後,每次執行update方法時都會直接執行方法內容,而後將返回的結果保存到緩存中,若是存在相同的key,直接替換緩存內容執行緩存更新。
下面來看看源碼:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CachePut { // 同上 @AliasFor("cacheNames") String[] value() default {}; // 同上 @AliasFor("value") String[] cacheNames() default {}; // 同上 String key() default ""; // 同上 String keyGenerator() default ""; // 同上 String cacheManager() default ""; // 同上 String cacheResolver() default ""; // 同上 String condition() default ""; // 同上 String unless() default ""; }
只有一點要注意:這裏的設置必定要和執行緩存保存的方法的@Cacheable的設置一致,不然沒法準確更新。
該註解主要用於刪除緩存操做。
@Service @Log4j2 public class AnimalService { @Autowired private AnimalRepository animalRepository; //... @CacheEvict(value = "animalById", key = "#id") public ResponseEntity<Integer> deleteAnimalById(final int id){ return ResponseEntity.ok(animalRepository.deleteById(id)); } //... }
簡單明瞭,看看源碼:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CacheEvict { // 同上 @AliasFor("cacheNames") String[] value() default {}; // 同上 @AliasFor("value") String[] cacheNames() default {}; // 同上 String key() default ""; // 同上 String keyGenerator() default ""; // 同上 String cacheManager() default ""; // 同上 String cacheResolver() default ""; // 同上 String condition() default ""; // 這個設置用於指定當前緩存名稱名下的全部緩存是否所有刪除,默認false。 boolean allEntries() default false; // 這個用於指定刪除緩存的操做是否在方法調用以前完成,默認爲false,表示先調用方法,在執行緩存刪除。 boolean beforeInvocation() default false; }
這個註解用於組個多個緩存操做,包括針對不用緩存名稱的相同操做等,源碼:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Caching { // 用於指定多個緩存設置操做 Cacheable[] cacheable() default {}; // 用於指定多個緩存更新操做 CachePut[] put() default {}; // 用於指定多個緩存失效操做 CacheEvict[] evict() default {}; }
簡單用法:
@Service @Log4j2 public class AnimalService { @Autowired private AnimalRepository animalRepository; //... @Caching( evict = { @CacheEvict(value = "animalById", key = "#id"), @CacheEvict(value = "animals", allEntries = true, beforeInvocation = true) } ) public ResponseEntity<Integer> deleteAnimalById(final int id){ return ResponseEntity.ok(animalRepository.deleteById(id)); } @Cacheable("animals") public ResponseEntity<Page<Animal>> getAnimalPage(final Animal animal, final int pageId, final int pageSize){ Page<Animal> page = new Page<>(); page.setCurrent(pageId); page.setSize(pageSize); return ResponseEntity.ok((Page<Animal>) animalRepository.selectPage(page,packWrapper(animal, WrapperType.QUERY))); } //... }
該註解標註於類之上,用於進行一些公共的緩存相關配置。源碼爲:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CacheConfig { // 設置統一的緩存名,適用於整個類中的方法所有是針對同一緩存名操做的狀況 String[] cacheNames() default {}; // 設置統一個鍵生成器,免去了每一個緩存設置中單獨設置 String keyGenerator() default ""; // 設置統一個自定義緩存管理器 String cacheManager() default ""; // 設置統一個自定義緩存解析器 String cacheResolver() default ""; }