001/* 002 * Copyright 2002-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.cache.guava; 018 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Map; 023import java.util.concurrent.ConcurrentHashMap; 024import java.util.concurrent.ConcurrentMap; 025 026import com.google.common.cache.CacheBuilder; 027import com.google.common.cache.CacheBuilderSpec; 028import com.google.common.cache.CacheLoader; 029 030import org.springframework.cache.Cache; 031import org.springframework.cache.CacheManager; 032import org.springframework.util.Assert; 033import org.springframework.util.ObjectUtils; 034 035/** 036 * {@link CacheManager} implementation that lazily builds {@link GuavaCache} 037 * instances for each {@link #getCache} request. Also supports a 'static' mode 038 * where the set of cache names is pre-defined through {@link #setCacheNames}, 039 * with no dynamic creation of further cache regions at runtime. 040 * 041 * <p>The configuration of the underlying cache can be fine-tuned through a 042 * Guava {@link CacheBuilder} or {@link CacheBuilderSpec}, passed into this 043 * CacheManager through {@link #setCacheBuilder}/{@link #setCacheBuilderSpec}. 044 * A {@link CacheBuilderSpec}-compliant expression value can also be applied 045 * via the {@link #setCacheSpecification "cacheSpecification"} bean property. 046 * 047 * <p>Requires Google Guava 12.0 or higher. 048 * 049 * @author Juergen Hoeller 050 * @author Stephane Nicoll 051 * @since 4.0 052 * @see GuavaCache 053 */ 054public class GuavaCacheManager implements CacheManager { 055 056 private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16); 057 058 private boolean dynamic = true; 059 060 private CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder(); 061 062 private CacheLoader<Object, Object> cacheLoader; 063 064 private boolean allowNullValues = true; 065 066 067 /** 068 * Construct a dynamic GuavaCacheManager, 069 * lazily creating cache instances as they are being requested. 070 */ 071 public GuavaCacheManager() { 072 } 073 074 /** 075 * Construct a static GuavaCacheManager, 076 * managing caches for the specified cache names only. 077 */ 078 public GuavaCacheManager(String... cacheNames) { 079 setCacheNames(Arrays.asList(cacheNames)); 080 } 081 082 083 /** 084 * Specify the set of cache names for this CacheManager's 'static' mode. 085 * <p>The number of caches and their names will be fixed after a call to this method, 086 * with no creation of further cache regions at runtime. 087 * <p>Calling this with a {@code null} collection argument resets the 088 * mode to 'dynamic', allowing for further creation of caches again. 089 */ 090 public void setCacheNames(Collection<String> cacheNames) { 091 if (cacheNames != null) { 092 for (String name : cacheNames) { 093 this.cacheMap.put(name, createGuavaCache(name)); 094 } 095 this.dynamic = false; 096 } 097 else { 098 this.dynamic = true; 099 } 100 } 101 102 /** 103 * Set the Guava CacheBuilder to use for building each individual 104 * {@link GuavaCache} instance. 105 * @see #createNativeGuavaCache 106 * @see com.google.common.cache.CacheBuilder#build() 107 */ 108 public void setCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) { 109 Assert.notNull(cacheBuilder, "CacheBuilder must not be null"); 110 doSetCacheBuilder(cacheBuilder); 111 } 112 113 /** 114 * Set the Guava CacheBuilderSpec to use for building each individual 115 * {@link GuavaCache} instance. 116 * @see #createNativeGuavaCache 117 * @see com.google.common.cache.CacheBuilder#from(CacheBuilderSpec) 118 */ 119 public void setCacheBuilderSpec(CacheBuilderSpec cacheBuilderSpec) { 120 doSetCacheBuilder(CacheBuilder.from(cacheBuilderSpec)); 121 } 122 123 /** 124 * Set the Guava cache specification String to use for building each 125 * individual {@link GuavaCache} instance. The given value needs to 126 * comply with Guava's {@link CacheBuilderSpec} (see its javadoc). 127 * @see #createNativeGuavaCache 128 * @see com.google.common.cache.CacheBuilder#from(String) 129 */ 130 public void setCacheSpecification(String cacheSpecification) { 131 doSetCacheBuilder(CacheBuilder.from(cacheSpecification)); 132 } 133 134 /** 135 * Set the Guava CacheLoader to use for building each individual 136 * {@link GuavaCache} instance, turning it into a LoadingCache. 137 * @see #createNativeGuavaCache 138 * @see com.google.common.cache.CacheBuilder#build(CacheLoader) 139 * @see com.google.common.cache.LoadingCache 140 */ 141 public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) { 142 if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) { 143 this.cacheLoader = cacheLoader; 144 refreshKnownCaches(); 145 } 146 } 147 148 /** 149 * Specify whether to accept and convert {@code null} values for all caches 150 * in this cache manager. 151 * <p>Default is "true", despite Guava itself not supporting {@code null} values. 152 * An internal holder object will be used to store user-level {@code null}s. 153 */ 154 public void setAllowNullValues(boolean allowNullValues) { 155 if (this.allowNullValues != allowNullValues) { 156 this.allowNullValues = allowNullValues; 157 refreshKnownCaches(); 158 } 159 } 160 161 /** 162 * Return whether this cache manager accepts and converts {@code null} values 163 * for all of its caches. 164 */ 165 public boolean isAllowNullValues() { 166 return this.allowNullValues; 167 } 168 169 170 @Override 171 public Collection<String> getCacheNames() { 172 return Collections.unmodifiableSet(this.cacheMap.keySet()); 173 } 174 175 @Override 176 public Cache getCache(String name) { 177 Cache cache = this.cacheMap.get(name); 178 if (cache == null && this.dynamic) { 179 synchronized (this.cacheMap) { 180 cache = this.cacheMap.get(name); 181 if (cache == null) { 182 cache = createGuavaCache(name); 183 this.cacheMap.put(name, cache); 184 } 185 } 186 } 187 return cache; 188 } 189 190 /** 191 * Create a new GuavaCache instance for the specified cache name. 192 * @param name the name of the cache 193 * @return the Spring GuavaCache adapter (or a decorator thereof) 194 */ 195 protected Cache createGuavaCache(String name) { 196 return new GuavaCache(name, createNativeGuavaCache(name), isAllowNullValues()); 197 } 198 199 /** 200 * Create a native Guava Cache instance for the specified cache name. 201 * @param name the name of the cache 202 * @return the native Guava Cache instance 203 */ 204 protected com.google.common.cache.Cache<Object, Object> createNativeGuavaCache(String name) { 205 if (this.cacheLoader != null) { 206 return this.cacheBuilder.build(this.cacheLoader); 207 } 208 else { 209 return this.cacheBuilder.build(); 210 } 211 } 212 213 private void doSetCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) { 214 if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) { 215 this.cacheBuilder = cacheBuilder; 216 refreshKnownCaches(); 217 } 218 } 219 220 /** 221 * Create the known caches again with the current state of this manager. 222 */ 223 private void refreshKnownCaches() { 224 for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) { 225 entry.setValue(createGuavaCache(entry.getKey())); 226 } 227 } 228 229}