深入理解Spring Redis的使用 (八)、Spring Redis实现 注解 自动缓存

项目中有些业务方法希望在有缓存的时候直接从缓存获取,不再执行方法,来提高吞吐率。而且这种情况有很多。如果为每一个方法都写一段if else的代码,导致耦合非常大,不方便后期的修改。

思来想去,决定使用自动注解+Spring AOP来实现。

直接贴代码。

 

自定义注解类:

package com.ns.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
 * 
 * ---------
 * @author Han
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCached {
    /**
     * redis key
     * @return
     */
    String value();
    /**
     * 过期时间,默认为1分钟,如果要设置永不过期,请设置小于等于0的值
     * @return
     */
    long timeout() default 1;
    /**
     * 时间单位,默认为分钟
     * @return
     */
    TimeUnit timeunit() default TimeUnit.MINUTES;
    /**
     * 额外定义一个空方法,调用该方法来对之前的缓存进行更新
     * @return
     */
    boolean forDelete() default false;
}

 

 

这个注解方便我们标志那个方法需要作为AOP的切入点。

 

AOP实现:

 

package com.ns.redis.aop;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.metamodel.binding.Caching;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import com.ns.annotation.RedisCached;
import com.ns.redis.dao.base.BaseRedisDao;
/**
 * 对dao的getbean的缓存处理
 * order保证优先执行此切面
 * @author Han
 */
@Aspect
public class AutoRedisCached extends BaseRedisDao<Object, Object> implements Ordered{
    private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);
    /*
     * 约束任意包下的包含Dao的类的任意方法,并且被cached注解
     */
    @Pointcut("execution(* *..*(..)) && @annotation(com.ns.annotation.RedisCached)")
    private void cacheMethod(){}
    
    @Around("cacheMethod()")
    public Object doArround(ProceedingJoinPoint pjp) throws Throwable{
        Object[] args = pjp.getArgs();
        
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        final RedisCached cacheinfo = method.getAnnotation(RedisCached.class);
        
        //定义序列化器
        final RedisSerializer<String> keySerializer = getStringSerializer();
        final RedisSerializer valueSerializer = new Jackson2JsonRedisSerializer(method.getReturnType());
        
        //序列化参数,作为hashkey
        byte [] keyBytesTemp = keySerializer.serialize(cacheinfo.value());
        for(Object arg : args){
            keyBytesTemp = ArrayUtils.addAll(keyBytesTemp, getDefaultSerializer().serialize(arg));
        }
        //取md5后key
        final byte [] keyBytes = keySerializer.serialize(DigestUtils.md5Hex(keyBytesTemp));
        
        //是删除方法
        if(cacheinfo.forDelete()){
            execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.del(keyBytes);
                }
            });
            return null;
        }
        
        Object obj= null;
        log.info("方法"+method.getName()+"切面,尝试从缓存获取...");
        obj = execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                byte [] tmp = connection.get(keyBytes);
                return valueSerializer.deserialize(tmp);
            }
        });
        if(obj == null){
            log.info("方法"+method.getName()+"切面,缓存未找到...");
            final Object objReturn = pjp.proceed();
            if(objReturn != null){
                execute(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        if(cacheinfo.timeout() > 0){
                            connection.setEx(keyBytes, TimeUnit.SECONDS.convert(cacheinfo.timeout(), cacheinfo.timeunit()), valueSerializer.serialize(objReturn));
                        }else{
                            connection.set(keyBytes,valueSerializer.serialize(objReturn));
                        }
                        return true;
                    }
                });
            }
            obj = objReturn;
        }else{
            log.info("方法"+method.getName()+"切面,缓存命中...");
        }
        //从dao获取
        return obj;
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

 

注:Orderd接口是为了保证此代码优先于其他切面执行。

posted @ 2015-09-09 23:40  洛城秋色  阅读(1613)  评论(0编辑  收藏  举报