项目中基本上都需要使用到Cache的功能, 但是Spring提供的Cacheable并不能很好的满足我们的需求, 所以这里自己借助Spring思想完成自己的业务逻辑.
- 定义Cacheable注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
RedisKey value();
String key();
}
- 定义Rediskey.java
public enum RedisKeyEnum {
TEST_CACHE("test:", 24, TimeUnit.HOURS, "Test");
/**
* 缓存Key的前缀
*/
private String keyPrefix;
/**
* 过期时间
*/
private long timeout;
/**
* 过期时间单位
*/
private TimeUnit timeUnit;
/**
* 描述
*/
private String desc;
private static final String REDIS_KEY_DEFUALT_SEPARATOR = ":";
RedisKey(String keyPrefix, long timeout, TimeUnit timeUnit, String desc){
this.keyPrefix = keyPrefix;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.desc = desc;
}
public long getTimeout() {
return timeout;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public String getDesc() {
return desc;
}
/**
* 获取完整的缓存Key
* @param keys
* @return
*/
public String getKey(String... keys) {
if(keys == null || keys.length <= 0){
return this.keyPrefix;
}
String redisKey = keyPrefix;
for (int i = 0, length = keys.length; i < length; i++) {
String key = keys[i];
redisKey += key;
if (i < length - 1) {
redisKey += REDIS_KEY_DEFUALT_SEPARATOR;
}
}
return redisKey;
}
}
- Cache.java
public interface Cache<K, V> {
/**
* 返回缓存名称
* @return
*/
String getName();
/**
* 添加一个缓存实例
*
* @param key
* @param value
*/
V put(K key, V value);
/**
* 添加一个可过期的缓存实例
* @param key
* @param value
* @param expire
* @param timeUnit
* @return
*/
V put(K key, V value, long expire, TimeUnit timeUnit);
/**
* 返回缓存数据
*
* @param key
* @return
*/
V get(K key);
/**
* 删除一个缓存实例, 并返回缓存数据
*
* @param key
* @return
*/
void remove(K key);
/**
* 获取所有的缓存key
* @return
*/
Set<K> keys();
/**
* 获取所有的缓存key
* @return
*/
Set<K> keys(K pattern);
/**
* 获取所有的缓存数据
* @return
*/
Collection<V> values();
/**
* 清空所有缓存
*/
void clear();
}
- RedisCache.java
public class RedisCache<K, V> implements Cache<K, V> {
public static final String DEFAULT_CACHE_NAME = RedisCache.class.getName() + "_CACHE_NAME";
private RedisTemplate<K, V> redisTemplate;
private ValueOperations<K, V> valueOperations;
public RedisCache(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
this.valueOperations = redisTemplate.opsForValue();
DataType dataType = redisTemplate.type("a");
}
@Override
public String getName() {
return DEFAULT_CACHE_NAME;
}
@Override
public V put(K key, V value) {
valueOperations.set(key, value);
return value;
}
@Override
public V put(K key, V value, long expire, TimeUnit timeUnit) {
valueOperations.set(key, value, expire, timeUnit);
return value;
}
@Override
public V get(K key) {
return valueOperations.get(key);
}
@Override
public void remove(K key) {
// V value = valueOperations.get(key);
redisTemplate.delete(key);
}
@Override
public Set<K> keys() {
return null;
}
@Override
public Set<K> keys(K pattern) {
return redisTemplate.keys(pattern);
}
@Override
public Collection<V> values() {
return null;
}
@Override
public void clear() {
}
}
- CacheManager.java
public interface CacheManager {
/**
* 获取缓存
* @return
*/
Cache getCache(String name);
/**
* 获取所有的缓存名称
*/
Collection<String> getCacheNames();
}
- AbstractCacheManager.java
public abstract class AbstractCacheManager implements CacheManager, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(AbstractCacheManager.class);
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private volatile Set<String> cacheNames = Collections.emptySet();
private static final String DEFAULT_CACHE_NAME_SUFFIX = "_CACHE_NAME";
@Override
public void afterPropertiesSet() throws Exception {
initlalizingCache();
}
private void initlalizingCache(){
Collection<? extends Cache> caches = loadCaches();
synchronized (this.cacheMap) {
this.cacheNames = Collections.emptySet();
this.cacheMap.clear();
Set<String> cacheNames = new LinkedHashSet<String>(caches.size());
for (Cache cache : caches) {
String name = cache.getName();
if(StringUtils.isEmpty(name)){
name = cache.getClass().getName() + DEFAULT_CACHE_NAME_SUFFIX;
}
this.cacheMap.put(name, cache);
cacheNames.add(name);
}
this.cacheNames = Collections.unmodifiableSet(cacheNames);
}
}
@Override
public Cache getCache(String name) {
Cache cache = cacheMap.get(name);
if(cache != null){
return cache;
}
return null;
}
protected abstract Collection<? extends Cache> loadCaches();
@Override
public Collection<String> getCacheNames() {
return this.cacheNames;
}
@Override
public void destroy() throws Exception {
cacheMap.clear();
}
}
- RedisCacheManager.java
public class RedisCacheManager extends AbstractCacheManager {
private RedisTemplate redisTemplate;
public RedisCacheManager(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
protected Collection<? extends Cache> loadCaches() {
Collection<Cache<String, Object>> caches = new ArrayList<>();
RedisCache<String, Object> redisCache = new RedisCache<>(redisTemplate);
caches.add(redisCache);
return caches;
}
}
- 实现CacheInterceptor.java
/**
* 缓存数据过滤器, 缓存到redis数据中的数据是ServiceResult.getDateMap()数据
* 使用: 在service方法上添加com.chinaredstar.urms.annotations.Cacheable注解, 并指定RedisKeyEunm和cache key, cache key支持Spel表达式
* 以下情况不缓存数据:
* 1: 返回状态为fasle时, 不缓存数据
* 2: 返回dataMap为空时, 不缓存数据
* 3: 返回数据结构不是ServiceReslut实例时, 不缓存数据
*
* 当缓存问题时, 不影响正常业务, 但所有的请求都会打到DB上, 对DB有很大的冲击
*/
public class CacheInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(CacheInterceptor.class);
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private CacheManager cacheManager;
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Method method = methodInvocation.getMethod();
Object[] args = methodInvocation.getArguments();
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (cacheable == null) {
return methodInvocation.proceed();
}
String key = parseCacheKey(method, args, cacheable.key());
logger.info(">>>>>>>> -- 获取缓存key : {}", key);
if(StringUtils.isEmpty(key)){
return methodInvocation.proceed();
}
RedisKey redisKey = cacheable.value();
Cache cache = cacheManager.getCache(RedisCache.DEFAULT_CACHE_NAME);
Object value = null;
try{
value = cache.get(redisKey.getKey(key));
} catch (Exception e){
logger.info(">>>>>>>> -- 从缓存中获取数据异常 : {}", ExceptionUtil.exceptionStackTrace(e));
}
if (value != null) {
logger.info(">>>>>>>> -- 从缓存中获取数据 : {}", JsonUtil.toJson(value));
return ServiceResult.newInstance(true, value);
}
value = methodInvocation.proceed();
logger.info(">>>>>>>> -- 从接口中获取数据 : {}", JsonUtil.toJson(value));
if ( value != null && value instanceof ServiceResult ) {
ServiceResult result = (ServiceResult) value;
if(!result.isSuccess() || result.getDataMap() == null){
return value;
}
try{
cache.put(redisKey.getKey(key), result.getDataMap(), redisKey.getTimeout(), redisKey.getTimeUnit());
} catch (Exception e){
logger.info(">>>>>>>> -- 将数据放入缓存异常 : {}", ExceptionUtil.exceptionStackTrace(e));
}
}
return value;
}
/**
* 使用SpeL解析缓存key
* @param method
* @param args
* @param expressionString
* @return
*/
private String parseCacheKey(Method method, Object[] args, String expressionString) {
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
if (parameterNames != null && parameterNames.length > 0
&& args != null && args.length > 0
&& args.length == parameterNames.length ) {
for (int i = 0, length = parameterNames.length; i < length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
}
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(expressionString);
return (String) expression.getValue(context);
}
}
- 配置Spring.xml
<bean id="redisCacheManager" class="com.package.cache.RedisCacheManager">
<constructor-arg ref="cacheRedisTemplate" />
</bean>
<bean id="cacheInterceptor" class="com.package.interceptor.CacheInterceptor" p:cacheManager-ref="redisCacheManager"/>
<!-- 方法拦截器 MethodInterceptor -->
<aop:config proxy-target-class="true">
<aop:pointcut id="cacheInterceptorPointcut" expression="execution(* com.package..*(..))
and @annotation(com.package.annotations.Cacheable)"/>
<aop:advisor advice-ref="cacheInterceptor" pointcut-ref="cacheInterceptorPointcut" order="2" />
</aop:config>
- 测试使用
@Cacheable(value = RedisKey.TEST_CACHE, key = "#code + ':' + #user.id")
public ServiceResult<String> test(String code, User user){
return new ServiceResult("success");
}
- 说明
Cacheable其中的参数key拼接的规则支持Spring SpeL表达式。其规则和Spring Cacheable使用方法一致。