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

概述

在分布式 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 configuration =
	        	new MutableConfiguration()
	            		// 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 依赖



  org.springframework
  spring-context-support
  ${springframework.version}




  org.ehcache
  ehcache
  3.1.3


  javax.cache
  cache-api
  1.0.0

注意:必须添加 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 redisTemplate(RedisConnectionFactory rf) {
		RedisTemplate redisTemplate = new RedisTemplate();
		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 依赖

		
		
			org.springframework.data
			spring-data-redis
			1.6.0.RELEASE
		
		
			redis.clients
			jedis
			2.7.3
		
		
		
		    org.apache.commons
		    commons-pool2
		    2.4.2
		

		

其中,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 getCacheNames() {
		Collection cacheNames = new ArrayList();		
		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 的魂。了解设计模式,就能看代码猜出有趣的实现。



你可能感兴趣的:(Java&Spring)