001/* 002 * Copyright 2002-2016 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.caffeine; 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.github.benmanes.caffeine.cache.CacheLoader; 027import com.github.benmanes.caffeine.cache.Caffeine; 028import com.github.benmanes.caffeine.cache.CaffeineSpec; 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 CaffeineCache} 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 * {@link Caffeine} builder or {@link CaffeineSpec}, passed into this 043 * CacheManager through {@link #setCaffeine}/{@link #setCaffeineSpec}. 044 * A {@link CaffeineSpec}-compliant expression value can also be applied 045 * via the {@link #setCacheSpecification "cacheSpecification"} bean property. 046 * 047 * <p>Requires Caffeine 2.1 or higher. 048 * 049 * @author Ben Manes 050 * @author Juergen Hoeller 051 * @author Stephane Nicoll 052 * @since 4.3 053 * @see CaffeineCache 054 */ 055public class CaffeineCacheManager implements CacheManager { 056 057 private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16); 058 059 private boolean dynamic = true; 060 061 private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder(); 062 063 private CacheLoader<Object, Object> cacheLoader; 064 065 private boolean allowNullValues = true; 066 067 068 /** 069 * Construct a dynamic CaffeineCacheManager, 070 * lazily creating cache instances as they are being requested. 071 */ 072 public CaffeineCacheManager() { 073 } 074 075 /** 076 * Construct a static CaffeineCacheManager, 077 * managing caches for the specified cache names only. 078 */ 079 public CaffeineCacheManager(String... cacheNames) { 080 setCacheNames(Arrays.asList(cacheNames)); 081 } 082 083 084 /** 085 * Specify the set of cache names for this CacheManager's 'static' mode. 086 * <p>The number of caches and their names will be fixed after a call to this method, 087 * with no creation of further cache regions at runtime. 088 * <p>Calling this with a {@code null} collection argument resets the 089 * mode to 'dynamic', allowing for further creation of caches again. 090 */ 091 public void setCacheNames(Collection<String> cacheNames) { 092 if (cacheNames != null) { 093 for (String name : cacheNames) { 094 this.cacheMap.put(name, createCaffeineCache(name)); 095 } 096 this.dynamic = false; 097 } 098 else { 099 this.dynamic = true; 100 } 101 } 102 103 /** 104 * Set the Caffeine to use for building each individual 105 * {@link CaffeineCache} instance. 106 * @see #createNativeCaffeineCache 107 * @see com.github.benmanes.caffeine.cache.Caffeine#build() 108 */ 109 public void setCaffeine(Caffeine<Object, Object> caffeine) { 110 Assert.notNull(caffeine, "Caffeine must not be null"); 111 doSetCaffeine(caffeine); 112 } 113 114 /** 115 * Set the {@link CaffeineSpec} to use for building each individual 116 * {@link CaffeineCache} instance. 117 * @see #createNativeCaffeineCache 118 * @see com.github.benmanes.caffeine.cache.Caffeine#from(CaffeineSpec) 119 */ 120 public void setCaffeineSpec(CaffeineSpec caffeineSpec) { 121 doSetCaffeine(Caffeine.from(caffeineSpec)); 122 } 123 124 /** 125 * Set the Caffeine cache specification String to use for building each 126 * individual {@link CaffeineCache} instance. The given value needs to 127 * comply with Caffeine's {@link CaffeineSpec} (see its javadoc). 128 * @see #createNativeCaffeineCache 129 * @see com.github.benmanes.caffeine.cache.Caffeine#from(String) 130 */ 131 public void setCacheSpecification(String cacheSpecification) { 132 doSetCaffeine(Caffeine.from(cacheSpecification)); 133 } 134 135 /** 136 * Set the Caffeine CacheLoader to use for building each individual 137 * {@link CaffeineCache} instance, turning it into a LoadingCache. 138 * @see #createNativeCaffeineCache 139 * @see com.github.benmanes.caffeine.cache.Caffeine#build(CacheLoader) 140 * @see com.github.benmanes.caffeine.cache.LoadingCache 141 */ 142 public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) { 143 if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) { 144 this.cacheLoader = cacheLoader; 145 refreshKnownCaches(); 146 } 147 } 148 149 /** 150 * Specify whether to accept and convert {@code null} values for all caches 151 * in this cache manager. 152 * <p>Default is "true", despite Caffeine itself not supporting {@code null} values. 153 * An internal holder object will be used to store user-level {@code null}s. 154 */ 155 public void setAllowNullValues(boolean allowNullValues) { 156 if (this.allowNullValues != allowNullValues) { 157 this.allowNullValues = allowNullValues; 158 refreshKnownCaches(); 159 } 160 } 161 162 /** 163 * Return whether this cache manager accepts and converts {@code null} values 164 * for all of its caches. 165 */ 166 public boolean isAllowNullValues() { 167 return this.allowNullValues; 168 } 169 170 171 @Override 172 public Collection<String> getCacheNames() { 173 return Collections.unmodifiableSet(this.cacheMap.keySet()); 174 } 175 176 @Override 177 public Cache getCache(String name) { 178 Cache cache = this.cacheMap.get(name); 179 if (cache == null && this.dynamic) { 180 synchronized (this.cacheMap) { 181 cache = this.cacheMap.get(name); 182 if (cache == null) { 183 cache = createCaffeineCache(name); 184 this.cacheMap.put(name, cache); 185 } 186 } 187 } 188 return cache; 189 } 190 191 /** 192 * Create a new CaffeineCache instance for the specified cache name. 193 * @param name the name of the cache 194 * @return the Spring CaffeineCache adapter (or a decorator thereof) 195 */ 196 protected Cache createCaffeineCache(String name) { 197 return new CaffeineCache(name, createNativeCaffeineCache(name), isAllowNullValues()); 198 } 199 200 /** 201 * Create a native Caffeine Cache instance for the specified cache name. 202 * @param name the name of the cache 203 * @return the native Caffeine Cache instance 204 */ 205 protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(String name) { 206 if (this.cacheLoader != null) { 207 return this.cacheBuilder.build(this.cacheLoader); 208 } 209 else { 210 return this.cacheBuilder.build(); 211 } 212 } 213 214 private void doSetCaffeine(Caffeine<Object, Object> cacheBuilder) { 215 if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) { 216 this.cacheBuilder = cacheBuilder; 217 refreshKnownCaches(); 218 } 219 } 220 221 /** 222 * Create the known caches again with the current state of this manager. 223 */ 224 private void refreshKnownCaches() { 225 for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) { 226 entry.setValue(createCaffeineCache(entry.getKey())); 227 } 228 } 229 230}