springboot整合redis缓存实现查询缓存,查询缓存源码解读

5 篇文章 0 订阅
4 篇文章 0 订阅
  1. spring 定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口来统一不同的缓存技术;
  2. CacheManager: 缓存管理器, 管理各种缓存(Cache)组件;如: RedisCache, EhCacheCache...等.
  3. 本文主要讲解 redis 缓存,以及自定义实现序列化
        // maven 依赖
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.0.0</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.54</version>
		</dependency>

        // 因为 ide 不会自动加载 mapper.xml 文件
        <resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
		</resources>

自定义序列化器, 采用的是阿里 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<T> implements RedisSerializer<T> {

    // 字符编码集
    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJsonRedisSerializer(Class<T> 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<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);//JSONObject
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer);


        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();

        RedisCacheConfiguration defaultCacheConfig = configuration.serializeValuesWith(pair);

        // 设置缓存有效时间, 如果没有设置, redis 默认永久有效
        defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofDays(30));

        // 初始化管理器
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);

        // 设置白名单
        /*
        使用fastjson的时候:序列化时将class信息写入,反解析的时候,
        fastjson默认情况下会开启autoType的检查,相当于一个白名单检查,
        如果序列化信息中的类路径不在autoType中,
        反解析就会报com.alibaba.fastjson.JSONException: autoType is not support的异常
        可参考 https://blog.csdn.net/u012240455/article/details/80538540
         */
        ParserConfig.getGlobalInstance().addAccept("com.yangyun.springboot.bean");

        return redisCacheManager;
    }

    /**
     * @description 自定义redis 模板, 对 redis的操作
     * @author yangyun
     * @date 2019/4/1 0001
     * @param redisConnectionFactory
     * @return org.springframework.data.redis.core.RedisTemplate<java.lang.Object,java.lang.Object>
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

        // 配置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用自定义 fastjson 序列化, 作为 value 值序列化 FastJsonRedisSerializer
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);

        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);

        // 使用 StringRedisSerializer 作为 key 的序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        return redisTemplate;
    }

    /**
     * 自定义 key 生成器
     *
     * /
    @Bean("myKeyGenerator")
    public KeyGenerator getKeyGenerator (){
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                // 方法名 + 参数数组 getList[id], 已RedisController.getList(id), 
                //最后redis 中 key: value::methodName[param] == list::getList[1] 
                // list代表了缓存的名字, getList[1] 表示key, 1 表示传入的 id 的值
                String name = method.getName() + Arrays.asList(params).toString();
                return name;
            }
        };
    }
}

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

关于 redis 缓存, get 和 put 运行流程

一: 应用启动时

初始化 RedisCacheManager, 请注意这个参数  allowInFlightCacheCreation, 在应用启动时, 默认为 true

初始化一个用来保存缓存名字的容器, Redis 采用的是 LinkedList

this.loadCaches()

public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
    // 用来记录缓存对象的结合
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
    // 用来记录缓存名的集合
    private volatile Set<String> 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<String> 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<RedisCache> loadCaches() {
        List<RedisCache> caches = new LinkedList();
        Iterator var2 = this.initialCacheConfiguration.entrySet().iterator();

        while(var2.hasNext()) {
            Entry<String, RedisCacheConfiguration> 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<String, Cache> cacheMap = new ConcurrentHashMap(16);
    private volatile Set<String> 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<CacheOperation> 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<CacheAspectSupport.CachePutRequest> 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<CacheAspectSupport.CacheOperationContext> 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> T execute(String name, Function<RedisConnection, T> callback) {
        RedisConnection connection = this.connectionFactory.getConnection();

        Object var4;
        try {
            this.checkAndPotentiallyWaitUntilUnlocked(name, connection);
            var4 = callback.apply(connection);
        } finally {
            //
            connection.close();
        }

        return var4;
    }
}

到这里, 关于 springboot 和 redis 查询缓存的保存和读取就说完了,有问题欢迎大家一起进步!

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值