注釈ベースspringキャッシュを拡張し、注釈サポートメソッドレベルのカスタム有効期間-redis編
10841 ワード
ここで使用するspringはredisのパッケージspring-data-redisに対して、主にRedisCacheManagerに対して二次パッケージを作ります.
主な依存パッケージ:
以下、拡張クラスcom.caiya.cache.redis.ExtendedRedisCacheManager:
spring-redis.xmlプロファイル:
注記使用(keyの生成ポリシーに注意):
主な依存パッケージ:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.0.RELEASE</version>
</dependency>
以下、拡張クラスcom.caiya.cache.redis.ExtendedRedisCacheManager:
package com.caiya.cache.redis;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCachePrefix;
import org.springframework.data.redis.core.RedisOperations;
import java.util.Collection;
import java.util.Collections;
/**
* CacheManager backed by a Simple Spring Redis (SSR) {@link org.springframework.data.redis.cache.RedisCache}. Spring Cache and
* CacheManager doesn't support configuring expiration time per method (there is no dedicated parameter in cache
* annotation to pass expiration time). This extension of {@link RedisCacheManager} overcomes this limitation and allow to
* pass expiration time as a part of cache name. To define custom expiration on method as a cache name use concatenation
* of specific cache name, separator and expiration e.g.
* <p/>
* <pre>
* public class UserDAO {
*
* // cache name: userCache, expiration: 300s
* @Cacheable("userCache#300")
* public User getUser(String name) {
*
* }
* }
* </pre>
*
* @author caiya
* @since 16/3/18
*/
public class ExtendedRedisCacheManager extends RedisCacheManager {
private static final Logger logger = Logger.getLogger(ExtendedRedisCacheManager.class);
private String defaultCacheName;
private char separator = '#';
public ExtendedRedisCacheManager(RedisOperations redisOperations) {
this(redisOperations, Collections.<String>emptyList());
}
public ExtendedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
super(redisOperations, cacheNames);
}
@Override
public Cache getCache(String name) {
// try to get cache by name
RedisCache cache = (RedisCache) super.getCache(name);
if (cache != null) {
return cache;
}
// there's no cache which has given name
// find separator in cache name
int index = name.lastIndexOf(getSeparator());
if (index < 0) {
return null;
}
// split name by the separator
String cacheName = name.substring(0, index);
if(StringUtils.isBlank(cacheName)){
cacheName = defaultCacheName;
}
cache = (RedisCache) super.getCache(cacheName);
if (cache == null) {
return null;
}
// get expiration from name
Integer expiration = getExpiration(name, index);
if (expiration == null || expiration < 0) {
logger.warn("Default expiration time will be used for cache '{}' because cannot parse '{}', cacheName : " + cacheName + ", name : " + name);
return cache;
}
return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null), getRedisOperations(), expiration);
}
public char getSeparator() {
return separator;
}
/**
* Char that separates cache name and expiration time, default: #.
*
* @param separator
*/
public void setSeparator(char separator) {
this.separator = separator;
}
private Integer getExpiration(final String name, final int separatorIndex) {
Integer expiration = null;
String expirationAsString = name.substring(separatorIndex + 1);
try {
expiration = Integer.parseInt(expirationAsString);
} catch (NumberFormatException ex) {
logger.error(String.format("Cannnot separate expiration time from cache: '%s'", name), ex);
}
return expiration;
}
@Override
public void setUsePrefix(boolean usePrefix) {
super.setUsePrefix(usePrefix);
}
@Override
public void setCachePrefix(RedisCachePrefix cachePrefix) {
super.setCachePrefix(cachePrefix);
}
public void setDefaultCacheName(String defaultCacheName) {
this.defaultCacheName = defaultCacheName;
}
}
spring-redis.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:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">
<!-- Jedis PoolConfig -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}"/>
<property name="maxIdle" value="${redis.pool.maxIdle}"/>
<property name="minIdle" value="${redis.pool.minIdle}"/>
<property name="maxWaitMillis" value="${redis.pool.maxWait}"/>
<!-- connection validateObject -->
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
<!-- returnObject connection validateObject -->
<property name="testOnReturn" value="${redis.pool.testOnReturn}"/>
<!-- validateObject -->
<property name="testWhileIdle" value="${redis.pool.testWhileIdle}"/>
</bean>
<!-- Redis SentinelConfig-->
<bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="mymaster"/>
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="${redis.sentinel1.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.sentinel1.port}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="${redis.sentinel2.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.sentinel2.port}"></constructor-arg>
</bean>
</set>
</property>
</bean>
<!-- Jedis ConnectionFactory -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg ref="redisSentinelConfiguration"></constructor-arg>
</bean>
<!-- Redis Template -->
<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>
</bean>
<!-- -->
<cache:annotation-driven/>
<!-- CacheManager, -->
<bean name="cacheManager" class="com.caiya.cache.redis.ExtendedRedisCacheManager">
<constructor-arg name="redisOperations" ref="redisTemplate"/>
<constructor-arg name="cacheNames">
<set>
<value>caiya_a</value>
<value>caiya_test</value>
</set>
</constructor-arg>
<!-- -->
<property name="defaultCacheName" value="caiya_a"/>
<!-- -->
<property name="loadRemoteCachesOnStartup" value="true"/>
<!-- -->
<property name="usePrefix" value="true"/>
<!-- , usePrefix true -->
<property name="cachePrefix">
<bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
<constructor-arg name="delimiter" value=":"/>
</bean>
</property>
<!-- -->
<property name="separator" value="#"/>
<!-- 1h -->
<property name="defaultExpiration" value="3600"/>
<!-- -->
<property name="transactionAware" value="false"/>
</bean>
</beans>
注記使用(keyの生成ポリシーに注意):
/**
* @description redis key :"caiya_a:querySoftwareList_null_null_null_null_1_10", cacheName .
*
* @param cid ID
* @param origin
* @param sortField
* @param sortType
* @param pageNo
* @param pageSize
* @return
*/
@Cacheable(value = "caiya_a#600", key = "'querySoftwareList_' + #cid + '_' + #origin + '_' + #sortField + '_' + #sortType + '_' + #pageNo + '_' + #pageSize")
public SoftwareWrapper querySoftwareList(Long cid, String origin, String sortField, String sortType, Integer pageNo, Integer pageSize) {
......
return ...;
}