多级缓存架构开发 七 ( 给多级缓存开发添加注解功能 )

实现目的:

           为了解决代码的侵入性问题,采用注解 + Spring Aop的方式来来实现,对项目进行缓存的可插拔性

实现步骤:

    1.导入jar包

        在父模块的pom文件下添加

    <dependencyManagement>
        <dependencies>
 
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>${aspectj.version}</version>
            </dependency>

            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>${spring.version}</version>
            </dependency>
 
        </dependencies>
    </dependencyManagement>

     在子模块的pom文件下添加

    <dependencies>

        <dependency>
            <groupId>org.github.roger</groupId>
            <artifactId>multi-layering-cache-core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>

    </dependencies>

2.开发缓存注解

          2.1) 定义一级缓存配置项注解

package com.github.roger.annotation;

import org.github.roger.enumeration.ExpireMode;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
    一级缓存配置项
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface FirstCache {

    /**
     * 缓存初始Size
     */
    int initialCapacity() default  10;

    /**
     * 缓存最大Size
     */
    int maximumSize() default  5000;

    /**
     * 缓存有效时间
     */
    int expireTime() default  9;

    /**
     * 缓存时间单位
     */
    TimeUnit timeUnit() default TimeUnit.MINUTES;

    /**
     * 缓存失效模式{@link ExpireMode}
     */
    ExpireMode expireMode() default ExpireMode.WRITE;
}

         2.2) 定义二级缓存配置项注解

package com.github.roger.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
    二级缓存配置项
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SecondaryCache {

    /**
     * 缓存有效时间
     */
    long expiration() default 0;

    /**
     * 缓存主动在失效前强制刷新缓存的时间
     * 建议是: preloadTime default expireTime * 0.2
     *
     * @return long
     */
    long preloadTime() default 0;

    /**
     * 时间单位 {@link TimeUnit}
     */
    TimeUnit timeUnit() default TimeUnit.HOURS;

    /**
     * 是否强制刷新(走数据库),默认是false
     */
    boolean forceRefresh() default false;


    /**
     * 是否允许存NULL值
     */
    boolean allowNullValue() default false;

    /**
     * 非空值和null值之间的时间倍率,默认是1。allowNullValuedefaulttrue才有效
     *
     * 如配置缓存的有效时间是200秒,倍率这设置成10,
     * 那么当缓存value为null时,缓存的有效时间将是20秒,非空时为200秒
     */
    int magnification() default 1;
}

         2.3) 定义可以对方法或某类的所有方法的返回结果进行缓存的注解

package com.github.roger.annotation;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

/**
 * 表示调用的方法(或类中的所有方法)的结果是可以被缓存的。
 * 当该方法被调用时先检查缓存是否命中,如果没有命中再调用被缓存的方法,并将其返回值放到缓存中。
 * 这里的value和key都支持SpEL 表达式
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Cacheable {

    /**
     * 别名是 {@link #cacheNames}.
     *
     * @return String[]
     */
    @AliasFor("cacheNames")
    String[] value() default {};

    /**
     * 缓存名称,支持SpEL表达式
     *
     * @return String[]
     */
    @AliasFor("value")
    String[] cacheNames() default {};

    /**
     * 缓存key,支持SpEL表达式
     *
     * @return String
     */
    String key() default "";


    /**
     * 是否忽略在操作缓存中遇到的异常,如反序列化异常,默认true。
     * <p>true: 有异常会输出warn级别的日志,并直接执行被缓存的方法(缓存将失效)</p>
     * <p>false:有异常会输出error级别的日志,并抛出异常</p>
     *
     * @return boolean
     */
    boolean ignoreException() default true;

    /**
     * 一级缓存配置
     *
     * @return FirstCache
     */
    FirstCache firstCache() default @FirstCache();

    /**
     * 二级缓存配置
     *
     * @return SecondaryCache
     */
    SecondaryCache secondaryCache() default @SecondaryCache();
}

         2.4) 定义添加缓存的注解

package com.github.roger.annotation;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.METHOD})
public @interface CachePut {
    /**
     * 别名是 {@link #cacheNames}.
     *
     * @return String[]
     */
    @AliasFor("cacheNames")
    String[] value() default {};

    /**
     * 缓存名称,支持SpEL表达式
     *
     * @return String[]
     */
    @AliasFor("value")
    String[] cacheNames() default {};

    /**
     * 缓存key,支持SpEL表达式
     *
     * @return String
     */
    String key() default "";


    /**
     * 是否忽略在操作缓存中遇到的异常,如反序列化异常,默认true。
     * <p>true: 有异常会输出warn级别的日志,并直接执行被缓存的方法(缓存将失效)</p>
     * <p>false:有异常会输出error级别的日志,并抛出异常</p>
     *
     * @return boolean
     */
    boolean ignoreException() default true;

    /**
     * 一级缓存配置
     *
     * @return FirstCache
     */
    FirstCache firstCache() default @FirstCache();

    /**
     * 二级缓存配置
     *
     * @return SecondaryCache
     */
    SecondaryCache secondaryCache() default @SecondaryCache();
}

         2.5)定义删除缓存的注解

package com.github.roger.annotation;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.METHOD})
public @interface CacheEvict {
    /**
     * 别名是 {@link #cacheNames}.
     *
     * @return String[]
     */
    @AliasFor("cacheNames")
    String[] value() default {};

    /**
     * 缓存名称,支持SpEL表达式
     *
     * @return String[]
     */
    @AliasFor("value")
    String[] cacheNames() default {};

    /**
     * 缓存key,支持SpEL表达式
     *
     * @return String
     */
    String key() default "";


    /**
     * 是否忽略在操作缓存中遇到的异常,如反序列化异常,默认true。
     * <p>true: 有异常会输出warn级别的日志,并直接执行被缓存的方法(缓存将失效)</p>
     * <p>false:有异常会输出error级别的日志,并抛出异常</p>
     *
     * @return boolean
     */
    boolean ignoreException() default true;

    /**
     * 是否删除缓存中所有数据
     * <p>默认情况下是只删除关联key的缓存数据
     * <p>注意:当该参数设置成 {@code true} 时 {@link #key} 参数将无效
     *
     * @return boolean
     */
    boolean allEntries() default false;
}

3.创建SpEL表达式计算器

      3.1) 定义一个类,描述表达式计算期间使用的根对象

package com.github.roger.expression;

import lombok.Getter;
import org.springframework.util.Assert;

import java.lang.reflect.Method;

/**
 * 描述表达式计算期间使用的根对象。
 */
@Getter
public class CacheExpressionRootObject {
    private final Method method;

    private final Object[] args;

    private final Object target;

    private final Class<?> targetClass;

    public CacheExpressionRootObject(Method method, Object[] args, Object target, Class<?> targetClass) {
        Assert.notNull(method, "Method is required");
        Assert.notNull(targetClass, "targetClass is required");
        this.method = method;
        this.target = target;
        this.targetClass = targetClass;
        this.args = args;
    }

    public String getMethodName() {
        return this.method.getName();
    }
}

      3.2) 获取到目标方法

    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        Method targetMethod = this.targetMethodCache.get(methodKey);
        if(targetMethod == null){
            targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            if (targetMethod == null) {
                targetMethod = method;
            }
            this.targetMethodCache.put(methodKey, targetMethod);
        }
        return targetMethod;
    }

      3.3) 定义一个缓存对象评估的上下文,该上下文使用方法的参数添加为SpEL变量

package com.github.roger.expression;

import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.ParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

public class CacheEvaluationContext extends MethodBasedEvaluationContext {

    private final Set<String> unavailableVariables = new HashSet<String>(1);

    public CacheEvaluationContext(CacheExpressionRootObject rootObject, Method targetMethod, Object[] args, ParameterNameDiscoverer parameterNameDiscoverer) {
        super(rootObject, targetMethod, args, parameterNameDiscoverer);
    }

    public void addUnavailableVariable(String name) {
        this.unavailableVariables.add(name);
    }

    @Override
    public Object lookupVariable(String name) {
        if(this.unavailableVariables.contains(name)){
            throw new VariableNotAvailableException(name);
        }
        return super.lookupVariable(name);
    }
}

      3.4) 通过上面三个小步骤组成一个使用类

package com.github.roger.expression;

import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator {

    /**
     * 指示没有结果变量
     */
    public static final Object NO_RESULT = new Object();

    /**
     * 指示结果变量根本不能使用
     */
    public static final Object RESULT_UNAVAILABLE = new Object();

    /**
     *  保存结果对象的变量的名称。
     */
    public static final String RESULT_VARIABLE = "result";

    private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);

    private final Map<ExpressionKey, Expression> cacheNameCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);

    private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);

    private final Map<ExpressionKey, Expression> unlessCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);

    private final Map<AnnotatedElementKey, Method> targetMethodCache =
            new ConcurrentHashMap<AnnotatedElementKey, Method>(64);

    public EvaluationContext createEvaluationContext(Method method, Object[] args, Object target, Class<?> targetClass) {

        return createEvaluationContext(method, args, target, targetClass, NO_RESULT);
    }

    public EvaluationContext createEvaluationContext(Method method, Object[] args,
                                                     Object target, Class<?> targetClass, Object result) {

        CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
                method, args, target, targetClass);
        Method targetMethod = getTargetMethod(targetClass, method);
        CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
                rootObject, targetMethod, args, getParameterNameDiscoverer());
        if (result == RESULT_UNAVAILABLE) {
            evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
        } else if (result != NO_RESULT) {
            evaluationContext.setVariable(RESULT_VARIABLE, result);
        }
        return evaluationContext;
    }


    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        Method targetMethod = this.targetMethodCache.get(methodKey);
        if(targetMethod == null){
            targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            if (targetMethod == null) {
                targetMethod = method;
            }
            this.targetMethodCache.put(methodKey, targetMethod);
        }
        return targetMethod;
    }


    public Object key(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {

        return getExpression(this.keyCache, methodKey, expression).getValue(evalContext);
    }

    public Object cacheName(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {

        return getExpression(this.cacheNameCache, methodKey, expression).getValue(evalContext);
    }

    public boolean condition(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return getExpression(this.conditionCache, methodKey, conditionExpression).getValue(evalContext, boolean.class);
    }

    public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return getExpression(this.unlessCache, methodKey, unlessExpression).getValue(evalContext, boolean.class);
    }

    /**
     * Clear all caches.
     */
    void clear() {
        this.keyCache.clear();
        this.conditionCache.clear();
        this.unlessCache.clear();
        this.targetMethodCache.clear();
    }
}

4.为每个注解开发aop切面

   3.1) 为每个注解定义注解类型的切点

   3.2)对每个缓存注解的切点定义环绕方法

           3.2.1) 定义SpEL表达式计算器,用于获取注解配置了key属性的值,用来构造缓存key的值

           3.2.2) 定义一个注解缺省key属性配置时,生成默认组成缓存key的值

           3.2.3) 注入核心模块multi-layering-cache-core中的多级缓存管理器,用来操作缓存

package com.github.roger.aspect;

import com.github.roger.annotation.CacheEvict;
import com.github.roger.annotation.CachePut;
import com.github.roger.annotation.Cacheable;
import com.github.roger.expression.CacheOperationExpressionEvaluator;
import com.github.roger.key.KeyGenerator;
import com.github.roger.key.impl.DefaultKeyGenerator;
import com.github.roger.support.CacheOperationInvoker;
import com.github.roger.utils.CacheAspectUtil;
import lombok.extern.slf4j.Slf4j;
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.github.roger.cache.ICache;
import org.github.roger.exception.SerializationException;
import org.github.roger.manager.ICacheManager;
import org.github.roger.settings.FirstCacheSetting;
import org.github.roger.settings.MultiLayeringCacheSetting;
import org.github.roger.settings.SecondaryCacheSetting;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.Collection;

@Aspect
@Slf4j
public class MultiLayeringCacheAspect {

    private static final String CACHE_KEY_ERROR_MESSAGE = "缓存Key %s 不能为NULL";
    private static final String CACHE_NAME_ERROR_MESSAGE = "缓存名称不能为NULL";

    @Autowired(required = false)//如果自定义了使用自定义的,否则使用默认的
    private KeyGenerator keyGenerator = new DefaultKeyGenerator();

    @Autowired
    private ICacheManager iCacheManager;

    @Pointcut("@annotation(com.github.roger.annotation.Cacheable)")
    public void cacheablePointCut(){
    }

    @Pointcut("@annotation(com.github.roger.annotation.CachePut")
    public void cachePutPointCut(){
    }

    @Pointcut("@annotation(com.github.roger.annotation.CacheEvict")
    public void cacheEvictPointCut(){
    }

    @Around("cacheablePointCut()")
    public Object cacheableAroundAdvice(ProceedingJoinPoint pJoinPoint) throws Throwable{
        //通过非缓存的方式获取数据的操作类接口
        CacheOperationInvoker aopInvoker = CacheAspectUtil.getCacheOpreationInvoker(pJoinPoint);

        //获取正在执行的目标方法
        Method method = CacheAspectUtil.getSpecificMethod(pJoinPoint);

        //获取方法上的Cacheable注解
        Cacheable cacheable = AnnotationUtils.findAnnotation(method,Cacheable.class);
        try {
            //执行查询缓存的方法
            return executeCachealbe(aopInvoker, method, cacheable, pJoinPoint);
        }catch (SerializationException sex){
            // 如果是序列化异常需要先删除原有缓存
            String[] cacheNames = cacheable.cacheNames();
            // 删除缓存
            delete(cacheNames, cacheable.key(), method, pJoinPoint);

            // 忽略操作缓存过程中遇到的异常
            if (cacheable.ignoreException()) {
                log.warn(sex.getMessage(), sex);
                return aopInvoker.invoke();
            }
            throw sex;
        }catch (Exception ex){
            // 忽略操作缓存过程中遇到的异常
            if (cacheable.ignoreException()) {
                log.warn(ex.getMessage(), ex);
                return aopInvoker.invoke();
            }
            throw ex;
        }
    }

    private Object executeCachealbe(CacheOperationInvoker aopInvoker, Method method, Cacheable cacheable, ProceedingJoinPoint pJoinPoint) {
        // 解析SpEL表达式获取cacheName和key
        String[] cacheNames = cacheable.cacheNames();
        Assert.notEmpty(cacheable.cacheNames(), CACHE_NAME_ERROR_MESSAGE);
        String cacheName = cacheNames[0];

        Object key = CacheAspectUtil.generateKey(keyGenerator,cacheable.key(), method, pJoinPoint);
        Assert.notNull(key, String.format(CACHE_KEY_ERROR_MESSAGE, cacheable.key()));

        // 构造多级缓存配置信息
        MultiLayeringCacheSetting layeringCacheSetting = CacheAspectUtil.generateMultiLayeringCacheSetting(cacheable.firstCache(),cacheable.secondaryCache());

        // 通过cacheName和缓存配置获取Cache
        ICache iCache = iCacheManager.getCache(cacheName, layeringCacheSetting);

        // 通Cache获取值
        return iCache.get(key, () -> aopInvoker.invoke());

    }

    @Around("cacheEvictPointCut()")
    public Object cacheEvicArountAdvice(ProceedingJoinPoint pJoinPoint) throws Throwable{
        //通过非缓存的方式获取数据的操作类接口
        CacheOperationInvoker aopInvoker = CacheAspectUtil.getCacheOpreationInvoker(pJoinPoint);

        //获取正在执行的目标方法
        Method method = CacheAspectUtil.getSpecificMethod(pJoinPoint);

        //获取方法上的Cacheable注解
        CacheEvict cacheEvict = AnnotationUtils.findAnnotation(method,CacheEvict.class);
        try{
            return executeCacheEvict(aopInvoker,method,cacheEvict,pJoinPoint);
        }catch (Exception ex){
            // 忽略操作缓存过程中遇到的异常
            if (cacheEvict.ignoreException()) {
                log.warn(ex.getMessage(), ex);
                return aopInvoker.invoke();
            }
            throw ex;
        }
    }

    private Object executeCacheEvict(CacheOperationInvoker aopInvoker, Method method, CacheEvict cacheEvict, ProceedingJoinPoint pJoinPoint) throws Throwable {
        // 解析SpEL表达式获取cacheName和key
        String[] cacheNames = cacheEvict.cacheNames();
        Assert.notEmpty(cacheEvict.cacheNames(), CACHE_NAME_ERROR_MESSAGE);
        // 判断是否删除所有缓存数据
        if(cacheEvict.allEntries()){
            // 删除所有缓存数据(清空)
            for (String cacheName : cacheNames) {
                Collection<ICache> iCaches = iCacheManager.getCache(cacheName);
                if (CollectionUtils.isEmpty(iCaches)) {
                    // 如果没有找到Cache就新建一个默认的
                    ICache iCache = iCacheManager.getCache(cacheName,
                            new MultiLayeringCacheSetting(new FirstCacheSetting(), new SecondaryCacheSetting()));
                    iCache.clear();
                } else {
                    for (ICache iCache : iCaches) {
                        iCache.clear();
                    }
                }
            }
        }else{
            delete(cacheNames,cacheEvict.key(),method,pJoinPoint);
        }
        return aopInvoker.invoke();
    }


    /**
     * 删除执行缓存名称上的指定key
     * */
    private void delete(String[] cacheNames, String keySpEL, Method method, ProceedingJoinPoint pJoinPoint) {
        Object key = CacheAspectUtil.generateKey(keyGenerator,keySpEL, method, pJoinPoint);
        Assert.notNull(key, String.format(CACHE_KEY_ERROR_MESSAGE, keySpEL));
        for (String cacheName : cacheNames) {
            Collection<ICache> iCaches = iCacheManager.getCache(cacheName);
            if (CollectionUtils.isEmpty(iCaches)) {
                // 如果没有找到Cache就新建一个默认的
                ICache iCache = iCacheManager.getCache(cacheName,
                        new MultiLayeringCacheSetting(new FirstCacheSetting(), new SecondaryCacheSetting()));
                iCache.evict(key);
            } else {
                for (ICache iCache : iCaches) {
                    iCache.evict(key);
                }
            }
        }
    }

    @Around("cachePutPointCut()")
    public Object cachePutAroundAdvice(ProceedingJoinPoint pJoinPoint) throws Throwable{

        //通过非缓存的方式获取数据的操作类接口
        CacheOperationInvoker aopInvoker = CacheAspectUtil.getCacheOpreationInvoker(pJoinPoint);

        //获取正在执行的目标方法
        Method method = CacheAspectUtil.getSpecificMethod(pJoinPoint);

        //获取方法上的CachePut注解
        CachePut cachePut = AnnotationUtils.findAnnotation(method,CachePut.class);

        try {
            // 执行查询缓存方法
            return executeCachePut(aopInvoker, method, cachePut, pJoinPoint);
        } catch (Exception e) {
            // 忽略操作缓存过程中遇到的异常
            if (cachePut.ignoreException()) {
                log.warn(e.getMessage(), e);
                return aopInvoker.invoke();
            }
            throw e;
        }

    }

    private Object executeCachePut(CacheOperationInvoker aopInvoker, Method method, CachePut cachePut, ProceedingJoinPoint pJoinPoint) throws Throwable{

        // 解析SpEL表达式获取cacheName和key
        String[] cacheNames = cachePut.cacheNames();
        Assert.notEmpty(cachePut.cacheNames(), CACHE_NAME_ERROR_MESSAGE);

        Object key = CacheAspectUtil.generateKey(keyGenerator,cachePut.key(), method, pJoinPoint);
        Assert.notNull(key, String.format(CACHE_KEY_ERROR_MESSAGE, cachePut.key()));

        // 构造多级缓存配置信息
        MultiLayeringCacheSetting layeringCacheSetting = CacheAspectUtil.generateMultiLayeringCacheSetting(cachePut.firstCache(),cachePut.secondaryCache());

        // 指定调用方法获取缓存值
        Object result = aopInvoker.invoke();
        for (String cacheName : cacheNames) {
            // 通过cacheName和缓存配置获取Cache
            ICache iCache = iCacheManager.getCache(cacheName, layeringCacheSetting);
            iCache.put(key, result);
        }
        return result;
    }
}

5.创建Junit测试类

          5.1) 导入Spring-test jar 包

          5.2) 启动切面,把切面交给Spring容器管理

@EnableAspectJAutoProxy

          5.3) 二级缓存redis客户端交给Spring容器管理

@Import({RedisConfig.class})
package com.github.roger.cahce.config;

import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.github.roger.serializer.FastJsonRedisSerializer;
import org.github.roger.serializer.KryoRedisSerializer;
import org.github.roger.serializer.StringRedisSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
@PropertySource({"classpath:application.properties"})
public class RedisConfig {

    @Value("${spring.redis.database:0}")
    private int database;

    @Value("${spring.redis.host:127.0.0.1}")
    private String host;

    @Value("${spring.redis.password:}")
    private String password;

    @Value("${spring.redis.port:6379}")
    private int port;

    @Value("${spring.redis.pool.max-idle:200}")
    private int maxIdle;

    @Value("${spring.redis.pool.min-idle:10}")
    private int minIdle;

    @Value("${spring.redis.pool.max-active:80}")
    private int maxActive;

    @Value("${spring.redis.pool.max-wait:-1}")
    private int maxWait;


//    @Bean
//    public JedisConnectionFactory redisConnectionFactory() {
//        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//        jedisPoolConfig.setMinIdle(minIdle);
//        jedisPoolConfig.setMaxIdle(maxIdle);
//        jedisPoolConfig.setMaxTotal(maxActive);
//        jedisPoolConfig.setMaxWaitMillis(maxWait);
//
//        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
//        jedisConnectionFactory.setDatabase(database);
//        jedisConnectionFactory.setHostName(host);
//        jedisConnectionFactory.setPassword(password);
//        jedisConnectionFactory.setPort(port);
//        jedisConnectionFactory.setUsePool(true);
//        return jedisConnectionFactory;
//    }

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        ClientResources clientResources = DefaultClientResources.create();
        LettuceClientConfigurationBuilder builder = LettuceClientConfiguration.builder();
        builder.clientResources(clientResources);
        LettuceClientConfiguration configuration = builder.build();

        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPort(port);
        config.setPassword(RedisPassword.of(password));
        config.setDatabase(database);
        return new LettuceConnectionFactory(config, configuration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<Object>(Object.class, "com.xxx.");
        KryoRedisSerializer<Object> kryoRedisSerializer = new KryoRedisSerializer<Object>(Object.class);

        // 设置值(value)的序列化采用FastJsonRedisSerializer。
        redisTemplate.setValueSerializer(kryoRedisSerializer);
        redisTemplate.setHashValueSerializer(kryoRedisSerializer);
        // 设置键(key)的序列化采用StringRedisSerializer。
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

 

          5.4) 缓存管理器交个Spring容器管理

package com.github.roger.cahce.config;

import com.github.roger.aspect.MultiLayeringCacheAspect;
import com.github.roger.service.impl.UserServiceImpl;
import org.github.roger.MultiLayeringCacheManager;
import org.github.roger.manager.ICacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
@Import({RedisConfig.class})
@EnableAspectJAutoProxy
public class ICacheManagerConfig {


    @Bean
    public ICacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
        MultiLayeringCacheManager layeringCacheManager = new MultiLayeringCacheManager(redisTemplate);

        return layeringCacheManager;
    }

    @Bean//把切面交给Spring 容器管理
    public MultiLayeringCacheAspect layeringCacheAspect(){
        return new MultiLayeringCacheAspect();
    }

    @Bean
    public UserServiceImpl userService(){
        return new UserServiceImpl();
    }
}
package com.github.roger.cahce.aspect;

import com.github.roger.cahce.config.ICacheManagerConfig;
import com.github.roger.domain.User;
import com.github.roger.service.impl.UserServiceImpl;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

// SpringJUnit4ClassRunner再Junit环境下提供Spring TestContext Framework的功能。
@RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration用来加载配置ApplicationContext,其中classes用来加载配置类
@ContextConfiguration(classes = {ICacheManagerConfig.class})
@Log4j
public class CacheAspectTest {

    @Autowired
    private UserServiceImpl userService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    @Test
    public void testGetUserById(){
        long userId = 111;
        // 二级缓存key : user:info:111 有效时间100秒 强制刷新时间 30秒
        // 根据UserId获取数据,首先从缓存中获取,如果获取不到,去数据库中获取
        // 然后把缓存写入一级,二级缓存
        User user = userService.getUserById(userId);
        sleep(10);
        // 已经写入二级redis缓存,因此可以从缓存中获取
        Object result = redisTemplate.opsForValue().get("user:info:111");
        Assert.assertNotNull(result);

        sleep(65);
        long ttl = redisTemplate.getExpire("user:info:111");
        log.debug("进入强制刷新缓存时间段 ttl = " + ttl);
        Assert.assertTrue(ttl > 0 && ttl < 30);
        userService.getUserById(userId);
        // 因为是开启线程去刷新二级缓存,因此这里在获取有效时间的时候,需要延后几秒,才能获取到新的有效时间
        sleep(2);
        long ttl2 = redisTemplate.getExpire("user:info:111");
        log.debug("强制刷新缓存后,有效时间发生变化 ttl2 = " + ttl2);
        Assert.assertTrue(ttl2 > 50);

    }

    private void sleep(int time) {
        try {
            Thread.sleep(time * 1000);
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值