// maven 依赖
org.springframework.boot
spring-boot-starter-cache
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.session
spring-session-data-redis
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.0.0
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba
fastjson
1.2.54
// 因为 ide 不会自动加载 mapper.xml 文件
src/main/java
**/*.xml
自定义序列化器, 采用的是阿里 fastjson, redis 自带了八种序列化方式
package com.yangyun.springboot.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.sun.xml.internal.ws.encoding.soap.SerializationException;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.nio.charset.Charset;
/**
* @description 自定义序列化器, 采用阿里 fastjson 的方式实现
* @author yangyun
* @date 2019/4/1 0001
* @return
*/
public class FastJsonRedisSerializer implements RedisSerializer {
// 字符编码集
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class clazz;
public FastJsonRedisSerializer(Class clazz){
super();
this.clazz = clazz;
}
/**
* @description 序列化
* @author yangyun
* @date 2019/4/1 0001
* @param t
* @return byte[]
*/
@Override
public byte[] serialize(T t) throws SerializationException {
if (null == t){
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
/**
* @description 反序列化
* @author yangyun
* @date 2019/4/1 0001
* @param bytes
* @return T
*/
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes && bytes.length <= 0){
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
当没设置数据失效时间时, 默认永久有效
package com.yangyun.springboot.config;
import com.alibaba.fastjson.parser.ParserConfig;
import com.yangyun.springboot.util.FastJsonRedisSerializer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
/**
* @author yangyun
* @create 2019-04-01-9:36
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* @description RedisCacheManager 生成的 RedisCache 默认使用 JdkSerializationRedisSerializer 序列化(cache 存储的时候)
* @author yangyun
* @date 2019/4/1 0001
* @param factory
* @return org.springframework.cache.CacheManager
*/
@Bean
@Primary // 当有多个缓存管理器, 表示该缓存器为默认
public CacheManager cacheManager (RedisConnectionFactory factory){
// 初始化 RedisCacheWrite, 它是作为程序和 redis 交互的一个主要的类, 对数据的缓存和读取都需要借助它
// RedisConnectionFactory 用于应用程序和redis交互连接的管理
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
// 序列化方式二 自定义序列化方式
FastJsonRedisSerializer
controller
package com.yangyun.springboot.controller;
import com.yangyun.springboot.bean.SaleGoodsDetailBean;
import com.yangyun.springboot.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yangyun
* @create 2019-04-01-17:54
*/
@RestController
public class RedisController {
@Autowired
private RedisService redisService;
@GetMapping("/redis/getList/{id}")
@Cacheable(value = "list", keyGenerator = "myKeyGenerator") // 该注解可以写到service或者facade 层
public SaleGoodsDetailBean getList(@PathVariable("id") Integer id){
SaleGoodsDetailBean bean = redisService.selectByPrimaryKey(id);
return bean;
}
}
service, mapper 代码就不贴出来了,可以自行编写.
关于 springboot, 应用启动的时候, 缓存管理器选择, 在没有使用其他任何第三方插件时, springboot 默认是使用的 SimpleCacheConfiguration,可以参考 https://blog.csdn.net/yangyun901222/article/details/85088689, 当在pom中添加了第三方插件依赖时, springboot 会自动识别, 本文使用的是 redis
初始化 RedisCacheManager, 请注意这个参数 allowInFlightCacheCreation, 在应用启动时, 默认为 true
初始化一个用来保存缓存名字的容器, Redis 采用的是 LinkedList
this.loadCaches()
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
// 用来记录缓存对象的结合
private final ConcurrentMap cacheMap = new ConcurrentHashMap(16);
// 用来记录缓存名的集合
private volatile Set cacheNames = Collections.emptySet();
public void initializeCaches() {
// this.loadCaches() 执行子类实现方法
Collection extends Cache> caches = this.loadCaches();
ConcurrentMap var2 = this.cacheMap;
synchronized(this.cacheMap) {
this.cacheNames = Collections.emptySet();
this.cacheMap.clear();
Set cacheNames = new LinkedHashSet(caches.size());
Iterator var4 = caches.iterator();
while(var4.hasNext()) {
Cache cache = (Cache)var4.next();
String name = cache.getName();
this.cacheMap.put(name, this.decorateCache(cache));
cacheNames.add(name);
}
this.cacheNames = Collections.unmodifiableSet(cacheNames);
}
}
}
// 这里是子类方法,
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
protected Collection loadCaches() {
List caches = new LinkedList();
Iterator var2 = this.initialCacheConfiguration.entrySet().iterator();
while(var2.hasNext()) {
Entry entry = (Entry)var2.next();
caches.add(this.createRedisCache((String)entry.getKey(), (RedisCacheConfiguration)entry.getValue()));
}
return caches;
}
}
AbstractCacheManager, 及其子类, 我们这里是使用的 RedisCacheManager
当我们查询数据时, 不管 redis 中有没有缓存该数据, 他都会去缓存中取一次,在使用 key 的时候, 不管是去缓存中取数据还是保存数据到 redis 缓存中, 都会对 key 进行序列化
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
private final ConcurrentMap cacheMap = new ConcurrentHashMap(16);
private volatile Set cacheNames = Collections.emptySet();
@Nullable
public Cache getCache(String name) {
// name 为 @Cacheable 注解中 value 的值,
Cache cache = (Cache)this.cacheMap.get(name);
if (cache != null) {
return cache;
} else {
// 如果是第一次查询
ConcurrentMap var3 = this.cacheMap;
synchronized(this.cacheMap) {
// cache 为null
cache = (Cache)this.cacheMap.get(name);
if (cache == null) {
// 这里会执行子类方法, cache 为新建
cache = this.getMissingCache(name);
if (cache != null) {
cache = this.decorateCache(cache);
// 保存缓存对象
this.cacheMap.put(name, cache);
this.updateCacheNames(name);
}
}
return cache;
}
}
}
}
// 子类方法
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
// allowInFlightCacheCreation 这个参数在应用初始化 RedisCacheManager 的时候, 默认设置为 true
protected RedisCache getMissingCache(String name) {
return this.allowInFlightCacheCreation ? this.createRedisCache(name, this.defaultCacheConfig) : null;
}
// 创建 RedisCache 缓存对象
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
return new RedisCache(name, this.cacheWriter, cacheConfig != null ? cacheConfig : this.defaultCacheConfig);
}
}
CacheAspectSupport 主要查询 Redis 是否已经缓存.
public abstract class CacheAspectSupport extends AbstractCacheInvoker implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
// 1
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
if (this.initialized) {
Class> targetClass = this.getTargetClass(target);
CacheOperationSource cacheOperationSource = this.getCacheOperationSource();
if (cacheOperationSource != null) {
Collection operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return this.execute(invoker, method, new CacheAspectSupport.CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
return invoker.invoke();
}
// 2
@Nullable
private Object execute(CacheOperationInvoker invoker, Method method, CacheAspectSupport.CacheOperationContexts contexts) {
// isSynchronized() 这里为false
if (contexts.isSynchronized()) {
CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
if (this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = (Cache)context.getCaches().iterator().next();
try {
return this.wrapCacheValue(method, cache.get(key, () -> {
return this.unwrapReturnValue(this.invokeOperation(invoker));
}));
} catch (ValueRetrievalException var10) {
throw (ThrowableWrapper)var10.getCause();
}
} else {
return this.invokeOperation(invoker);
}
} else {
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
// 查询缓存数据 2.1, 第一次 cacheHit 为null, 已经缓存的数据, 会有返回
ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
List cachePutRequests = new LinkedList();
if (cacheHit == null) {
this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !this.hasCachePut(contexts)) {
cacheValue = cacheHit.get();
returnValue = this.wrapCacheValue(method, cacheValue);
} else {
// 第一次查询, 缓存中没有数据, 这里会执行数据库访问操作, 结果为数据库返回数据
returnValue = this.invokeOperation(invoker);
cacheValue = this.unwrapReturnValue(returnValue);
}
this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
Iterator var8 = cachePutRequests.iterator();
while(var8.hasNext()) {
CacheAspectSupport.CachePutRequest cachePutRequest = (CacheAspectSupport.CachePutRequest)var8.next();
// 3 对于第一次查询的数据, 这里会将查询数据库数据保存到 redis 缓存中
cachePutRequest.apply(cacheValue);
}
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
// 最后数据返回
return returnValue;
}
}
}
接上面代码, 这里主要做 key 的解析, 使用的是我们自己定义的 keygenerator
// 2.1
@Nullable
private ValueWrapper findCachedItem(Collection contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
Iterator var3 = contexts.iterator();
while(var3.hasNext()) {
CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)var3.next();
if (this.isConditionPassing(context, result)) {
// 根据 @Cacheable 修饰的方法获取 key; 2.1.1
Object key = this.generateKey(context, result);
// 根据 key 获取缓存数据, 第一次时, 是没有数据的 2.1.2
ValueWrapper cached = this.findInCaches(context, key);
if (cached != null) {
return cached;
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
return null;
}
// 2.1.1 key 的生成策略, 获取key
private Object generateKey(CacheAspectSupport.CacheOperationContext context, @Nullable Object result) {
// 2.1.1.1
Object key = context.generateKey(result);
if (key == null) {
throw new IllegalArgumentException("Null key returned for cache operation (maybe you are using named params on classes without debug info?) " + context.metadata.operation);
} else {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation);
}
return key;
}
}
// 2.1.1.1
protected Object generateKey(@Nullable Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = this.createEvaluationContext(result);
return CacheAspectSupport.this.evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
} else {
// target: 目标controller method: 目标方法 args: 目标方法参数
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
}
2.1.2 接上面 ValueWrapper cached = this.findInCaches(context, key); 代码, 每次查询都会对 redis 访问, 根据 key 去 redis 获取数据
@Nullable
private ValueWrapper findInCaches(CacheAspectSupport.CacheOperationContext context, Object key) {
Iterator var3 = context.getCaches().iterator();
Cache cache;
ValueWrapper wrapper;
do {
if (!var3.hasNext()) {
return null;
}
cache = (Cache)var3.next();
wrapper = this.doGet(cache, key);
} while(wrapper == null);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
}
return wrapper;
}
接上面序号 3 的代码, 将数据库数据缓存到 redis 中
public class RedisCache extends AbstractValueAdaptingCache {
// 该方法走完, 数据已经保存到 redis 了
public void put(Object key, @Nullable Object value) {
Object cacheValue = this.preProcessCacheValue(value);
if (!this.isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format("Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.", this.name));
} else {
// cahceWriter 这个类, 我们前面说明过, 主要用于对 redis 的交互, 对数据的读取
// this.createAndConvertCacheKey(key) 对 key 序列化
// this.serializeCacheValue(cacheValue) 对值序列化
// this.cacheConfig.getTtl() 获取我们自已定义的缓存有效时间
this.cacheWriter.put(this.name, this.createAndConvertCacheKey(key), this.serializeCacheValue(cacheValue), this.cacheConfig.getTtl());
}
}
}
class DefaultRedisCacheWriter implements RedisCacheWriter {
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
this.execute(name, (connection) -> {
if (shouldExpireWithin(ttl)) {
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
} else {
connection.set(key, value);
}
return "OK";
});
}
// 这里是实际对 redis 的操作, 并且每次操作完, 连接都会关闭连接
private T execute(String name, Function callback) {
RedisConnection connection = this.connectionFactory.getConnection();
Object var4;
try {
this.checkAndPotentiallyWaitUntilUnlocked(name, connection);
var4 = callback.apply(connection);
} finally {
//
connection.close();
}
return var4;
}
}
到这里, 关于 springboot 和 redis 查询缓存的保存和读取就说完了,有问题欢迎大家一起进步!