【spring-data-redis】實現列表緩存方案---代碼實現

一、配置pom.xmljava

<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.7.2.RELEASE</version>
		</dependency>

二、配置springweb

cache:annotation-driven開啓註解,其中的選項mode這裏要說明一下,proxy|aspectj指定使用哪一種代理方式。proxy代理使用的是基於spring-AOP技術,aspectj代理則使用的Aspectj切面技術。若是使用aspectj選項須要引入對spring-aspects.jar的依賴,並開啓load-time weaving (or compile-time weaving)功能。redis

其中碰到的坑是註解不生效。緣由是,在默認狀況下,mode爲proxy代理,代碼在同一個具體類的方法中去調用該類的另外一個方法時,被調用的方法被設置了cache註解,可是因爲spring AOP不支持類中方法間的調用的代理。解決辦法是將被調用的方法重構到另外一個類中,cache註解生效。spring

<!-- 啓用緩存註解功能,這個是必須的,不然註解不會生效,另外,該註解必定要聲明在spring主配置文件中才會生效 -->
	<cache:annotation-driven cache-manager="cacheManager" key-generator="keyGenerator" />


	<bean id="jedisConnectionFactory"
		class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
		<property name="hostName" value="${redis.host}" />
		<property name="port" value="${redis.port}" />
		<property name="timeout" value="${redis.timeout}" />
		<!-- 引用配置文件 spring-context-jedis.xml 中的bean -->
		<property name="poolConfig" ref="jedisPoolConfig" />
	</bean>

	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory" />
		<property name="keySerializer">
			<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="valueSerializer">
			<bean class="com.frbao.web.common.cache.RedisObjectSerializer" />
		</property>
		<property name="hashKeySerializer">
			<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="hashValueSerializer">
			<bean class="com.frbao.web.common.cache.RedisObjectSerializer" />
		</property>
	</bean>

	<!-- spring本身的緩存管理器,這裏定義了緩存位置名稱 ,即註解中的value -->
	<bean id="cacheManager" class="com.frbao.web.common.cache.RedisCacheManager">
		<constructor-arg index="0" ref="redisTemplate" />
	</bean>
	
	<!-- 方法上使用@Cacheable時,針對方法處理的key的生成器 -->
	<bean id="keyGenerator" class="com.frbao.web.common.cache.DefaultCacheMethodKeyGenerator" />

三、數據查詢方法實現緩存

查詢時,支持根據指定參數flush判斷是否刷新緩存。其中flush參數不進行緩存的key生成app

@CachePut(value = MethodCacheKey.PROJECLIST, condition = "#flush")
    @Cacheable(value = MethodCacheKey.PROJECLIST, condition = "!#flush")
    public ObjectListDto fetchProjectList(final FetchProjectListParameter parameterObject,
            @KeyParam(include = false) boolean flush) {
        ObjectListDto objectList = new ObjectListDto();
        objectList = RemoteInvoker.doInvoke(new InvocationHandler<ObjectListDto>() {
            @Override
            public ObjectListDto execute() {

                if (parameterObject.orderBy == 0) {
                    return projectListService.selectProjectList(parameterObject.itype, parameterObject.fyearincomerate,
                            parameterObject.idaycount, parameterObject.pageNo, parameterObject.pageSize);
                } else {
                    return projectListService.selectProjectListOrderbyPage(parameterObject.itype,
                            parameterObject.fyearincomerate, parameterObject.idaycount, parameterObject.orderBy,
                            parameterObject.pageNo, parameterObject.pageSize);
                }

            }
        }, InterfaceNo.INVEST_0_002);

        return objectList;
    }

四、緩存key註解ide

在生成key時,指定include爲false的參數不進行拼接函數

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface KeyParam {
    boolean include() default true;
}

五、緩存key生成器fetch

解析請求參數,若是註解KeyParam的參數指定include爲false則不進行拼接。ui

public class DefaultCacheMethodKeyGenerator implements KeyGenerator {

    /**
     * 對參數進行拼接後MD5
     */
    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder sb = new StringBuilder();
        sb.append(target.getClass().getName());
        sb.append(".").append(method.getName());

        Annotation[][] paraAnnosArry = method.getParameterAnnotations();

        int i = 0;// 參數索引
        StringBuilder paramsSb = new StringBuilder();
        for (Annotation[] paraAnnos : paraAnnosArry) {
            KeyParam key = null;
            for (Annotation anno : paraAnnos) {
                if (KeyParam.class.isInstance(anno)) {// 若是存在指定是否生成鍵值註解,進行解析
                    key = (KeyParam) anno;
                    break;
                }
            }
            // 若是不指定,默認生成包含到鍵值中
            if ((key == null || key.include()) && params[i] != null) {
                paramsSb.append(String.valueOf(params[i]));
            }
            i++;

        }

        if (paramsSb.length() > 0) {
            sb.append("_").append(MD5.encode(paramsSb.toString()));
        }
        return sb.toString();
    }

}

六、改造CacheManager,支持不一樣的cache實現(替換原RedisCache實現)

經過簡單的繼承,重寫createAndAddCache方法,返回自定義的RedisCache實現。

import org.springframework.cache.Cache;
import org.springframework.data.redis.core.RedisOperations;

public class RedisCacheManager extends org.springframework.data.redis.cache.RedisCacheManager {

    /**
     * 構造函數
     * 
     * @param redisOperations
     */
    public RedisCacheManager(@SuppressWarnings("rawtypes") RedisOperations redisOperations) {
        super(redisOperations);
    }

    @Override
    protected Cache createAndAddCache(String cacheName) {
        addCache(createDiffCache(cacheName));
        return super.getCache(cacheName);
    }

    @SuppressWarnings("unchecked")
    private RedisCache createDiffCache(String cacheName) {
        long expiration = computeExpiration(cacheName);

        return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null), getRedisOperations(),
                expiration);
    }
}

七、自定義RedisCache,支持按照不一樣的待緩存的數據(key/value)配置緩存時間

因爲原RedisCache實現的設置緩存,其中緩存時間是從cacheMetadata成員變量中得到,可是其屬性卻爲私有和final定義,所以不能經過簡單的繼承進行重寫。

private final RedisCacheMetadata cacheMetadata;
@Override
	public void put(final Object key, final Object value) {

		put(new RedisCacheElement(new RedisCacheKey(key).usePrefix(cacheMetadata.getKeyPrefix()).withKeySerializer(
				redisOperations.getKeySerializer()), value).expireAfter(cacheMetadata.getDefaultExpiration()));
	}

 所以經過代理方式,在基於原Cache接口上主要是重寫put和putIfAbsent方法

實現上以下代碼在RedisCacheElement上調用expireAfter方法。

expireAfter(ExpireCacheStratergies.getExpireSeconds(redisCache.getName(), value))

public class RedisCache implements Cache {

    private final byte[] keyPrefix;
    private final org.springframework.data.redis.cache.RedisCache redisCache;

    /**
     * Constructs a new <code>RedisCache</code> instance.
     *
     * @param name cache name
     * @param prefix
     * @param redisOperations
     * @param expiration
     */
    public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
            long expiration) {

        hasText(name, "non-empty cache name is required");

        keyPrefix = prefix;
        redisCache = new org.springframework.data.redis.cache.RedisCache(name, prefix, redisOperations, expiration);
    }

    /**
     * Return the value to which this cache maps the specified key, generically specifying a type that return value will be cast
     * to.
     * 
     * @param key
     * @param type
     * @return
     * @see DATAREDIS-243
     */
    public <T> T get(Object key, Class<T> type) {
        return redisCache.get(key, type);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.cache.Cache#get(java.lang.Object)
     */
    @Override
    public ValueWrapper get(Object key) {
        return redisCache.get(key);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.cache.Cache#put(java.lang.Object, java.lang.Object)
     */
    @Override
    public void put(final Object key, final Object value) {
        @SuppressWarnings("rawtypes")
        RedisOperations redisOperations = (RedisOperations) redisCache.getNativeCache();

        redisCache.put(new RedisCacheElement(
                new RedisCacheKey(key).usePrefix(keyPrefix).withKeySerializer(redisOperations.getKeySerializer()), value)
                        .expireAfter(ExpireCacheStratergies.getExpireSeconds(redisCache.getName(), value)));
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.cache.Cache#putIfAbsent(java.lang.Object, java.lang.Object)
     */
    public ValueWrapper putIfAbsent(Object key, final Object value) {
        @SuppressWarnings("rawtypes")
        RedisOperations redisOperations = (RedisOperations) redisCache.getNativeCache();

        return redisCache.putIfAbsent(new RedisCacheElement(
                new RedisCacheKey(key).usePrefix(keyPrefix).withKeySerializer(redisOperations.getKeySerializer()), value)
                        .expireAfter(ExpireCacheStratergies.getExpireSeconds(redisCache.getName(), value)));
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.cache.Cache#evict(java.lang.Object)
     */
    public void evict(Object key) {
        redisCache.evict(key);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.cache.Cache#clear()
     */
    public void clear() {
        redisCache.clear();
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.cache.Cache#getName()
     */
    public String getName() {
        return redisCache.getName();
    }

    /**
     * {@inheritDoc} This implementation simply returns the RedisTemplate used for configuring the cache, giving access to the
     * underlying Redis store.
     */
    public Object getNativeCache() {
        return redisCache.getNativeCache();
    }
相關文章
相關標籤/搜索