Dummy CacheManager 的配置和做用java
EhCache 是一個純Java的進程內緩存框架,具備快速、精幹等特色,是Hibernate中默認CacheProvider。Ehcache是一種普遍使用的開源Java分佈式緩存。主要面向通用緩存,Java EE和輕量級容器。它具備內存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個gzip緩存servlet過濾器,支持REST和SOAP api等特色。redis
Spring 提供了對緩存功能的抽象:即容許綁定不一樣的緩存解決方案(如Ehcache),但自己不直接提供緩存功能的實現。它支持註解方式使用緩存,很是方便。算法
能夠單獨使用,通常在第三方庫中被用到的比較多(如mybatis、shiro等)ehcache 對分佈式支持不夠好,多個節點不能同步,一般和redis一塊使用spring
ehcache直接在jvm虛擬機中緩存,速度快,效率高;可是緩存共享麻煩,集羣分佈式應用不方便。數據庫
redis是經過socket訪問到緩存服務,效率比ecache低,比數據庫要快不少,
處理集羣和分佈式緩存方便,有成熟的方案。若是是單個應用或者對緩存訪問要求很高的應用,用ehcache。若是是大型系統,存在緩存共享、分佈式部署、緩存內容很大的,建議用redis。編程
ehcache也有緩存共享方案,不過是經過RMI或者Jgroup多播方式進行廣播緩存通知更新,緩存共享複雜,維護不方便;簡單的共享能夠,可是涉及到緩存恢復,大數據緩存,則不合適。api
即從緩存中讀取數據的次數 與 總讀取次數的比率,命中率越高越好:緩存
命中率 = 從緩存中讀取次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])mybatis
Miss率 = 沒有從緩存中讀取的次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])app
這是一個很是重要的監控指標,若是作緩存必定要健康這個指標來看緩存是否工做良好;
移除策略,即若是緩存滿了,從緩存中移除數據的策略;常見的有LFU、LRU、FIFO:
FIFO(First In First Out):先進先出算法,即先放入緩存的先被移除;
LRU(Least Recently Used):最久未使用算法,使用時間距離如今最久的那個被移除;
LFU(Least Frequently Used):最近最少使用算法,必定時間段內使用次數(頻率)最少的那個被移除;
存活期,即從緩存中建立時間點開始直到它到期的一個時間段(無論在這個時間段內有沒有訪問都將過時)
空閒期,即一個數據多久沒被訪問將從緩存中移除的時間。
Cache接口默認提供了以下實現:
ConcurrentMapCache:使用java.util.concurrent.ConcurrentHashMap實現的Cache;
GuavaCache:對Guava com.google.common.cache.Cache進行的Wrapper,須要Google Guava 12.0或更高版本,@since spring 4;
EhCacheCache:使用Ehcache實現
JCacheCache:對javax.cache.Cache進行的wrapper,@since spring 3.2;spring4將此類更新到JCache 0.11版本;
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.2</version> </dependency>
默認狀況下Ehcache會自動加載classpath根目錄下名爲ehcache.xml文件,也能夠將該文件放到其餘地方在使用時指定文件的位置
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <!--表示硬盤上保存緩存的位置。默認是臨時文件夾。--> <!--<diskStore path="java.io.tmpdir"/>--> <diskStore path="/logs/ehcache"/> <!--默認緩存配置,若是類沒有作特定的設置,則使用這裏配置的緩存屬性。 maxElementsInMemory - 設置緩存中容許保存的最大對象(pojo)數量 eternal -設置對象是否永久保存,若是爲true,則緩存中的數據永遠不銷燬,一直保存。 timeToIdleSeconds - 設置空閒銷燬時間。只有eternal爲false時才起做用。表示從如今到上次訪問時間若是超過這個值,則緩存數據銷燬 timeToLiveSeconds-設置活動銷燬時間。表示從如今到緩存建立時間若是超過這個值,則緩存自動銷燬 overflowToDisk - 設置是否在超過保存數量時,將超出的部分保存到硬盤上。--> <defaultCache maxElementsInMemory="1500" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="300" overflowToDisk="true"/> <cache name="demoCache" maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="true"/>
<!-- helloworld緩存 -->
<cache name="HelloWorldCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
<cache name="UserCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
public class MyEhcache { public static void main(String[] args) { User user=new User("cache","123456","cache cache","","male",20); // 獲取緩存管理器 CacheManager manager = CacheManager.create("src/main/resources/config/ehcache.xml"); // 根據配置文件得到Cache實例 Cache demo = manager.getCache("demoCache"); // 清空Cache中的全部元素 demo.removeAll(); demo.put(new Element("hello","world")); demo.put(new Element(user.getUserName(),user)); Element e=demo.get(user.getUserName()); System.out.println(demo.get("hello").getObjectValue()); System.out.println(((User)e.getObjectValue()).getUserRealName()); //卸載緩存管理器 // manager.shutdown(); } }
diskStore : ehcache支持內存和磁盤兩種存儲
defaultCache : 默認的緩存
cache :自定的緩存,當自定的配置不知足實際狀況時能夠經過自定義(能夠包含多個cache節點)
name : 緩存的名稱,能夠經過指定名稱獲取指定的某個Cache對象
maxElementsInMemory :內存中容許存儲的最大的元素個數,0表明無限個
clearOnFlush:內存數量最大時是否清除。
eternal :設置緩存中對象是否爲永久的,若是是,超時設置將被忽略,對象從不過時。根據存儲數據的不一樣,例如一些靜態不變的數據如省市區等能夠設置爲永不過期
timeToIdleSeconds : 設置對象在失效前的容許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。
timeToLiveSeconds :緩存數據的生存時間(TTL),也就是一個元素從構建到消亡的最大時間間隔值,這隻能在元素不是永久駐留時有效,若是該值是0就意味着元素能夠停頓無窮長的時間。
overflowToDisk :內存不足時,是否啓用磁盤緩存。
maxEntriesLocalDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。
maxElementsOnDisk:硬盤最大緩存個數。
diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每一個Cache都應該有本身的一個緩衝區。
diskPersistent:是否在VM重啓時存儲硬盤的緩存數據。默認值是false。
diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
Cache cache = manager.getCache("mycache"); CacheConfiguration config = cache.getCacheConfiguration(); config.setTimeToIdleSeconds(60); config.setTimeToLiveSeconds(120); config.setmaxEntriesLocalHeap(10000); config.setmaxEntriesLocalDisk(1000000);
// 能夠本身建立一個Cache對象添加到CacheManager中 public void addCache(Cache cache); public synchronized void removeCache(String cacheName);
Cache: 一個Cache能夠包含多個Element,並被CacheManager管理。它實現了對緩存的邏輯行爲
Element:須要緩存的元素,它維護着一個鍵值對, 元素也能夠設置有效期,0表明無限制
獲取CacheManager的方式:
能夠經過create()或者newInstance()方法或重載方法來建立獲取CacheManager的方式:
public static CacheManager create(); public static CacheManager create(String configurationFileName); public static CacheManager create(InputStream inputStream); public static CacheManager create(URL configurationFileURL); public static CacheManager newInstance();
Ehcache的CacheManager構造函數或工廠方法被調用時,會默認加載classpath下名爲ehcache.xml的配置文件。
若是加載失敗,會加載Ehcache jar包中的ehcache-failsafe.xml文件,這個文件中含有簡單的默認配置。
// CacheManager.create() == CacheManager.create("./src/main/resources/ehcache.xml") // 使用Ehcache默認配置新建一個CacheManager實例 CacheManager cacheManager = CacheManager.create(); cacheManager = CacheManager.newInstance(); cacheManager = CacheManager.newInstance("./src/main/resources/ehcache.xml"); InputStream inputStream = new FileInputStream(new File("./src/main/resources/ehcache.xml")); cacheManager = CacheManager.newInstance(inputStream); String[] cacheNames = cacheManager.getCacheNames(); // [HelloWorldCache]
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd"> <description>ehcache緩存配置管理文件</description>
<!-- 啓用緩存註解開關,這個是必須的,不然註解不會生效。另外,該註解必定要聲明在spring主配置文件中才會生效 -->
<!-- 自定義CacheKeyGenerator,當緩存沒有定義key的時候有效-->
<cache:annotation-driven cache-manager="cacheManager" key-generator="cacheKeyGenerator"/>
<bean id="cacheKeyGenerator" class="com.esther.code.util.CacheKeyGenerator"/>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcache"/> </bean>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
</bean>
</beans>
自定義緩存MyCacheable:
import org.springframework.cache.annotation.Cacheable; import java.lang.annotation.*; /** * @author * 2018-04-23 18:38 * $DESCRIPTION} */ @Retention(RetentionPolicy.RUNTIME)//註解會在class中存在,運行時可經過反射獲取 @Target({ElementType.METHOD,ElementType.TYPE})//目標是方法 @Documented//文檔生成時,該註解將被包含在javadoc中,可去掉 @Cacheable(value = "UserCache") public @interface MyCacheable { }
CacheKeyGenerator :
import com.google.common.hash.Hashing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.nio.charset.Charset; /** * @author * 2018-04-23 16:01 * $DESCRIPTION} */ @Component("cacheKeyGenerator ") public class CacheKeyGenerator implements KeyGenerator { private Logger log= LoggerFactory.getLogger(getClass()); // custom cache key public static final int NO_PARAM_KEY = 0; public static final int NULL_PARAM_KEY = 53; @Override public Object generate(Object target, Method method, Object... params) { StringBuilder key = new StringBuilder(); key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append("("); if (params.length == 0) { return key.append(NO_PARAM_KEY).toString(); } for (Object param : params) { if (param == null) { log.warn("input null param for Spring cache, use default key={}", NULL_PARAM_KEY); key.append(NULL_PARAM_KEY); } else if (ClassUtils.isPrimitiveArray(param.getClass())) { int length = Array.getLength(param); for (int i = 0; i < length; i++) { key.append(Array.get(param, i)); key.append('|'); } } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) { key.append(param); } else { log.warn("Using an object as a cache key may lead to unexpected results. " + "Either use @Cacheable(key=..) or implement CacheKey. Method is " + target.getClass() + "#" + method.getName()); key.append(param.hashCode()); } key.append(','); } // 去除最後一個逗號 key.deleteCharAt(key.length()-1); key.append(")"); String finalKey = key.toString(); long cacheKeyHash = Hashing.murmur3_128().hashString(finalKey, Charset.defaultCharset()).asLong(); log.debug("using cache key={} hashCode={}", finalKey, cacheKeyHash); return key.toString(); } }
測試接口和實現類:
public interface IEhcacheService { // 測試失效狀況,有效期爲5秒 public String getTimestamp(String param); public String getDataFromDB(String key); public void removeDataAtDB(String key); public String refreshData(String key); public User findUserById(Integer userId); public boolean isReserved(Integer userId); public void removeUser(Integer userId); public void removeAllUser(); // @Caching註解實現 public String testCaching(String param); // 自定義註解實現 User get(Integer userId); }
import com.esther.code.annotation.MyCacheable; import com.esther.code.api.IEhcacheService; import com.esther.code.api.IUserService; import com.esther.code.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; /** * @author * 2018-04-23 10:59 * ehcache與spring註解實現 */ @Service("ehcacheService") public class EhcacheServiceImpl implements IEhcacheService { @Autowired private IUserService userService; // value的值和ehcache.xml中的配置保持一致 @Cacheable(value = "HelloWorldCache", key = "#param") @Override public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } @Cacheable(value = "HelloWorldCache", key = "#key") @Override public String getDataFromDB(String key) { System.out.println("從數據庫中獲取數據..."); return key + ":" + String.valueOf(Math.round(Math.random() * 1000000)); } @CacheEvict(value = "HelloWorldCache", key = "#key") @Override public void removeDataAtDB(String key) { System.out.println("從數據庫中刪除數據"); } // 使用@CachePut標註的方法在執行前不會去檢查緩存中是否存在以前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。 // 每次都會執行方法,並將結果存入指定的緩存中 @CachePut(value = "HelloWorldCache", key = "#key") @Override public String refreshData(String key) { System.out.println("模擬從數據庫中加載數據"); return key + "::" + String.valueOf(Math.round(Math.random()*1000000)); } // ------------------------------------------------------------------------ /** * 沒有定義key的話,使用xml文件中配置的CacheKeyGenerator * * unless過濾方法返回值。當方法返回空值時,就不會被緩存起來 * condition:對傳入的參數進行篩選. 觸發條件,只有知足條件的狀況纔會加入緩存,默認爲空,既表示所有都加入緩存,支持SpEL。 * expire:過時時間,單位爲秒。 * beforeInvocation: 是否在方法執行前就清空,缺省爲 false,若是指定爲 true,則在方法尚未執行的時候就清空緩存,缺省狀況下,若是方法執行拋出異常,則不會清空緩存 * @param userId * @return */ @Override @Cacheable(value = "UserCache", condition = "#userId<=2", unless = "#result==null") public User findUserById(Integer userId) { System.out.println("模擬從數據庫中查詢數據"); return userService.selectByPrimaryKey(userId); } /** * condition:對傳入的參數進行篩選. 觸發條件,只有知足條件的狀況纔會加入緩存,默認爲空,既表示所有都加入緩存,支持SpEL。 * expire:過時時間,單位爲秒。 * @param userId * @return */ @Override @Cacheable(value = "UserCache", condition = "#userId<=2") public boolean isReserved(Integer userId) { System.out.println("UserCache:" + userId); return false; } //清除掉UserCache中某個指定key的緩存 @Override @CacheEvict(value = "UserCache", key = "'user:' + #userId") public void removeUser(Integer userId) { System.out.println("UserCache remove:" + userId); } //清除掉UserCache中所有的緩存 @Override @CacheEvict(value = "UserCache", allEntries = true) public void removeAllUser() { System.out.println("UserCache delete all"); } /** * 多個緩存組合的@Caching * @param param * @return */ @Override @Caching(evict = @CacheEvict(value = "UserCache",allEntries=true),cacheable = {@Cacheable("HelloWorldCache")}) public String testCaching(String param){ System.out.println("UserCache delete all"); Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } /** * 自定義緩存MyCacheable * @param userId * @return */ @Override @MyCacheable public User get(Integer userId) { return userService.selectByPrimaryKey(userId); } }
有的時候,咱們在代碼遷移、調試或者部署的時候,剛好沒有 cache 容器,好比 memcache 還不具有條件,h2db 尚未裝好等,若是這個時候你想調試代碼,豈不是要瘋掉?這裏有一個辦法,在不具有緩存條件的時候,在不改代碼的狀況下,禁用緩存。
方法就是修改 spring*.xml 配置文件,設置一個找不到緩存就不作任何操做的標誌位,以下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd"> <description>ehcache緩存配置管理文件</description> <!-- 啓用緩存註解開關,這個是必須的,不然註解不會生效。另外,該註解必定要聲明在spring主配置文件中才會生效 --> <!-- 自定義CacheKeyGenerator,當緩存沒有定義key的時候有效-->
<!--mode屬性,可選值有proxy和aspectj。默認是使用proxy。
當mode爲proxy時,只有緩存方法在外部被調用的時候Spring Cache纔會發生做用,
這也就意味着若是一個緩存方法在其聲明對象內部被調用時Spring Cache是不會發生做用的。而mode爲aspectj時就不會有這種問題。
另外使用proxy時,只有public方法上的@Cacheable等標註纔會起做用,若是須要非public方法上的方法也可使用Spring Cache時把mode設置爲aspectj。
此外,<cache:annotation-driven/>還能夠指定一個proxy-target-class屬性,表示是否要代理class,默認爲false。
咱們前面提到的@Cacheable、@cacheEvict等也能夠標註在接口上,這對於基於接口的代理來講是沒有什麼問題的,
可是須要注意的是當咱們設置proxy-target-class爲true或者mode爲aspectj時,是直接基於class進行操做的,
定義在接口上的@Cacheable等Cache註解不會被識別到,那對應的Spring Cache也不會起做用了。-->
<cache:annotation-driven cache-manager="cacheManager" key-generator="cacheKeyGenerator"/> <bean id="cacheKeyGenerator" class="com.esther.code.util.CacheKeyGenerator"/> <!--通常的緩存管理器,如EhCache--> <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcache"/> </bean> <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml"/> </bean> <!--Dummy CacheManager:在找不到對應緩存時(如UserCache),能夠設置標誌位fallbackToNoOpCache=true,禁用緩存。 若是找不到對應緩存,且fallbackToNoOpCache=false,拋異常java.lang.IllegalArgumentException: Cannot find cache named 'UserCache' for CacheEvictOperation--> <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager"> <property name="cacheManagers"> <list> <ref bean="ehcacheManager"/> </list> </property> <property name="fallbackToNoOpCache" value="true"/> </bean> </beans>