【Spring Boot】(24)、Spring Boot中使用缓存之Spring缓存

版权声明:本文为博主原创文章,未经博主允许不得转载。请联系博主或者emailTo:[email protected],谢谢! https://blog.csdn.net/caychen/article/details/80464794

1、缓存依赖

只要添加如下依赖,即可使用缓存功能。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>


2、缓存注解

  • @EnableCaching:在主入口类上添加该注解,用于开启缓存;

  • @CacheConfig:标注在类上,表示当前类使用的缓存组件中的key为该注解指定的cacheNames/value,当该注解指定了cacheNames/value之后,@Cacheable上就无需再使用cacheNames/value了;

  • @Cacheable:将方法的结果进行缓存;

    • cacheNames/value:缓存组件的名字;

    • key:缓存数据使用的key,默认是使用方法参数的值,可以使用SpEL表达式,比如#id == #a0 == #p0 == #root.args[0]都表示使用第一个参数的值作为缓存的key;

    • keyGenerator:key的生成器,可以自己指定key的生成器的组件id;

    • cacheManager:指定缓存管理器;

    • cacheResolver:指定获取解析器;

    • condition:指定符合条件的情况下才缓存,支持SpEL表达式;

      • condition=“#id>1”:表示id的值大于1时才缓存。

    • unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,该属性可以获取到结果进行判断,unless与condition刚好相反;

      • unless=“#a0==2”:表示第一个参数的值为2时,查询结果不缓存。

    • sync:是否使用异步模式,使用sync后unless就不支持了;

      其中keyGeneratorkey二者只能选一,cacheResolvercacheManager也二者选一。

  • @CachePut:用于更新缓存,它先调用方法,然后将目标方法的结果进行缓存。

    能够更新缓存的前提是,@CachePut更新使用的key与@Cacheable使用的key要保持一致。

    @CachePut注解属性同@Cacheable的属性类似,少了sync属性。

  • @CacheEvict:缓存清除

    同样如果要清除缓存,则使用的key值要与@Cacheable使用的key一致,否则达不到清除缓存的功能。

    @CacheEvict注解属性比@Cacheable注解少了sync和unless,但是多了两个属性:allEntriesbeforeInvocation

    allEntries:表示是否删除所有缓存。

    beforeInvocation:表示删除缓存的操作是否在目标方法执行之前。


3、原理

CacheAutoConfiguration是缓存的自动配置类。

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
		RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
    
    //other code...
    
    static class CacheConfigurationImportSelector implements ImportSelector {

		@Override
		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;
		}
	}
}

经过如上代码,imports保存了默认支持的缓存配置类(有顺序):

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration

org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration

org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration

org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration

org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration

org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration

org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration

org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration

org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration

org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration

org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration


通过开启debug=true,查看控制台,可以发现:默认cache配置类生效的是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 this.customizerInvoker.customize(cacheManager);
	}

}

该类给容器中注册了一个ConcurrentMapCacheManager类型的cacheManager组件。

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {

	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);

    //other code...
    
    //从缓存ConcurrentMap对象中获取cacheNames/value指定的Cache对象。
    @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;
	}
    
    protected Cache createConcurrentMapCache(String name) {
		SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
		return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
				isAllowNullValues(), actualSerialization);

	}
}

该组件可以获取和创建ConcurrentMapCache类型的缓存组件,作用是将数据保存在ConcurrentMap对象中。

public class ConcurrentMapCache extends AbstractValueAdaptingCache {

    //真实用于存储缓存数据的属性
	private final ConcurrentMap<Object, Object> store;

    @Override
	public final String getName() {
		return this.name;
	}
    
    @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 void evict(Object key) {
		this.store.remove(key);
	}

	@Override
	public void clear() {
		this.store.clear();
	}
    
    //other code...
}

运行流程:

​ 1)、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字去缓存中获取(CacheManager先获取相应的缓存),第一次获取缓存,如果没有Cache组件,则会自动创建。

​ 2)、去Cache中查找缓存的内容,使用一个key(默认是方法的参数),其中key是按照某种策略生成的,默认是使用SimpleKeyGenerator生成的。

SimpleKeyGenerator生成key的策略:

​ 如果没有参数,key=new SimpleKey();

​ 如果有一个参数,key=参数值

​ 如果有多个参数,key=new SimpleKey(params);

​ 3)、没有查到缓存,就调用目标方法;如果查到缓存,则直接返回结果,不调用目标方法。

​ 4)、没有查到缓存后去调用目标方法,然后将目标方法返回的结果放进缓存。


4、注解解释

@Cacheable:存储缓存

自定义key:

​ methodName:当前被调用的方法名,例如#root.methodName

​ method:当前被调用的方法,例如#root.method.name

​ target:当前被调用的目标对象,例如#root.target

​ targetClass:当前被调用的目标对象类,例如#root.targetClass

​ args:当前被调用的方法的参数列表,例如#root.args[0]

​ caches:当前方法调用使用的缓存列表,

​ argument name:方法参数的名字,可以直接#参数名,也可以使用#a0或者#p0的形式,0表示参数索引,例如#id,#a0,#p0等。

例如:

@Cacheable(cacheNames = "emp", key = "#root.methodName+'[' + #id + ']'")

当然也可以自定义KeyGenerator:

@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
    return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
}

则在@Cacheable上使用该自定义的KeyGenerator:

@Cacheable(cacheNames = "emp", keyGenerator = "myKeyGenerator")


@CachePut:同步更新缓存

它主要用于更新缓存,它先调用方法,然后将目标方法的结果进行缓存。但是能够更新缓存的前提是,@CachePut更新使用的key与@Cacheable使用的key要保持一致

@Cacheable(cacheNames = "emp", key = "#a0")
public Employee getById(Integer id){}

@CachePut(cacheNames = "emp", key = "#result.id")
public Employee update(Employee employee){}

其中#result表示目标方法的返回值。因为@CachePut更新缓存比目标方法晚,所有@CachePut能获取到返回值。而@Cacheable比目标方法早,所以无法使用#result。


@CacheEvict:缓存清除

同样如果要清除缓存,则使用的key值要与@Cacheable使用的key一致,否则达不到清除缓存的功能。

​ 属性allEntries,默认为false,表示不把cacheNames里所有的缓存都删除掉。当该值为true的时候,会把缓存中对应的cacheNames里的所有缓存都删除。

​ 属性beforeInvocation,默认为false,表示删除缓存的操作在目标方法执行之后。当该值为true的时候,则删除缓存的操作在目标方法执行之前。区别:如果设为true,当目标方法执行出现异常后,对应的缓存已经被删除了。如果设为false(默认),当目标方法执行出现异常后,就不会把缓存删除掉。


====================打个广告,欢迎关注====================

QQ:
412425870
微信公众号:Cay课堂

csdn博客:
http://blog.csdn.net/caychen
码云:
https://gitee.com/caychen/
github:
https://github.com/caychen

点击群号或者扫描二维码即可加入QQ群:

328243383(1群)




点击群号或者扫描二维码即可加入QQ群:

180479701(2群)




猜你喜欢

转载自blog.csdn.net/caychen/article/details/80464794