基于spring4.0配置分布式ehcache,以及相关使用
说明:本文是基于RMI手动同步的方式,使用程序动态注入配置缓存,抛弃传统的ehcache.xml配置方式
1,注入cacheManager管理所有缓存,添加各个缓存名及相关参数配置:
思路大致是:
在项目的主配置类中,通过@Bean注入cacheManager,统一管理所有cache,传统的用xml方式配置太累赘,其实只需要更该chacheName,其他参数变化不是很大,考虑用程序封装做到最大程度的代码重用所以采取拼接JSON串的形式,配置缓存名,及所有相关需要设置好的参数。然后通过解析JSON,拼接成带主机IP的RmiUrl,以manual手动rmi同步的方式配置peerDiscovery成员发现,再通过配置cacheManagerPeerListenerFactory这个类启动本机监听程序,来发现其他主机发来的同步请求。最后再用@EnableCaching注解开启spring支持对ehcache注解使用
下面是项目配置好的详细代码:
1 package com.cshtong.tower.web.init; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.UncheckedIOException; 6 import java.net.InetAddress; 7 import java.net.UnknownHostException; 8 import java.util.ArrayList; 9 import java.util.Iterator; 10 import java.util.List; 11 import java.util.Properties; 12 13 import javax.annotation.Resource; 14 15 import org.slf4j.Logger; 16 import org.slf4j.LoggerFactory; 17 import org.springframework.cache.annotation.EnableCaching; 18 import org.springframework.cache.ehcache.EhCacheCacheManager; 19 import org.springframework.cache.interceptor.KeyGenerator; 20 import org.springframework.cache.interceptor.SimpleKeyGenerator; 21 import org.springframework.context.annotation.Bean; 22 import org.springframework.context.annotation.Configuration; 23 import org.springframework.transaction.annotation.EnableTransactionManagement; 24 25 import com.alibaba.fastjson.JSONArray; 26 import com.alibaba.fastjson.JSONObject; 27 import com.cshtong.tower.service.UserService; 28 import com.cshtong.tower.util.StringUtil; 29 30 import net.sf.ehcache.Cache; 31 import net.sf.ehcache.CacheException; 32 import net.sf.ehcache.CacheManager; 33 import net.sf.ehcache.config.CacheConfiguration; 34 import net.sf.ehcache.config.DiskStoreConfiguration; 35 import net.sf.ehcache.config.FactoryConfiguration; 36 import net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory; 37 import net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory; 38 import net.sf.ehcache.distribution.RMICacheReplicatorFactory; 39 import net.sf.ehcache.store.MemoryStoreEvictionPolicy; 40 41 @EnableCaching 42 @Configuration 43 @EnableTransactionManagement 44 public class EhcacheConfig extends RMICacheReplicatorFactory{ 45 46 private static Logger logger = LoggerFactory.getLogger(EhcacheConfig.class); 47 private static final String EHCACHE_PROPERTIES = "ehcache.properties"; 48 49 /** 最大缓存数量 0 = no limit. **/ 50 private static final Integer MAX_CACHE = 0; 51 /** 缓存失效时间间隔/秒 **/ 52 private static final Integer TIME_TOLIVE_SECONDS = 24*60*60; 53 /** 缓存闲置多少秒后自动销毁 **/ 54 private static final Integer TIME_TOIDLE_SECONDS = 60*60; 55 /** 磁盘失效线程运行时间间隔/秒。 **/ 56 private static final Integer DISK_EXPIRY_Thread_INTERVAL_SENCONDS = 100; 57 58 @Resource 59 UserService userService; 60 61 /** 注入cacheManager **/ 62 @Bean 63 public org.springframework.cache.CacheManager cacheManager() { 64 org.springframework.cache.CacheManager cm = new EhCacheCacheManager(putCache()); 65 return cm; 66 } 67 68 @Bean 69 public KeyGenerator keyGenerator() { 70 return new SimpleKeyGenerator(); 71 } 72 73 @Bean 74 public CacheManager putCache(){ 75 76 String rmiUrls = initRmiURLs(); 77 78 net.sf.ehcache.config.Configuration cf = new net.sf.ehcache.config.Configuration(); 79 Properties pro = initRmiUrlsProperties(); 80 if (null!=rmiUrls) { 81 /** 临时文件目录 **/ 82 cf.diskStore(new DiskStoreConfiguration().path("java.io.tmpdir")) 83 /**成员发现 peerDiscovery 方式:manual:手动,rmiUrls:主机名+端口号+缓存名,用来接受或者发送信息的接口,不同的缓存或者机器用“|”隔开 **/ 84 .cacheManagerPeerProviderFactory(new FactoryConfiguration<FactoryConfiguration<?>>() 85 .className(RMICacheManagerPeerProviderFactory.class.getName()) 86 .properties("peerDiscovery=manual,rmiUrls=" + rmiUrls) 87 ); 88 /** 本机监听程序,来发现其他主机发来的同步请求 hostName=192.168.1.112 这里默认是本机可以不配置 **/ 89 cf.cacheManagerPeerListenerFactory(new FactoryConfiguration<FactoryConfiguration<?>>() 90 .className(RMICacheManagerPeerListenerFactory.class.getName()) 91 .properties("port="+ pro.getProperty("rmiPortNumber") +",socketTimeoutMillis=2000") 92 ); 93 }else{ 94 logger.debug("The rmiUrls is null!!"); 95 } 96 97 CacheManager cm = null; 98 99 try { 100 cm = CacheManager.create(cf); 101 } catch (UncheckedIOException e) { 102 logger.debug("Fail to load config. message:{}", e.getMessage()); 103 } 104 105 List<Cache> array = null; 106 try { 107 array = cacheCollection(); 108 } catch (CacheException e) { 109 logger.error("collect cache Failed"); 110 } 111 112 for (Cache list:array) { 113 cm.addCache(list); 114 } 115 116 return cm; 117 } 118 119 public static List<Cache> cacheCollection(){ 120 String listParameters = cacheParametersCollection(); 121 return cacheConf(listParameters); 122 } 123 124 /** 125 * 添加缓存的参数 126 * 相关属性: 127 name : "缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里) 128 maxElementsInMemory : 缓存最大个数,0没有限制。 129 eternal="false" : 对象是否永久有效,一但设置了,timeout将不起作用。 (必须设置) 130 maxEntriesLocalHeap="1000" : 堆内存中最大缓存对象数,0没有限制(必须设置) 131 maxEntriesLocalDisk= "1000" : 硬盘最大缓存个数。 132 overflowToDisk="false" : 当缓存达到maxElementsInMemory值是,是否允许溢出到磁盘(必须设设置)(内存不足时,是否启用磁盘缓存。) 133 diskSpoolBufferSizeMB : 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 134 diskPersistent="false" : 磁盘缓存在JVM重新启动时是否保持(默认为false)硬盘持久化 135 timeToIdleSeconds="0" : 导致元素过期的访问间隔(秒为单位),即当缓存闲置n秒后销毁。 当eternal为false时,这个属性才有效,0表示可以永远空闲,默认为0 136 timeToLiveSeconds="0" : 元素在缓存里存在的时间(秒为单位),即当缓存存活n秒后销毁. 0 表示永远存在不过期 137 memoryStoreEvictionPolicy="LFU" : 当达到maxElementsInMemory时,如何强制进行驱逐默认使用"最近使用(LRU)"策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU 138 diskExpiryThreadIntervalSeconds :磁盘失效线程运行时间间隔,默认是120秒。 139 clearOnFlush : 内存数量最大时是否清除。 140 141 cacheEventListenerFactory : 给缓存添加监听 142 replicateAsynchronously=true : 异步的方式 143 replicatePuts=true,replicateUpdates=true,replicateUpdatesViaCopy=true,replicateRemovals= true : 在put,update,copy,remove操作是否复制 144 cationIntervalMillis=1000 : 同步时间1s 145 bootstrapCacheLoaderFactory 启动是指一启动就同步数据 146 * @return 147 */ 148 public static String cacheParametersCollection(){ 149 150 String listParameters = "[" ; 151 152 /** 排班:列表详情 **/ 153 listParameters += "{'cacheName':'schedule_listPatrol','maxEntriesLocalHeap':'0'}"; 154 155 /** APP:用户当月的排班 **/ 156 listParameters += "{'cacheName':'owner_schedule','maxEntriesLocalHeap':'0'}"; 157 158 /**考勤统计 :echarts图表相关数据**/ 159 listParameters += "{'cacheName':'attendance_findByOrgIds','maxEntriesLocalHeap':'0'}"; 160 /**考勤统计 :主界面表格相关的数据**/ 161 listParameters += "{'cacheName':'attendance_findByOrgIdsAndPage','maxEntriesLocalHeap':'0','timeToIdleSeconds':'0'}"; 162 163 /** 机构 **/ 164 listParameters += "{'cacheName':'org_findAll','maxEntriesLocalHeap':'0'}"; 165 166 /** 用户信息 key=userId **/ 167 listParameters += "{'cacheName':'list_userInfo','maxEntriesLocalHeap':'0'}"; 168 169 /** APP 勤务圈 **/ 170 listParameters += "{'cacheName':'app_message','maxElementsInMemory':'0','maxEntriesLocalHeap':'0'}"; 171 172 /** 报警,最近1km内的用户 **/ 173 listParameters += "{'cacheName':'rescue_users','maxElementsInMemory':'0','maxEntriesLocalHeap':'0'}"; 174 175 /** 报警,最近1km内的用户 **/ 176 listParameters += "{'cacheName':'app_message_typeName','maxElementsInMemory':'0','maxEntriesLocalHeap':'0'}"; 177 178 listParameters += "]"; 179 180 return listParameters; 181 } 182 183 /** 184 * 添加成员发现: //主机ip+端口号/ 185 * @return 186 */ 187 public static List<String> cacheManagerPeerProviderCollection(){ 188 Properties pro = initRmiUrlsProperties(); 189 190 List<String> ip = new ArrayList<>(); 191 try { 192 ip.add(pro.get("machine1").toString()); 193 ip.add(pro.get("machine2").toString()); 194 } catch (Exception e) { 195 logger.error("Fail to provider cacheManagerPeer. config file [{}], message:{}", EHCACHE_PROPERTIES, e.getMessage()); 196 } 197 198 InetAddress ia; 199 try { 200 ia = InetAddress.getLocalHost(); 201 String localip = ia.getHostAddress(); 202 for (int i = 0; i < ip.size(); i++) { 203 /** 过滤本机ip **/ 204 if (localip.equalsIgnoreCase(ip.get(i))) { 205 ip.remove(i); 206 } 207 } 208 } catch (UnknownHostException Host) { 209 Host.printStackTrace(); 210 logger.error("Unknown to host Address. config file [{}], message:{}", EHCACHE_PROPERTIES, Host.getMessage()); 211 } 212 213 List<String> peer = new ArrayList<>(); 214 for (int j = 0; j < ip.size(); j++) { 215 peer.add("//" + ip.get(j) + ":" + pro.getProperty("rmiPortNumber").toString()); 216 } 217 218 return peer; 219 } 220 221 public static String initRmiURLs(){ 222 String rmiUrls = ""; 223 String listParameters = cacheParametersCollection(); 224 JSONArray array = initCacheParameters(listParameters); 225 for (Iterator<Object> iterator = array.iterator(); iterator.hasNext();) { 226 JSONObject obj = (JSONObject)iterator.next(); 227 String cacheName = obj.get("cacheName").toString(); 228 List<String> peer = cacheManagerPeerProviderCollection(); 229 for (String list:peer) { 230 rmiUrls += list + cacheName + "|"; 231 } 232 } 233 if (!"".equals(rmiUrls)) { 234 rmiUrls = rmiUrls.substring(0,rmiUrls.length()-1); 235 } 236 return rmiUrls; 237 } 238 239 public static JSONArray initCacheParameters(String listParameters){ 240 JSONArray array = null; 241 try { 242 array = JSONArray.parseArray(listParameters); 243 } catch (Exception e) { 244 logger.error("Fail to init The cache parameters. message:{}", e.getMessage()); 245 } 246 return array; 247 } 248 249 public static Properties initRmiUrlsProperties(){ 250 InputStream resourcesStream = EhcacheConfig.class.getClassLoader().getResourceAsStream(EHCACHE_PROPERTIES); 251 Properties pro = new Properties(); 252 try { 253 pro.load(resourcesStream); 254 } catch (IOException e) { 255 logger.error("Fail to load config. config file [{}], message:{}", EHCACHE_PROPERTIES, e.getMessage()); 256 } 257 return pro; 258 } 259 260 /** 261 * @param listPatrolParameters 缓存参数JSON数组 262 * @return 缓存的集合 263 */ 264 @SuppressWarnings("deprecation") 265 public static List<Cache> cacheConf(String listParameters){ 266 267 List<Cache> listCache = new ArrayList<>(); 268 JSONArray array = initCacheParameters(listParameters); 269 for (Iterator<Object> iterator = array.iterator(); iterator.hasNext();) { 270 JSONObject obj = (JSONObject)iterator.next(); 271 272 String cacheName = obj.get("cacheName").toString(); 273 String maxElementsInMemory = obj.getString("maxElementsInMemory"); 274 String maxEntriesLocalHeap = obj.getString("maxEntriesLocalHeap"); 275 String timeToLiveSeconds = obj.getString("timeToLiveSeconds"); 276 String timeToIdleSeconds = obj.getString("timeToIdleSeconds"); 277 278 RMICacheReplicatorFactory rmi = new RMICacheReplicatorFactory(); 279 Properties pro = initRmiUrlsProperties(); 280 rmi.createCacheEventListener(pro); 281 282 CacheConfiguration cacheConfiguration = new CacheConfiguration(cacheName,StringUtil.isNull(maxEntriesLocalHeap)?MAX_CACHE:Integer.parseInt(maxEntriesLocalHeap)) 283 .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU) 284 .maxElementsInMemory(StringUtil.isNull(maxElementsInMemory)?MAX_CACHE:Integer.parseInt(maxElementsInMemory)) 285 .overflowToDisk(true) 286 .eternal(false) 287 .timeToLiveSeconds(StringUtil.isNull(timeToLiveSeconds)?TIME_TOLIVE_SECONDS:Integer.parseInt(timeToLiveSeconds)) 288 .timeToIdleSeconds(StringUtil.isNull(timeToIdleSeconds)?TIME_TOIDLE_SECONDS:Integer.parseInt(timeToIdleSeconds)) 289 .diskPersistent(false) 290 .diskExpiryThreadIntervalSeconds(DISK_EXPIRY_Thread_INTERVAL_SENCONDS) 291 .clearOnFlush(true) 292 .cacheEventListenerFactory(new CacheConfiguration.CacheEventListenerFactoryConfiguration().className(RMICacheReplicatorFactory.class.getName())); 293 Cache cache = new Cache(cacheConfiguration); 294 295 listCache.add(cache); 296 } 297 298 return listCache; 299 } 300 }
properties文件:
1 #RMICacheReplicatorFactory properties 2 replicateAsynchronously=true 3 replicatePuts=true 4 replicatePutsViaCopy=false 5 replicateUpdates=true 6 replicateUpdatesViaCopy=false 7 replicateRemovals=true 8 asynchronousReplicationIntervalMillis=1000 9 asynchronousReplicationMaximumBatchSize=1000 10 11 #RMI URLs 12 machine1=//主机ip+端口号/ 13 14 #RMI port 15 rmiPortNumber=8010
到这里就注入好了缓存名为listParameters里面cacheName的所有缓存,如果后续添加或修改缓存,只需更改listParameters的相关属性,如果在集群环境,也只需在porperties文件中添加machine..配置即可。
2,基于注解方式的缓存使用:
1,为方便重用所有缓存建议在service层使用,当方法第一次执行时将返回值以key-value对象写进缓存,之后在执行该方法时,如果缓存的condition满足则直接取缓存返回,实际上方法都不会进!
2,在修改或添加方法使用@CachePut,查询方法使用@Cacheable,删除方法使用@CacheEvict,注意:缓存一定要配合使用,例如一个list方法将相应值缓存起来,如果有针对该值CUD操作时,一定要将新的返回值@CachePut,否则会出现数据脏读的情况!如果更新或修改方法的返回值与list不相同即该缓存@CacheEvict,否则会报数据映射错误!
3,注解说明:
@Cacheable (value="缓存值/SpEL/不填默认返回值", key="缓存key/SpEL") : 应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中。如图:
@CachePut 应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存: 如图:
@CacheEvict: 移除数据,如图:
@Caching 组合多个Cache注解使用,如图:
注解参数说明:
value:必须指定,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称, listparamentes中的cacheName
key:通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index。我们统一采用方法的参数做唯一key,没有参数不写!
condition:有的时候我们可能并不希望缓存一个方法所有的返回结果,condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存
1 @Cacheable(value="users",key="#user.id",condition="#user.id%2==0") 2 public User find(User user) { 3 System.out.println("find user by user " + user); 4 return user; 5 }
3,基于程序代码方式的缓存相关使用:
当注解不能完全满足需求,或需要在程序代码中实现动态操作时,就需要对ehcache实现相关封装,从而现对缓存手动进行增删改。可以考虑写成util,我这里是以service的形式现实的封装及相关调用,仅供参考。
1 package com.cshtong.tower.service; 2 3 import java.io.UncheckedIOException; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import javax.annotation.Resource; 8 9 import org.slf4j.Logger; 10 import org.slf4j.LoggerFactory; 11 import org.springframework.stereotype.Service; 12 13 import com.alibaba.fastjson.JSONObject; 14 import com.cshtong.tower.model.MessageType; 15 import com.cshtong.tower.model.User; 16 import com.cshtong.tower.repository.MessageTypeRepository; 17 18 import net.sf.ehcache.Cache; 19 import net.sf.ehcache.CacheManager; 20 import net.sf.ehcache.Ehcache; 21 import net.sf.ehcache.Element; 22 23 @Service 24 public class EhcacheServiceImpl implements EhcacheService{ 25 26 private static Logger logger = LoggerFactory.getLogger(EhcacheServiceImpl.class); 27 28 @Resource 29 private UserService userSerivce; 30 31 @Resource 32 private MessageTypeRepository messageTypeRepository; 33 34 public static CacheManager cacheManager(){ 35 36 CacheManager cm = null; 37 try { 38 cm = CacheManager.newInstance(); 39 } catch (UncheckedIOException e) { 40 logger.error("Fail to load config, message:{}", e.getMessage()); 41 } 42 return cm; 43 } 44 45 /** 46 * key可以为空 47 */ 48 @SuppressWarnings({"deprecation" }) 49 @Override 50 public com.cshtong.tower.model.Ehcache findCache(String cacheName, String key) { 51 com.cshtong.tower.model.Ehcache eh = null; 52 if (null == key) { 53 Ehcache cache = cacheManager().getEhcache(cacheName); 54 eh = new com.cshtong.tower.model.Ehcache(); 55 eh.setCacheName(cache.getName()); 56 List<String> listKey = new ArrayList<>(); 57 for (int j = 0; j < cache.getKeys().size(); j++) { 58 listKey.add(cache.getKeys().get(j).toString()); 59 } 60 eh.setKeys(listKey); 61 eh.setSize(cache.getSize()); 62 /*eh.setHitrate(cache.getStatistics().cacheHitRatio());*/ 63 eh.setDiskStoreSize(cache.getDiskStoreSize()); 64 eh.setMemoryStoreSize(cache.getMemoryStoreSize()); 65 eh.setStatus(cache.getStatus().toString()); 66 }else{ 67 Element el = cacheManager().getEhcache(cacheName).get(key); 68 if (el == null) { 69 el = cacheManager().getEhcache(cacheName).get(Integer.parseInt(key)); 70 } 71 eh = new com.cshtong.tower.model.Ehcache(); 72 eh.setKeyHit(el.getHitCount()); 73 eh.setLastUpdateTime(el.getLastUpdateTime()); 74 eh.setValues(el.getValue()); 75 } 76 return eh; 77 } 78 79 @SuppressWarnings("deprecation") 80 @Override 81 public List<com.cshtong.tower.model.Ehcache> listAllEhcahce() { 82 List<com.cshtong.tower.model.Ehcache> list = new ArrayList<>(); 83 String[] cache = cacheManager().getCacheNames(); 84 List<Ehcache> cachelist = new ArrayList<>(); 85 for (int i = 0; i < cache.length; i++) { 86 Ehcache c = cacheManager().getEhcache(cache[i]); 87 cachelist.add(c); 88 } 89 for (int i = 0; i < cachelist.size(); i++) { 90 com.cshtong.tower.model.Ehcache eh = new com.cshtong.tower.model.Ehcache(); 91 eh.setCacheName(cachelist.get(i).getName()); 92 List<String> listKey = new ArrayList<>(); 93 for (int j = 0; j < cachelist.get(i).getKeys().size(); j++) { 94 listKey.add(cachelist.get(i).getKeys().get(j).toString()); 95 } 96 eh.setKeys(listKey); 97 eh.setSize(cachelist.get(i).getSize()); 98 eh.setStatus(cachelist.get(i).getStatus().toString()); 99 eh.setMemoryStoreSize(cachelist.get(i).getMemoryStoreSize()); 100 eh.setDiskStoreSize(cachelist.get(i).getDiskStoreSize()); 101 list.add(eh); 102 } 103 return list; 104 } 105 106 /** 107 * 获取缓存 108 * @param cacheName 109 * @param key 110 * @return json字符串 111 */ 112 @Override 113 public String getCache(String cacheName, Object key) { 114 logger.debug("Getting Cache that name is " + cacheName + "and key is" + key); 115 Element el = cacheManager().getCache(cacheName).get(key); 116 return JSONObject.toJSONString(el.getObjectValue()); 117 } 118 119 @Override 120 public Cache getCache(String name) { 121 logger.debug("Getting Cache that name is " + name); 122 return cacheManager().getCache(name); 123 } 124 125 /** 126 * 获取所有的缓存 127 * @param names 128 * @return 129 */ 130 @Override 131 public String[] getCacheNames() { 132 logger.debug("All of the Cache is " + cacheManager().getCacheNames()); 133 return cacheManager().getCacheNames(); 134 } 135 136 @Override 137 public void update(String cacheName, Object key, Object value) { 138 try { 139 remove(cacheName, key); 140 put(cacheName, key, value); 141 } catch (Exception e) { 142 logger.debug("Fail to update the Cache,which is " + cacheManager().getCacheNames()); 143 } 144 } 145 146 @Override 147 public void put(String cacheName, Object key, Object value) { 148 logger.debug("add Cache is " + cacheManager().getCacheNames() + ",and key is " + key + ",and value is" + value); 149 Element el = new Element(key, value); 150 cacheManager().getCache(cacheName).put(el); 151 } 152 153 @Override 154 public boolean ishasCache(String cacheName, Object key) { 155 boolean rs = false; 156 Element value = cacheManager().getCache(cacheName).get(key); 157 if (null != value) { 158 rs = true; 159 } 160 return rs; 161 } 162 163 /** 164 * 根据缓存名清除缓存key value 165 * @param name 166 */ 167 @Override 168 public void evictName(String name) { 169 logger.debug("delete Cache that name is " + name); 170 cacheManager().getCache(name).removeAll(); 171 } 172 173 /** 174 * 根据缓存名对应的key清除缓存 175 */ 176 @Override 177 public void remove(String cacheName, Object key) { 178 logger.debug("Delete Cache that Name is "+ cacheName +"and key is " + key ); 179 Cache cache = cacheManager().getCache(cacheName); 180 cache.remove(key); 181 } 182 183 /** 184 * 清除当前cacheManager的所有缓存 185 */ 186 @Override 187 public void clear() { 188 logger.debug("clear all cache!!"); 189 cacheManager().clearAll(); 190 } 191 192 }
以上愚见,只是个人的理解,仅供参考。如有不对的地方,欢迎指正批评....