码迷,mamicode.com
首页 > 编程语言 > 详细

Spring MVC整合Memcached基于注释的实践使用

时间:2015-07-23 08:17:03      阅读:246      评论:0      收藏:0      [点我收藏+]

标签:memcached   spring cache   缓存自定义注释使用   spring整合memcached   

  本文并不介绍memcached的安装使用,也不长篇大论哪个缓存框架性能好。而是结合自己实际开发,来谈谈自己的使用。

  一、配置文件application-cache.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:jee="http://www.springframework.org/schema/jee"
	xmlns:tx="http://www.springframework.org/schema/tx" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
						http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
						http://www.springframework.org/schema/tx 
						http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 
						http://www.springframework.org/schema/jee 
						http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
						http://www.springframework.org/schema/aop 
						http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
						http://www.springframework.org/schema/context 
						http://www.springframework.org/schema/context/spring-context-3.1.xsd 
						http://www.springframework.org/schema/jdbc 
						http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd 
						http://www.springframework.org/schema/cache 
						http://www.springframework.org/schema/cache/spring-cache-3.1.xsd"
	default-lazy-init="true">
	
	<!-- 缓存服务 -->
	<!-- 自定义key生成策略-->
	<!-- <cache:annotation-driven key-generator="stringKeyGenerator" />
	<bean id="stringKeyGenerator" class="cn.roomy.supply.metadata.cache.StringKeyGenerator" /> -->
	<!--Spring 3.1 引入了基于注释(annotation)的缓存(cache)技术 -->
	<cache:annotation-driven cache-manager="cacheManager" />
	
	<bean id="cacheManager" class="cn.roomy.supply.metadata.cache.MemcacheCacheManager">
	    <!-- 是否事务环绕的,如果true,则如果事务回滚,缓存也回滚,默认false -->
	    <property name="transactionAware" value="true" />
		<property name="caches">
			<set>
                <bean class="cn.roomy.supply.metadata.cache.MemcachedSpringCache">
					<property name="name" value="r" />
					<property name="memcachedClient" ref="memcachedClient4User" />
					<!-- 1天 -->
					<property name="expiredDuration" value="86400" />
				</bean>
				<bean class="cn.roomy.supply.metadata.cache.MemcachedSpringCache">
					<property name="name" value="s" />
					<property name="memcachedClient" ref="memcachedClient4User" />
					<!-- 30天 -->
					<property name="expiredDuration" value="2592000" />
				</bean>
				<bean class="cn.roomy.supply.metadata.cache.MemcachedSpringCache">
					<property name="name" value="v" />
					<property name="memcachedClient" ref="memcachedClient4User" />
					<!-- 7天 -->
					<property name="expiredDuration" value="604800" />
				</bean>
			</set>
		</property>
	</bean>
    
    <bean id="memcachedClient4User" class="net.spy.memcached.spring.MemcachedClientFactoryBean">
        <property name="servers" value="${memcached.url}"/>
        <!-- 指定要使用的协议(BINARY,TEXT),默认是TEXT -->
        <property name="protocol" value="TEXT"/>
        <!-- 设置默认的转码器(默认以net.spy.memcached.transcoders.SerializingTranscoder) -->
        <property name="transcoder">
            <bean class="net.spy.memcached.transcoders.SerializingTranscoder">
                <property name="compressionThreshold" value="1024"/>
            </bean>
        </property>
        <!-- 以毫秒为单位设置默认的操作超时时间 -->
        <property name="opTimeout" value="3000"/>
        <property name="timeoutExceptionThreshold" value="19980"/>
        <!-- 设置哈希算法 -->
        <property name="hashAlg">
            <value type="net.spy.memcached.DefaultHashAlgorithm">KETAMA_HASH</value>
        </property>
        <!-- 设置定位器类型(ARRAY_MOD,CONSISTENT),默认是ARRAY_MOD -->
        <property name="locatorType" value="CONSISTENT"/>
        <!-- 设置故障模式(取消,重新分配,重试),默认是重新分配 --> 
        <property name="failureMode" value="Redistribute"/>
        <!-- 
        <property name="failureMode" value="Retry"/>
         -->
         <!-- 如果你想使用Nagle算法,设置为true -->
        <property name="useNagleAlgorithm" value="false"/>
    </bean>
    
    
</beans>

  1、配置文件有一个关键的支持缓存的配置项:<cache:annotation-driven cache-manager="cacheManager" />,这个配置项缺省使用了一个名字叫 cacheManager 的缓存管理器,我们自定义了一个缓存管理器MemcacheCacheManager,它需要配置一个属性 caches,即此缓存管理器管理的缓存集合。自定义MemcacheSpringCache配置了各个cache的有效时间。

  2、引入第三方spy的客户端MenCacheClientFactoryBean,设置其相关属性。


二、MemcacheCacheManager与MemcacheSpringCache

/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.roomy.supply.metadata.cache;

import java.util.Collection;

import org.springframework.cache.Cache;
import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager;

public class MemcacheCacheManager extends AbstractTransactionSupportingCacheManager {

	public MemcacheCacheManager() {
	}
	
	private Collection<? extends Cache> caches;

    /**
     * Specify the collection of Cache instances to use for this CacheManager.
     */
    public void setCaches(Collection<? extends Cache> caches) {
        this.caches = caches;
    }

    @Override
    protected Collection<? extends Cache> loadCaches() {
        return this.caches;
    }

}

  这里的重点在继承了AbstractTransactionSupportingCacheManager抽象类,看类名大概也知道它的主要作用,对Spring事务的支持!即如果事务回滚了,Cache的数据也会移除掉。看其源码可知,其继承了AbstractCacheManager,而AbstractCacheManager实现了CacheManager接口。

package cn.roomy.supply.metadata.cache;

import java.io.Serializable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import net.spy.memcached.MemcachedClient;

import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.util.Assert;

/**
 * 基于Spring Cache抽象体系的Memcached缓存实现
 */
public class MemcachedSpringCache implements Cache, InitializingBean {

	private final static Logger logger = LoggerFactory.getLogger(MemcachedSpringCache.class);

	private String name;

	private MemcachedClient memcachedClient;

	/**
	 * 默认最长缓存时间为1小时
	 */
	private static final int MAX_EXPIRED_DURATION = 60 * 60;

	/** Null值的最长缓存时间 */
	private static final int NULL_VALUE_EXPIRATION = 60 * 60 * 24 * 7;

	/** 增量过期时间允许设置的最大值 */
	private static final int DELTA_EXPIRATION_THRESHOLD = 60 * 60 * 24 * 30;

	/**
	 * 缓存数据超时时间
	 */
	private int expiredDuration = MAX_EXPIRED_DURATION;

	private static final Object NULL_HOLDER = new NullHolder();

	private boolean allowNullValues = true;

	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(memcachedClient, "memcachedClient must not be null!");
	}

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

	@Override
	public MemcachedClient getNativeCache() {
		return this.memcachedClient;
	}

	/**
	 * 根据key得到一个ValueWrapper,然后调用其get方法获取值
	 */
	@Override
	public ValueWrapper get(Object key) {
		String cacheKey = getCacheKey(key);
		try {
			StopWatch sw = new StopWatch();
			sw.start();
			Object value = memcachedClient.get(cacheKey);
			sw.stop();
			if (sw.getTime() > 50) {
				logger.info("读取memcached用时{}, key={}", sw.getTime(), cacheKey);
			}
			return (value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null);
		} catch (Exception e) {
			logger.error("读取memcached缓存发生异常, key={}, server={}", cacheKey,
					memcachedClient.getNodeLocator().getPrimary(cacheKey).getSocketAddress(), e.getCause());
			return null;
		}
	}

	/**
	 * 根据key,和value的类型直接获取value  
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T get(Object key, Class<T> type) {
		ValueWrapper element = get(key);
		Object value = (element != null ? element.get() : null);
		if (value == null)
			return null;
		if (type != null && !type.isInstance(value)) {
			throw new IllegalStateException("缓存的值类型指定错误 [" + type.getName() + "]: " + value);
		}
		return (T) value;
	}

	/**
	 * 存入到缓存的key,由缓存的区域+key对象值串接而成
	 * @param key key对象
	 * @return 
	 */
	private String getCacheKey(Object key) {
		return this.name + key.toString();
	}

	/**
	 * 往缓存放数据
	 * 安全的Set方法,在3秒内返回结果, 否则取消操作.
	 */
	@Override
	public void put(Object key, Object value) {
		String cacheKey = getCacheKey(key);
		logger.debug("放入缓存的Key:{}, Value:{}, StoreValue:{}", cacheKey, value, toStoreValue(value));
		int expiration = expiredDuration;
		if (value == null) {
			if (allowNullValues) {
				value = NULL_HOLDER; // 若允许缓存空值,则替换null为占坑对象;不允许直接缓存null,因为无法序列化
			}
			if (expiredDuration > NULL_VALUE_EXPIRATION) {
				expiration = NULL_VALUE_EXPIRATION; // 缩短空值的过期时间,最长缓存7天
			}
		} else if (expiredDuration > DELTA_EXPIRATION_THRESHOLD) {
			expiration += (int) (System.currentTimeMillis() / 1000); // 修改为UNIX时间戳类型的过期时间,使能够设置超过30天的过期时间
																		// 注意:时间戳计算这里有2038问题,
																		// 2038-1-19 11:14:07 (GMT +8) 后,转换成的 int 会溢出,导致出现负值
		}

		Future<Boolean> future = memcachedClient.set(cacheKey, expiration, value);
		try {
			future.get(3, TimeUnit.SECONDS);
		} catch (Exception e) {
			future.cancel(false);
			logger.error("memcached写入缓存发生异常, key={}, server={}", cacheKey,
					memcachedClient.getNodeLocator().getPrimary(cacheKey).getSocketAddress(), e);
		}
	}

	/**
	 * 从缓存中移除key对应的缓存
	 * 安全的evict方法,在3秒内返回结果, 否则取消操作.
	 */
	@Override
	public void evict(Object key) {
		String cacheKey = getCacheKey(key);
		logger.debug("删除缓存的Key:{}", cacheKey);
		Future<Boolean> future = memcachedClient.delete(cacheKey);
		try {
			future.get(3, TimeUnit.SECONDS);
		} catch (Exception e) {
			future.cancel(false);
			logger.error("memcached清除缓存出现异常, key={}, server={}", cacheKey,
					memcachedClient.getNodeLocator().getPrimary(cacheKey).getSocketAddress(), e);
		}
	}

	@Override
	public void clear() {
		try {
			memcachedClient.flush();
		} catch (Exception e) {
			logger.error("memcached执行flush出现异常", e);
		}
	}

	protected Object fromStoreValue(Object storeValue) {
		if (this.allowNullValues && storeValue instanceof NullHolder) {
			return null;
		}
		return storeValue;
	}

	private static class NullHolder implements Serializable {
		private static final long serialVersionUID = -99681708140860560L;
	}

	protected Object toStoreValue(Object userValue) {
		if (this.allowNullValues && userValue == null) {
			return NULL_HOLDER;
		}
		return userValue;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setMemcachedClient(MemcachedClient memcachedClient) {
		this.memcachedClient = memcachedClient;
	}

	public void setExpiredDuration(int expiredDuration) {
		this.expiredDuration = expiredDuration;
	}

	public void setAllowNullValues(boolean allowNullValues) {
		this.allowNullValues = allowNullValues;
	}

}

  这类实现了Cache api接口,重写了存入、取出、移除方法,并做了空值优化。所以之后的缓存操作将调用此自定义的方法。


三、自定义注释

//把@Cacheable自定义成注释@CacheableRelation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(value = "r", key="#distributId + ‘_‘ + #supplierId")
public @interface CacheableRelation {

}
//把@CacheEvict自定义成注释@CacheEvictRelation 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@CacheEvict(value = "r", key="#relation.distributor.id + ‘_‘ + #relation.supplierId")
public @interface CacheEvictRelation {

}
//把@CachePut自定义成注释@CachePutRelation 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@CachePut(value = "s", key="#relation.distributId + ‘_‘ + #relation.supplierId")
public @interface CachePutRelation {

}

  关于@Cacheable、@CacheEvict、@CachePut等注释的用法,这儿就不介绍了。重点在于value与key,这时你会发现,这个value在配置文件中有出现过,当时有设置其有效时间!而key的设定则可参照SpEL用法,当然其注释的属性中还有condition等条件判断,篇幅有限,请自行查阅。


四、参考文章

开涛大神的博客文章:Spring Cache抽象详解 

IBM developerworks:注释驱动的 Spring cache 缓存介绍



本文出自 “学而思” 博客,请务必保留此出处http://linhongyu.blog.51cto.com/6373370/1677336

Spring MVC整合Memcached基于注释的实践使用

标签:memcached   spring cache   缓存自定义注释使用   spring整合memcached   

原文地址:http://linhongyu.blog.51cto.com/6373370/1677336

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!