使用 Spring缓存抽象 支持 EhCache 和 Redis 混合部署

本文介绍了如何在Spring框架中使用注释驱动的缓存技术,结合EhCache和Redis实现混合部署。内容涵盖EhCache和Redis的配置、Maven依赖、缓存应用,以及如何在实际应用中使用和测试混合部署的缓存系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

在分布式 web 项目中,通常即需要本地缓存提高程序性能,也需要共享缓存在多机之间交换数据。本文介绍了使用Spring缓存抽象机制混合部署的方法。

Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,使用Spring缓存抽象,程序员仅需要使用少量的注释就实现了对象的缓存,不必了解复杂的面向对象的编程(AOP)技术。

Spring 的缓存技术相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition, 而且支持 Ehcache 2.x, Gemfire cache, Caffeine, Guava caches 以及 JSR-107 兼容的缓存 (例如 Ehcache 3.x)产品。

本文第一部分简单介绍注释驱动的缓存技术,第二部分介绍使用 Redis 做共享缓存,第三部分介绍混合部署。

第一部分:注释驱动的缓存技术简介

这里假设你对Java缓存有一些了解,这里主要介绍 Spring4,EhCache3 的配置方法。其他配置请参考 Spring官网(英文)文档

1. 配置

配置类如下:

@Configuration
@EnableCaching
public class CacheConfig {

	// http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-store-configuration-jsr107
	//http://stackoverflow.com/questions/39386830/using-ehcache-3-with-spring-annotations-not-using-spring-boot
	
	@Bean
	public JCacheCacheManager jcacheCacheManager(){
		JCacheCacheManager cm = new JCacheCacheManager();
		cm.setCacheManager(jsr107cacheManager());
		return cm;
	}
	
	@Bean
	public CacheManager jsr107cacheManager(){
		//http://www.ehcache.org/documentation/3.1/107.html
		CachingProvider provider = Caching.getCachingProvider();
		CacheManager cacheManager = provider.getCacheManager();
	    
	    	MutableConfiguration<Long, String> configuration =
	        	new MutableConfiguration<Long, String>()
	            		// Cannot set type for store! this may be a bug in spring or ehCache
	        		//.setTypes(Long.class, String.class)
	        		.setStoreByValue(false) 
	        	.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE));
	    	cacheManager.createCache("foo", configuration);
		
		return cacheManager;
	}
	
}

@Configuration 表示这是一个配置类

@EnableCaching 启动 Cache 注释

第一个 Bean 声明 Spring Cache 的 CacheManager 抽象管理接口对象。该对象是 JSR107 缓存规范的一个适配器(Adapter)

第二个 Bean 是 JSR107规范缓存的管理对象。Caching.getCachingProvider() 能正确找到注册的具体实现模块,如 EhCache3。然后创建了 “foo” 缓存区域。

2. 加载配置

在程序启动时:

	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(PersistenceJDBCConfig.class);
		ctx.register(CacheConfig.class);
		ctx.register(RedisConfig.class);	
		ctx.refresh();
如果你不了解 Spring 配置类的加载,建议阅读: 使用 Java 配置进行 Spring bean 管理

3. Maven 依赖

<!-- spring cache support -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
  <version>${springframework.version}</version>
</dependency>

<!-- ehcache 3 & JSR107 -->
<dependency>
  <groupId>org.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>3.1.3</version>
</dependency>
<dependency>
  <groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
  <version>1.0.0</version>
</dependency>

注意:必须添加 ehcache3 让 JSR107 模块找到实现。

4.缓存应用

@Service
@Transactional
public class CacheService {
	
	@Cacheable("foo")
	public Foo findBook(String name) {
		System.out.print("cache....or not?\n");
		return new Foo("jdbc test1");
	}
}
假设你需要缓存结果,仅需要在@Service对象的方法上注释 @Cachable("foo") 即可实现该业务对象的缓存。

除 @Cacheable 外,常用的还有 @CachePut、@CacheEvict 注释,具体使用见:注释驱动的 Spring cache 缓存介绍 这些内容在新版本中没有变化。

注意:

(1)注意缓存与交易的关系。建议缓存在服务以上层使用,在 数据访问层(DAO)中谨慎使用!!!

(2)如果你喜欢JSR的注释,请参考Spring官方文档(英文)。


第二部分:使用 Redis 做共享缓存

Redis 的中文介绍:http://www.redis.cn/

1. 配置

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

	@Bean
	public JedisConnectionFactory redisConnectionFactory() {
		JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
		// Defaults
		redisConnectionFactory.setHostName("127.0.0.1");
		redisConnectionFactory.setPort(6379);
		return redisConnectionFactory;
	}

	@Bean
	public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory rf) {
		RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
		redisTemplate.setConnectionFactory(rf);
		return redisTemplate;
	}

	@Bean
	public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
		RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);

		// Number of seconds before expiration. Defaults to unlimited (0)
		cacheManager.setDefaultExpiration(10); // Sets the default expire time (in seconds)
		return cacheManager;
	}
}

注意:设置 Cache 过期时间要合适,太长就长期有效,太短你看不到测试结果。建议 10-30秒。

2. Maven 依赖

		<!-- redis cache related.....start -->
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.6.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.7.3</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-pool2</artifactId>
		    <version>2.4.2</version>
		</dependency>

		<!-- redis cache related.....end -->

其中,commons-pool2 是 访问 Redis 服务器的链接池需要的依赖。

具体代码见:Redis 缓存 + Spring 的集成示例 ,作者写的很不错。

第三部分:EhCache 与 Redis 混合部署支持

1. 原理与代码

通过前面的配置,我们发现 Spring 缓存抽象 就是在其他 Cache 管理程序基础上提出了自己的抽象体系,即利用适配器模式建立了通用的 Cache 管理体系。

找到 org.springframework.cache.CacheManager API文档,发现改接口相当简单。我们需要做的工作就是将两个 Manager 组合一下。 代码:

MixCacheManager 源代码:

public class MixCacheManager implements CacheManager {
	
	private CacheManager redisCacheManager;
	private CacheManager memCacheManager;
	

	private String redisPrefix = "redis-"; 
 
 	public Cache getCache(String arg0) {
		if (arg0.startsWith(redisPrefix))
			return redisCacheManager.getCache(arg0);
		else
 			return memCacheManager.getCache(arg0);
 	}

	public Collection<String> getCacheNames() {
		Collection<String> cacheNames = new ArrayList<String>();		
		if (redisCacheManager != null) {
			cacheNames.addAll(redisCacheManager.getCacheNames());
		}
		if (memCacheManager != null) {
			cacheNames.addAll(memCacheManager.getCacheNames());
		}		
		return cacheNames;
	}

	// getting & setting ...
}

该类非常简单。注意:redisCacheManager 和 memCacheManager 都是实现了 org.springframework.cache.CacheManager 接口的类。MixCacheManager也实现了该接口。

getCacheNames 的任务就是拼接所以的 Cache 区域;

getCache 就是根据应用需要的 Cache 名字,返回合适的 Cache。这里指定“redis-”开头的名字,使用共享缓存。

2. 配置

修改 RedisConfig 类,添加

	// Problem with Autowiring & No unique bean
	// http://stackoverflow.com/questions/2699608/problem-with-autowiring-no-unique-bean
	@Bean
	@Primary
	public CacheManager cacheManager(RedisCacheManager redisCacheManager,JCacheCacheManager jcacheCacheManager) {
		MixCacheManager cacheManager = new MixCacheManager();
		cacheManager.setRedisCacheManager(redisCacheManager);
		cacheManager.setMemCacheManager(jcacheCacheManager);
		return cacheManager;
	}
@Primary 是让 Spring Cache 知道,现在 org.springframework.cache.CacheManager 三个实现中,你该使用混合实现。

3.测试与使用

测试使用的服务代码:

@Service
@Transactional
public class CacheService {
	
	@Cacheable("foo")
	public Foo findBook(String name) {
		System.out.print("cache....or not?\n");
		return new Foo("jdbc test1");
	}
	
	@Cacheable("redis-foo")
	public Foo findBookRedis(String name) {
		System.out.print("cache Redis....or not?\n");
		return new Foo("jdbc test1");
	}

}

测试的使用代码:

	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(PersistenceJDBCConfig.class);
		ctx.register(CacheConfig.class);
		ctx.register(RedisConfig.class);	
		ctx.refresh();
		
		CacheService cs = ctx.getBean(CacheService.class);
		cs.findBook("naan1");
		cs.findBook("naan1");
		cs.findBook("naan1");
		cs.findBook("new1");
		
		cs.findBookRedis("redis");
		cs.findBookRedis("redis");
		cs.findBookRedis("redis");
		cs.findBookRedis("redis-new");
		


输出结果:

cache....or not?
cache....or not?
cache Redis....or not?
cache Redis....or not?

在 Redis 控制台上输入 keys * 看到建立的 key。

总结:

(1)Redis 与 内存缓存 混合使用非常简单实用。

(2)设计模式就是 Java 的魂。了解设计模式,就能看代码猜出有趣的实现。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值