SpringBoot SimpleCacheConfiguration的自动配置原理

引言

  在之前的博客中分享了简单的SpringBoot缓存的HelloWorld程序,在篇博客中主要来分析一下SpringBoot对于缓存自动配置的原理

缓存自动配置原理

  首先在SpringBoot自动配置原理中有一个概念,就是所有的自动配置命名规则都是XXXAutoConfiguration的类。在这里对于缓存的自动配置也是有对应的CacheAutoConfiguration的配置类。下面就来看一下这个自动配置类,在这个配置类中给容器中添加了一些关于缓存管理器的自定义器。

  @Bean
  @ConditionalOnMissingBean
   public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
        return new CacheManagerCustomizers((List)customizers.getIfAvailable());
    }

  当然了这配置类还往容器中加入了一个缓存管理器的选择器。可以看到它是作为自动配置类的内部类出现的。

@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
  static class CacheConfigurationImportSelector implements ImportSelector {
        CacheConfigurationImportSelector() {
        }

        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            CacheType[] types = CacheType.values();
            String[] imports = new String[types.length];

            for(int i = 0; i < types.length; ++i) {
                imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
            }

            return imports;
        }
    }

  在其中调用了如下的方法导入对应的缓存组件。可以在这个方法上打上断点通过断点调试对应的导入的缓存加载器的组件。

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}

在这里我们看到SpringBoot的缓存管理器在容器中为我们注册了11个缓存的组件的配置类。通过这些类的命名方法就可以看出SpringBoot中对于缓存组件的支持。
SpringBoot SimpleCacheConfiguration的自动配置原理
那么对于以上的这些配置类哪一个配置类是默认生效的呢?在之前的博客中我们分析过SpringBoot自动配置类知道了有一个@Conditional注解,这个注解就是对于SpringBoot的规则的判断。也就是说只有当满足这些条件的时候才会生效。那么这里的11个配置类到底是哪一个类默认生效的话就是要进行判断,判断哪个类是默认满足条件的。
SpringBoot SimpleCacheConfiguration的自动配置原理
当我们在搜索加载日志的时候会发现一个叫做SimpleCacheConfiguration的缓存配置被加载了,而对于其他的配置类并没有匹配,也就说在SpringBoot中默认生效的缓存配置就是这个类。
而对于SimpleCacheConfiguration自动配置类来说到底做了哪些操作呢?

@Configuration
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
    private final CacheProperties cacheProperties;
    private final CacheManagerCustomizers customizerInvoker;

    SimpleCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) {
        this.cacheProperties = cacheProperties;
        this.customizerInvoker = customizerInvoker;
    }

    @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }

        return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
    }
}

从以上代码中可以看到代码也比较短,主要注意它为容器中添加了一个ConcurrentMapCacheManager 缓存管理器。还有它没有进行其他的缓存管理器的配置
@ConditionalOnMissingBean({CacheManager.class})、@Conditional({CacheCondition.class})而对于 ConcurrentMapCacheManager 这个缓存管理器到底是什么作用。从字面上理解可能就是使用了一个ConcurrentHashMap进行的一个对于缓存的处理操作。实际上的操作是如下的

	@Override
	public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		if (cache == null && this.dynamic) {
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					cache = createConcurrentMapCache(name);
					this.cacheMap.put(name, cache);
				}
			}
		}
		return cache;
	}

而其中的cacheMapj就是一个ConcurrentHashMap而这个map就是缓存的名称和缓存。对于没有的缓存名称则会创建一个缓存

protected Cache createConcurrentMapCache(String name) {
		SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
		return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
				isAllowNullValues(), actualSerialization);

	}

从这里我们就可看到,对于默认的这个缓存配置来说它可以获取和创建ConcurrentMapCache类型的缓存组件。而对于这个缓存组件我们可以进一步的分析一下,而以下是对于这个类的简单的一些方法,对于其他的详细的方法读者可以自己进行相应的阅读。我们看到这类主要的作用就是将数据保存在一个ConcurrentHashMap中在需要的时候进行数据的存取操作。

public final boolean isStoreByValue() {
		return (this.serialization != null);
	}

	@Override
	public final String getName() {
		return this.name;
	}

	@Override
	public final ConcurrentMap<Object, Object> getNativeCache() {
		return this.store;
	}

	@Override
	protected Object lookup(Object key) {
		return this.store.get(key);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
		// Try efficient lookup on the ConcurrentHashMap first...
		ValueWrapper storeValue = get(key);
		if (storeValue != null) {
			return (T) storeValue.get();
		}

		// No value found -> load value within full synchronization.
		synchronized (this.store) {
			storeValue = get(key);
			if (storeValue != null) {
				return (T) storeValue.get();
			}

			T value;
			try {
				value = valueLoader.call();
			}
			catch (Throwable ex) {
				throw new ValueRetrievalException(key, valueLoader, ex);
			}
			put(key, value);
			return value;
		}
	}

	@Override
	public void put(Object key, Object value) {
		this.store.put(key, toStoreValue(value));
	}

	@Override
	public ValueWrapper putIfAbsent(Object key, Object value) {
		Object existing = this.store.putIfAbsent(key, toStoreValue(value));
		return toValueWrapper(existing);
	}

缓存的运行步骤流程

  我们可以将这些方法上打上断点,然后去观察这些方法的调用时机。

1.首先从浏览器中发送一个查询操作
2.方法进入到ConcurrentCacheManager类中的getCache()方法中

  之前也说过这个方法是用来创建和获取缓存的操作。也就是说先进行缓存的创建和获取的操作,当第一获取缓存的时候并不会获取缓存,而是根据规则创建对应的缓存。

3.进入到ConcurrentMapCache中的lookup(String key)方法中从store中查找相应的缓存数据,要按照对应的key查找缓存内容

  这一步操作我们需要注意了,对于key的生成策略之前的时候也说过这个生成策略是可以进行修改的。所以在通过key获取值的时候,就需要先进行一个key生成策略操作。在CacheAspectSupport类中有一个方法

private Object generateKey(CacheOperationContext context, Object result) {
		Object key = context.generateKey(result);
		if (key == null) {
			throw new IllegalArgumentException("Null key returned for cache operation (maybe you are " +
					"using named params on classes without debug info?) " + context.metadata.operation);
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation);
		}
		return key;
	}

在这个方法其中还有一个方法

protected Object generateKey(Object result) {
			if (StringUtils.hasText(this.metadata.operation.getKey())) {
				EvaluationContext evaluationContext = createEvaluationContext(result);
				return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
			}
			return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
		}

而对于这个类CacheOperationMetadata来说就是为了生成对应的Key,而它作为一个内部类也实现了generate()这样的方法在默认的SimpleKeyGenerator类中实现了对应的操作

@Override
	public Object generate(Object target, Method method, Object... params) {
		return generateKey(params);
	}
	public static Object generateKey(Object... params) {
		if (params.length == 0) {
			return SimpleKey.EMPTY;
		}
		if (params.length == 1) {
			Object param = params[0];
			if (param != null && !param.getClass().isArray()) {
				return param;
			}
		}
		return new SimpleKey(params);
	}
	public SimpleKey(Object... elements) {
		Assert.notNull(elements, "Elements must not be null");
		this.params = new Object[elements.length];
		System.arraycopy(elements, 0, this.params, 0, elements.length);
		this.hashCode = Arrays.deepHashCode(this.params);
	

到这里对应的Key进行生成策略就结束了。而对于这个生成策略简单的分析来说如果传入的没有传入参数,也就是说要返回一个SimpleKey.EMPTY:而如果有一个参数的话就返回一个参数,如果是多个参数参数的话就会返回SimpleKey(params);这里我们发现默认是以参数为key的。

4.当没有对应的key被获取到之后,就会将生成的新的key,调用Put函数放入到缓存中,而在这个操作过程中就会调用目标方法(也就是标注了@Cacheable注解的方法)将目标方法的返回值缓存到对应的Cache中。
注意:在方法执行前Cacheable注解标注的方法首先进行的操作就是检查缓存中是否存在对应的数据,如果存在直接获取,如果不存在就添加新的cache内容。

总结

1.使用CacheManager按照名字得到Cache组件
2.key是使用key生成器生成的,默认的是simpleKey生成器
3.如果进行调用的时候先是检查缓存,如果缓存中没有的话再进行查库操作。