草庐IT

springboot缓存之CacheManager详解

好饿啊早知道不学java了 2024-05-02 原文

spring-cache

spring 提供了spring-cache上层接口给大家实现,其中有一些方便操作缓存的注解,诸如@Cacheable、@CacheEvict等等。今天就来学习一下redis的实现 spring-data-redis.

配置缓存需要配置一个CacheManager

public interface CacheManager {

	/**
	 * Get the cache associated with the given name.
	 * <p>Note that the cache may be lazily created at runtime if the
	 * native provider supports it.
	 * @param name the cache identifier (must not be {@code null})
	 * @return the associated cache, or {@code null} if such a cache
	 * does not exist or could be not created
	 */
	@Nullable
	Cache getCache(String name);

	/**
	 * Get a collection of the cache names known by this manager.
	 * @return the names of all caches known by the cache manager
	 */
	Collection<String> getCacheNames();

}

这个顶层接口有一个抽象类AbstractCacheManager

public abstract class AbstractCacheManager implements CacheManager, InitializingBean {

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

	private volatile Set<String> cacheNames = Collections.emptySet();


	// Early cache initialization on startup

	@Override
	public void afterPropertiesSet() {
		initializeCaches();
	}

	/**
	 * Initialize the static configuration of caches.
	 * <p>Triggered on startup through {@link #afterPropertiesSet()};
	 * can also be called to re-initialize at runtime.
	 * @since 4.2.2
	 * @see #loadCaches()
	 */
	public void initializeCaches() {
		Collection<? extends Cache> caches = loadCaches();

		synchronized (this.cacheMap) {
			this.cacheNames = Collections.emptySet();
			this.cacheMap.clear();
			Set<String> cacheNames = new LinkedHashSet<>(caches.size());
			for (Cache cache : caches) {
				String name = cache.getName();
				this.cacheMap.put(name, decorateCache(cache));
				cacheNames.add(name);
			}
			this.cacheNames = Collections.unmodifiableSet(cacheNames);
		}
	}

	/**
	 * Load the initial caches for this cache manager.
	 * <p>Called by {@link #afterPropertiesSet()} on startup.
	 * The returned collection may be empty but must not be {@code null}.
	 */
	protected abstract Collection<? extends Cache> loadCaches();


	// Lazy cache initialization on access

	@Override
	@Nullable
	public Cache getCache(String name) {
		// Quick check for existing cache...
		Cache cache = this.cacheMap.get(name);
		if (cache != null) {
			return cache;
		}

		// The provider may support on-demand cache creation...
		Cache missingCache = getMissingCache(name);
		if (missingCache != null) {
			// Fully synchronize now for missing cache registration
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					cache = decorateCache(missingCache);
					this.cacheMap.put(name, cache);
					updateCacheNames(name);
				}
			}
		}
		return cache;
	}

	@Override
	public Collection<String> getCacheNames() {
		return this.cacheNames;
	}


	// Common cache initialization delegates for subclasses

	/**
	 * Check for a registered cache of the given name.
	 * In contrast to {@link #getCache(String)}, this method does not trigger
	 * the lazy creation of missing caches via {@link #getMissingCache(String)}.
	 * @param name the cache identifier (must not be {@code null})
	 * @return the associated Cache instance, or {@code null} if none found
	 * @since 4.1
	 * @see #getCache(String)
	 * @see #getMissingCache(String)
	 */
	@Nullable
	protected final Cache lookupCache(String name) {
		return this.cacheMap.get(name);
	}

	/**
	 * Dynamically register an additional Cache with this manager.
	 * @param cache the Cache to register
	 * @deprecated as of Spring 4.3, in favor of {@link #getMissingCache(String)}
	 */
	@Deprecated
	protected final void addCache(Cache cache) {
		String name = cache.getName();
		synchronized (this.cacheMap) {
			if (this.cacheMap.put(name, decorateCache(cache)) == null) {
				updateCacheNames(name);
			}
		}
	}

	/**
	 * Update the exposed {@link #cacheNames} set with the given name.
	 * <p>This will always be called within a full {@link #cacheMap} lock
	 * and effectively behaves like a {@code CopyOnWriteArraySet} with
	 * preserved order but exposed as an unmodifiable reference.
	 * @param name the name of the cache to be added
	 */
	private void updateCacheNames(String name) {
		Set<String> cacheNames = new LinkedHashSet<>(this.cacheNames);
		cacheNames.add(name);
		this.cacheNames = Collections.unmodifiableSet(cacheNames);
	}


	// Overridable template methods for cache initialization

	/**
	 * Decorate the given Cache object if necessary.
	 * @param cache the Cache object to be added to this CacheManager
	 * @return the decorated Cache object to be used instead,
	 * or simply the passed-in Cache object by default
	 */
	protected Cache decorateCache(Cache cache) {
		return cache;
	}

	/**
	 * Return a missing cache with the specified {@code name}, or {@code null} if
	 * such a cache does not exist or could not be created on demand.
	 * <p>Caches may be lazily created at runtime if the native provider supports it.
	 * If a lookup by name does not yield any result, an {@code AbstractCacheManager}
	 * subclass gets a chance to register such a cache at runtime. The returned cache
	 * will be automatically added to this cache manager.
	 * @param name the name of the cache to retrieve
	 * @return the missing cache, or {@code null} if no such cache exists or could be
	 * created on demand
	 * @since 4.1
	 * @see #getCache(String)
	 */
	@Nullable
	protected Cache getMissingCache(String name) {
		return null;
	}

}

各个厂商都会提供一个对应的CacheManager实现这个抽象类,例如redis的RedisCacheManager.
首先我们对AbstractCacheManager进行解读,看看里面都有些什么操作。

	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
	private volatile Set<String> cacheNames = Collections.emptySet();

这两个成员变量存的是缓存map和缓存名称。

@Override
public void afterPropertiesSet() {
	initializeCaches();
}

实现了InitializingBean在装在这个bean的时候调用initializeCaches();方法进行初始化。下面看一下这个方法。

public void initializeCaches() {
		// 调用抽象方法loadCaches();获取缓存
		Collection<? extends Cache> caches = loadCaches();
		// 线程安全的初始化成员变量cacheMap和cacheNames 
		synchronized (this.cacheMap) {
			// 初始化为空
			this.cacheNames = Collections.emptySet();
			this.cacheMap.clear();
			Set<String> cacheNames = new LinkedHashSet<>(caches.size());
			// 获取诸如配置文件中配置的缓存名称
			for (Cache cache : caches) {
				//获取缓存名称 
				String name = cache.getName();
				// 装饰之后放入map中
				this.cacheMap.put(name, decorateCache(cache));
				// 将缓存名称放入cacheNames
				cacheNames.add(name);
			}
			// 将cacheNames设置成不可修改的set集合
			this.cacheNames = Collections.unmodifiableSet(cacheNames);
		}
	}

看一下初始化的缓存从哪里来的。看看这个方法的定义。

protected abstract Collection<? extends Cache> loadCaches();

看一下RedisCacheManager中的实现。

	@Override
	protected Collection<RedisCache> loadCaches() {

		List<RedisCache> caches = new LinkedList<>();
		// 从配置中获取配置的缓存
		for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
			// 封装成RedisCache放在集合里
			caches.add(createRedisCache(entry.getKey(), entry.getValue()));
		}

		return caches;
	}

这里的initialCacheConfiguration的定义如下:

private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;

左边的String是缓存名称。
我们在配置文件中配置cache-names等等最终就会放在这个initialCacheConfiguration里面。具体放进去的过程下次写整合springCache的redis实现的时候介绍他的自动装配过程以及redis的缓存实现。配置文件中比如这样写:

#配置spring-cache
spring:
  cache:
    # 缓存使用的实现是redis
    type: redis
    # 初始化缓存的两个名称 
    #如果这里配置了那系统里@Cacheable注解的缓存就只能是这些缓存,
    #没有配置就是自动动态创建 下文会介绍 
    cache-names: 'demo,test'
    #对redis的一些配置
    redis:
   	  #自动生成key的前缀
      key-prefix: 'tabtan:'
      #默认过期时间
      time-to-live: 3600000

这其中cache-names中的每一个缓存名称就会作为initialCacheConfigurationMap的键。根据一些其他的配置封装成的缓存就会成为对应的值。
回过头来再看初始化方法initializeCaches()开头的loadCaches()方法。

Collection<? extends Cache> caches = loadCaches();

redis的实现把你配置的caches封装成了List<RedisCache>返回到了这里。
至此初始化就完成。(RedisCache实现了Cache
在说其他方法之前,我想说这种具体实现延迟到子类的方式是十分常用的,可以借鉴学习。
下面开始介绍CacheManager接口的第一个方法Cache getCache(String name);
看一下接口中如何定义

	/**
	 * Get the cache associated with the given name.
	 * 获取与给定名称关联的缓存。
	 * <p>Note that the cache may be lazily created at runtime if the
	 * native provider supports it.
	 * @param name the cache identifier (must not be {@code null})
	 * @param name 缓存名称 不能为空
	 * @return the associated cache, or {@code null} if such a cache
	 * does not exist or could be not created
	 */
	@Nullable
	Cache getCache(String name);

看一下抽象类中的实现

@Override
@Nullable
public Cache getCache(String name) {
	// 获取初始化后的cacheMap看看有没有这个缓存 有直接返回 没有就getMissingCache(name)动态创建
	Cache cache = this.cacheMap.get(name);
	if (cache != null) {
		return cache;
	}

	// The provider may support on-demand cache creation...
	// 上边的英文注释意思大致是 实现可能支持按需创建缓存 
	//我们今天看的是redis实现 是支持动态按需创建的
	// 同样这个getMissingCache提供给子类实现 但不是抽象方法。
	Cache missingCache = getMissingCache(name);
	// 判断一下getMissingCache(name)之后是不是空 空的话就返回空的
	if (missingCache != null) {
		// Fully synchronize now for missing cache registration
		// 上锁,防止注册丢失
		synchronized (this.cacheMap) {
			cache = this.cacheMap.get(name);
			// double check
			if (cache == null) {
				// 装饰好按需创建的
				cache = decorateCache(missingCache);
				// 放到map里
				this.cacheMap.put(name, cache);
				// 更新缓存的名字 这个方法是借口的第二个方法
				updateCacheNames(name);
			}
		}
	}
	return cache;
}

看一下getMissingCache(name)的定义。

@Nullable
protected Cache getMissingCache(String name) {
	return null;
}

默认返回null,意思就是不支持按需创建缓存,没有把这个方法定义成抽象方法的原因就在这里,如果具体实现不想支持按需创建缓存的话就不需要实现这个方法。redis是支持的我们看一下具体实现。

@Override
protected RedisCache getMissingCache(String name) {
	// 默认支持动态创建就createRedisCache(name, defaultCacheConfig)创建 如果配置关闭了动态创建就直接
	return allowInFlightCacheCreation ? createRedisCache(name, defaultCacheConfig) : null;
}

关闭动态创建的配置方法为disableCreateOnMissingCache()

public RedisCacheManagerBuilder disableCreateOnMissingCache() {

	this.allowInFlightCacheCreation = false;
	return this;
}

值得关注的是,这个关闭动态创建缓存的方法只在RedisCacheManagerBuilder能配置,配置文件里没有 你想配置的话可以自己写。调用这个方法组合cache-names可以限制系统中的缓存名称只能是在cache-names配置中的这些。

有关springboot缓存之CacheManager详解的更多相关文章

  1. ruby - 如何在 Ubuntu 中清除 Ruby Phusion Passenger 的缓存? - 2

    我试过重新启动apache,缓存的页面仍然出现,所以一定有一个文件夹在某个地方。我没有“公共(public)/缓存”,那么我还应该查看哪些其他地方?是否有一个URL标志也可以触发此效果? 最佳答案 您需要触摸一个文件才能清除phusion,例如:touch/webapps/mycook/tmp/restart.txt参见docs 关于ruby-如何在Ubuntu中清除RubyPhusionPassenger的缓存?,我们在StackOverflow上找到一个类似的问题:

  2. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

    尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

  3. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  4. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  5. ruby-on-rails - bundle 安装尝试使用缓存文件 - 2

    当我尝试进行bundle安装时,我的gem_path和gem_home指向/usr/local/rvm/gems/我没有写入权限,并且由于权限无效而失败。因此,我已将两个路径都更改为我具有写入权限的本地目录。这样做时,我进行了bundle安装,我得到:bruno@test6:~$bundleinstallFetchinggemmetadatafromhttps://rubygems.org/.........Fetchinggemmetadatafromhttps://rubygems.org/..Bundler::GemspecError:Couldnotreadgemat/afs/

  6. ruby-on-rails - Heroku Action 缓存似乎不起作用 - 2

    我一直在Heroku上尝试不同的缓存策略,并添加了他们的memcached附加组件,目的是为我的应用程序添加Action缓存。但是,当我在我当前的应用程序上查看Rails.cache.stats时(安装了memcached并使用dalligem),在执行应该缓存的操作后,我得到current和total_items为0。在Controller的顶部,我想缓存我有的Action:caches_action:show此外,我修改了我的环境配置(对于在Heroku上运行的配置)config.cache_store=:dalli_store我是否可以查看其他一些统计数据,看看它是否有效或我做错

  7. ruby-on-rails - rails expire_page 没有删除缓存的文件 - 2

    我有一个具有页面缓存的ControllerAction,我制作了一个清扫程序,它使用Controller和指定的Action调用expire_page...Controller操作呈现一个js.erb模板,所以我试图确保expire_page删除public/javascripts中的.js文件,但它没有这样做。classJavascriptsController"javascripts",:action=>"lol",:format=>'js')endend...所以,我访问javascripts/lol.js并呈现我的模板。我验证了public/javascripts/lol.js

  8. ruby-on-rails - rails 3 缓存 : expire action for named route - 2

    我的Controller有这个:caches_action:render_ticker_for_channel,:expires_in=>30.seconds在我的路由文件中我有这个:match'/render_c_t/:channel_id'=>'render#render_ticker_for_channel',:as=>:render_channel_ticker在日志文件中我看到了这个:Writefragmentviews/mcr3.dev/render_c_t/63(11.6ms)我如何手动使它过期?我需要从与渲染Controller不同的Controller使它过期,但即使

  9. ruby - 更新 gem 时 Docker 包安装缓存问题 - 2

    我在开发和生产中都使用docker,真正困扰我的一件事是docker缓存的简单性。我的ruby​​应用程序需要bundleinstall来安装依赖项,因此我从以下Dockerfile开始:添加GemfileGemfile添加Gemfile.lockGemfile.lock运行bundleinstall--path/root/bundle所有依赖项都被缓存,并且在我添加新gem之前效果很好。即使我添加的gem只有0.5MB,从头开始安装所有应用程序gem仍然需要10-15分钟。由于依赖项文件夹的大小(大约300MB),然后再花10分钟来部署它。我在node_modules和npm上遇到了

  10. ruby-on-rails - 在缓存中找不到 StaleElementReference 错误元素 - 2

    我正在使用Capybara2.1和Ruby1.9.3,使用selenium驱动程序(带有Minitest和测试单元)来测试网络应用程序。我正在努力解决StaleElementReferenceException问题。我已经看到很多关于该主题的讨论,但我无法找到解决我所面临问题的方法。所以基本上,我试图使用以下代码在我的页面上找到所有分页元素:pagination_elements=page.all('.paginationa')然后我对这些元素做一些断言,例如:pagination_elements.first.must_have_content('1')在这些断言之后,我通过单击下一

随机推荐