实现目的:
为了解决代码的侵入性问题,采用注解 + 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);
}
}
}