springboot 整合Redis

4. RedisTemplate原理

4.1 CacheAutoConfiguration

首先,在application的refresh生成组件的阶段,会对在Application类上的如@Srpingboot和@MapperScan@EnableCaching依据顺序执行,而@EnableCaching的官方注解为

/* In both of the scenarios above, {@code @EnableCaching} and {@code
* <cache:annotation-driven/>} are responsible for registering the necessary Spring
* components that power annotation-driven cache management, such as the
* {@link org.springframework.cache.interceptor.CacheInterceptor CacheInterceptor} and the
* proxy- or AspectJ-based advice that weaves the interceptor into the call stack when
* {@link org.springframework.cache.annotation.Cacheable @Cacheable} methods are invoked.
*/
//即会注册相应的CacheInterceptor组件

该组件使得CacheAutoConfiguration生效并加入到ioc容器中,该类若是使用@EnableCache则默认情况下会实现

//有了 proxyBeanMethods 属性后,配置类不会被代理了。主要是为了提高性能,如果你的 @Bean 方法之间没有调用关系的话可以把 proxyBeanMethods 设置为 false。否则,方法内部引用的类生产的类和 Spring 容器中类是两个类。
@Configuration(proxyBeanMethods = false)
// 查看是否有CacheManager的Class信息使得下文可以初始化
@ConditionalOnClass(CacheManager.class)
//查看是否有对@Cacheabe等注解的方法的类进行AOP增强的工具类
@ConditionalOnBean(CacheAspectSupport.class)
//查看是否注册了CacheManager在容器中,若是则该自动配置类失效
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")

@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,
		HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
    // CacheManagerCustomizers,可用来加入容器以修改cacheManager初始化配置
	public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
		return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
	}

	@Bean
    // CacheManagerValidator,可用来加入容器以修改cacheManager校验配置,在内部类实现
	public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties,
			ObjectProvider<CacheManager> cacheManager) {
		return new CacheManagerValidator(cacheProperties, cacheManager);
	}

	@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
	@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
    //后置处理器
	static class CacheManagerEntityManagerFactoryDependsOnPostProcessor
			extends EntityManagerFactoryDependsOnPostProcessor {

		CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
			super("cacheManager");
		}

	}

	/**
	 * Bean used to validate that a CacheManager exists and provide a more meaningful
	 * exception.
	 */
	static class CacheManagerValidator implements InitializingBean {

		private final CacheProperties cacheProperties;

		private final ObjectProvider<CacheManager> cacheManager;

		CacheManagerValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
			this.cacheProperties = cacheProperties;
			this.cacheManager = cacheManager;
		}

		@Override
		public void afterPropertiesSet() {
			Assert.notNull(this.cacheManager.getIfAvailable(),
					() -> "No cache manager could be auto-configured, check your configuration (caching type is '"
							+ this.cacheProperties.getType() + "')");
		}

	}

	/**
	 * {@link ImportSelector} to add {@link CacheType} configuration classes.
	 */
    //CacheConfigurations中有CacheType该枚举类中的值为key的spring包中存在的XXXCacheConfiguration.class。
    // 将其取出,applicationContext实例化组件时按一定顺序实现(条件成立)
	static class CacheConfigurationImportSelector implements ImportSelector {

		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			CacheType[] types = CacheType.values();
			String[] imports = new String[types.length];
			for (int i = 0; i < types.length; i++) {
                //getConfigurationClass为static方法,可直接调用
				imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
			}
			return imports;
		}

	}
	// 加载RedisAutoConfiguration,见4.2
}

4.2 RedisAutoConfiguration

若是我们使用的是redisNoSql,则我们从spring-boot-starter-data-redis中导入了spring-data-redis包,使得RedisAutoConfiguration生效(redis有在springboot的AutoConfiguration JAr包中有自动配置类,Hazelcast等CacheConfigurations中的需要连接外部服务端的也有,但都需要导入相关依赖jar包中的资源才可以实例化)

Cache类型的AutoConfiguration依据设计有不同的功能,有的会实例化环境类进入ioc容器,而RedisAutoConfiguration其作用是配置Redis的客户端操作类RedisTemplate

@Configuration(proxyBeanMethods = false)
//只有实现了RedisOperations才能使得RedisAutoConfiguration实例化,而在spring-data-redis中才有,需要用starter导入,所以没加入默认不开启。
@ConditionalOnClass(RedisOperations.class)
//实例化Redis配置类的引用
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    ···
}

Lettuce 和 Jedis 都是Redis的client,所以他们都可以连接 Redis Server。
Jedis在实现上是直接连接的Redis Server,如果在多线程环境下是非线程安全的。
每个线程都去拿自己的 Jedis 实例,当连接数量增多时,资源消耗阶梯式增大,连接成本就较高了。

Lettuce的连接是基于Netty的,Netty 是一个多线程、事件驱动的 I/O 框架。连接实例可以在多个线程间共享,当多线程使用同一连接实例时,是线程安全的。
所以,一个多线程的应用可以使用同一个连接实例,而不用担心并发线程的数量。
当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

通过异步的方式可以让我们更好的利用系统资源,而不用浪费线程等待网络或磁盘I/O。
所以 Lettuce 可以帮助我们充分利用异步的优势。

使用连接池,为每个Jedis实例增加物理连接Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

RedisAutoConfiguration向容器中导入了两个类 RedisTemplateStringRedisTemplate,作为Redis客户端分别操作k-v都为对象和k-v都为字符串的值。

factory中会创建且保存LettuceConnection

Conenction的创建会绑定一个Provider

Provider会绑定一个Client,Client才是对RedisServer的连接者

RedisTemplate:主要是直接面对Redis数据库Server的CRUD操作,可以看成一个JAVA版本的客户端

  • 采用Lettuce或者Jedis提供的对Redis数据库的connection,使得在java层面对connection进行增删查改即可对Redis数据库生效,只需面向connection进行编程配置实现想要的功能,并将需要得参数传入connection即可。
@Configuration(proxyBeanMethods = false)
//判断是否有引入org.springframework.data.redis.core
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
// 创建Lettuce(默认)类型的redisConnectionFactory(其中有对于Redis的pool以及pool管理,以及对RedisClient的创建,起作用如同JDbcConnectionFactory中线程池的思想,不过这变得 ),见4.3
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

   @Bean
   @ConditionalOnMissingBean(name = "redisTemplate")
   public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
         throws UnknownHostException {
       //只实现对默认RedisTemplate的实例化,见4.4
      RedisTemplate<Object, Object> template = new RedisTemplate<>();
       //传入由LettuceConnectionConfiguration生成的redisConnectionFactory
       //调用其相应得方法会自动从redisConnectionFactory中获取连接,只需往其中传入参数即可
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   }

   @Bean
   @ConditionalOnMissingBean
    
   // 这种xxxRedisTemplate其本质上仍是一个redisTemplate,只是配置得不同,最常见得便是序列化与反序列化的采用的json转换器的不同配置。
   public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
         throws UnknownHostException {
      StringRedisTemplate template = new StringRedisTemplate();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   }

}

4.3 LettuceConnectionFactory

  • Conenctionfactory作为template的参数保存在Template中,但不能直接使用,需要借助RedisConnectionUtils在有action到达时进行afterPropertiesSet的配置生成Client和connection、ConnectionProvider,
  • 将Client绑定到ConnectionProvider上,在将ConnectionProvider绑定到connction上,使得Template及工具类可以面向Connection进行操作
  • Client通过DefaultConnectionFuture进行Redis连接
public class LettuceConnectionFactory
      implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {

   private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
         LettuceConverters.exceptionConverter());

   private final Log log = LogFactory.getLog(getClass());
   private final LettuceClientConfiguration clientConfiguration;
	
    // 如同一个客户端对Redis的操作
   private @Nullable AbstractRedisClient client;
    // 对于客户端以何种方式执行client的工具类
   private @Nullable LettuceConnectionProvider connectionProvider;
   private @Nullable LettuceConnectionProvider reactiveConnectionProvider;
   private boolean validateConnection = false;
   private boolean shareNativeConnection = true;
   private boolean eagerInitialization = false;
   private @Nullable SharedConnection<byte[]> connection;
   private @Nullable SharedConnection<ByteBuffer> reactiveConnection;
   private @Nullable LettucePool pool;
   /** Synchronization monitor for the shared Connection */
   private final Object connectionMonitor = new Object();
   private boolean convertPipelineAndTxResults = true;

   private RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration("localhost", 6379);
   private PipeliningFlushPolicy pipeliningFlushPolicy = PipeliningFlushPolicy.flushEachCommand();

   private @Nullable RedisConfiguration configuration;

   private @Nullable ClusterCommandExecutor clusterCommandExecutor;
    
    // 默认用MutableLettuceClientConfiguration进行工厂客户端配置
    public LettuceConnectionFactory() {
		this(new MutableLettuceClientConfiguration());
	}
    
    //standaloneConfig 为Redis的默认端口的配置类
    //clientConfig为Client配置类
    private LettuceConnectionFactory(LettuceClientConfiguration clientConfig) {

		Assert.notNull(clientConfig, "LettuceClientConfiguration must not be null!");
		// 获取配置
		this.clientConfiguration = clientConfig;
		this.configuration = this.standaloneConfig;
	}
    public void afterPropertiesSet() {
		
        // 新建客户端,为RedisClient的扩展类或RedisClusterClient,Client通过DefaultConnectionFuture进行Redis连接
		this.client = createClient();
		
        // 新建connectionProvider,默认为StandaloneConnectionProvider,主要功能为设置传入的Client以哪种形式对Redis进行连接
		this.connectionProvider,默认为StandaloneConnectionProvider = new ExceptionTranslatingConnectionProvider(createConnectionProvider(client, CODEC));
		this.reactiveConnectionProvider = new ExceptionTranslatingConnectionProvider(
				createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC));

		if (isClusterAware()) {

			this.clusterCommandExecutor = new ClusterCommandExecutor(
					new LettuceClusterTopologyProvider((RedisClusterClient) client),
					new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider),
					EXCEPTION_TRANSLATION);
		}
		
        // 若是获取本地连接或者需要此时便初始化连接则初始化
		if (getEagerInitialization() && getShareNativeConnection()) {
			initConnection();
		}
	}

4.4 RedisTemplate

  • RedisTemplate由两个较为重要的execute方法,其他的方法也是将参数处理后用这两个方法运行
@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {

    //查看template是否初始化了,一般RedisTemplate不会直接使用,其会在StringRedisTemplate等的构造函数中调用afterPropertiesSet()方法初始化后initialized=true后才可以调用其中的execute方法
   Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
   // 是否有需要执行的动作
   Assert.notNull(action, "Callback object must not be null");
	
   //获取连接工厂
   RedisConnectionFactory factory = getRequiredConnectionFactory();
   RedisConnection conn = null;
   try {

      if (enableTransactionSupport) {
          // 是否开启了事务支持,若是则获取事务管理器中持有的连接
         // only bind resources in case of potential transaction synchronization
         conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
      } else {
         // 从工厂中获取一个redis连接
         conn = RedisConnectionUtils.getConnection(factory);
      }
		
       // 查看事务管理器是否持有该工厂的连接
      boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
		
       // 若是有则用事务连接中的
      RedisConnection connToUse = preProcessConnection(conn, existingConnection);
		
       // 是否支持管道操作
      boolean pipelineStatus = connToUse.isPipelined();
      if (pipeline && !pipelineStatus) {
          // 若是则开启
         connToUse.openPipeline();
      }
		
       // 若工厂的连接池中有连接并获取到或者事务管理器中有保存连接,则获取,否则动态代理创建一个代理类
      RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
       // 通过获取的连接执行action到Redis中
       //其后会用connection中的provider,再调用Client执行action
      T result = action.doInRedis(connToExpose);

      // close pipeline
      if (pipeline && !pipelineStatus) {
         connToUse.closePipeline();
      }

      // TODO: any other connection processing?
      return postProcessResult(result, connToUse, existingConnection);
   } finally {
       // 断开连接返回连接池
      RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
   }
}
public void afterPropertiesSet() {

		super.afterPropertiesSet();

		boolean defaultUsed = false;
		//默认使用JDK的序列化器
		if (defaultSerializer == null) {

			defaultSerializer = new JdkSerializationRedisSerializer(
					classLoader != null ? classLoader : this.getClass().getClassLoader());
		}

    	// 当key,value的序列化器为空时,给他们设置默认的序列化器
		if (enableDefaultSerializer) {
			
			if (keySerializer == null) {
				keySerializer = defaultSerializer;
				defaultUsed = true;
			}
			if (valueSerializer == null) {
				valueSerializer = defaultSerializer;
				defaultUsed = true;
			}
			if (hashKeySerializer == null) {
				hashKeySerializer = defaultSerializer;
				defaultUsed = true;
			}
			if (hashValueSerializer == null) {
				hashValueSerializer = defaultSerializer;
				defaultUsed = true;
			}
		}

		if (enableDefaultSerializer && defaultUsed) {
			Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
		}

		if (scriptExecutor == null) {
			this.scriptExecutor = new DefaultScriptExecutor<>(this);
		}

		initialized = true;
	}

4. 自定义RedisCacheManager

RedisCacheManager在@Cacheable等注解时生效,也可独立使用,我们直接对Redis操作一般用template,但缓存的获取等我们一般让其自动化完成,所以RedisCacheManager的重要性才会上升,和Template分离

在导入redis依赖后RedisCacheConfiguration类就会自动生效,创建RedisCacheManager,并使用RedisCache进行缓存数据,要缓存的对象的类要实现Serializable接口,默认情况下是以jdk序列化数据存在redis中,如下:

k:"emp::1"
v:
\xAC\xED\x00\x05sr\x00$cn.edu.ustc.springboot.bean.Employeeuqf\x03p\x9A\xCF\xE0\x02\x00\x05L\x00\x03dIdt\x00\x13Ljava/lang/Integer;L\x00\x05emailt\x00\x12Ljava/lang/String;L\x00\x06genderq\x00~\x00\x01L\x00\x02idq\x00~\x00\x01L\x00\x08lastNameq\x00~\x00\x02xpsr\x00\x11java.lang.Integer\x12\xE2\xA0\xA4\xF7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xAC\x95\x1D\x0B\x94\xE0\x8B\x02\x00\x00xp\x00\x00\x00\x03t\x00\x07cch@aaasq\x00~\x00\x04\x00\x00\x00\x01q\x00~\x00\x08t\x00\x03cch

要想让对象以json形式存储在redis中,需要自定义RedisCacheManager,使用GenericJackson2JsonRedisSerializer类对value进行序列化。2.0版本后默认创建

@Configuration
public class MyRedisConfig {
    @Bean
    RedisCacheManager cacheManager(RedisConnectionFactory factory){
        //创建默认RedisCacheWriter
        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
        
        //创建默认RedisCacheConfiguration并使用GenericJackson2JsonRedisSerializer构造的		SerializationPair对value进行转换
        //创建GenericJackson2JsonRedisSerializer的json序列化器
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //使用json序列化器构造出对转换Object类型的SerializationPair序列化对
        RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
        //将可以把Object转换为json的SerializationPair传入RedisCacheConfiguration
        //使得RedisCacheConfiguration在转换value时使用定制序列化器
        RedisCacheConfiguration cacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(serializationPair);
        
        RedisCacheManager cacheManager = new RedisCacheManager(cacheWriter,cacheConfiguration);
        return cacheManager;
    }
}

序列化数据如下:

k:"emp::3"

v:
{
  "@class": "cn.edu.ustc.springboot.bean.Employee",
  "id": 3,
  "lastName": "aaa",
  "email": "aaaa",
  "gender": 1,
  "dId": 5
}

注意,这里必须用GenericJackson2JsonRedisSerializer进行value的序列化解析,如果使用Jackson2JsonRedisSerializer,序列化的json没有 "@class": "cn.edu.ustc.springboot.bean.Employee",在读取缓存时会报类型转换异常。

5. RedisCacheManager原理

我们用AOP动态增强我们的service类,使得对@Cacheable等的方法进行判断存储,调用时RedisCacheManager会绑定到CacheAspectSupportCacheAspectSupport中的方法会到对应RedisCacheManager的对应的cache中去查找

2.0版本以前,RedisCacheManager通过RedisTemplate前往redis进行CRUD操作,但在2.0版本后面,则出于解耦的考虑,将他们解耦开来。不然所有的配置都需要单独配置相应的Template来实现,使得每个template的复用情况下降

注意RedisCacheManager是Spring层面的管理类和RidisServer本身实现无关

5.1 RedisCacheConfiguration

@Configuration(proxyBeanMethods = false)
//仍需要RedisConnectionFactory,而他也在starter导入的jar包中,因此没添加依赖也默认不会加载
@ConditionalOnClass(RedisConnectionFactory.class)
// 为了防止别的同级别的RedisCacheConfiguration加载他们的RedisCacheManager,因此在RedisAutoConfiguration便加载,2.0后不需要RedisTemplate也可以实现注解的缓存,但要自己将工厂添加到容器中
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
    
    //向容器中导入RedisCacheManager
	@Bean
    //cacheManager不同于以前,自己导入RedisConnectionFactory,能够自己获取connection进行操作
	RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
			ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
			ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
			RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
		//使用determineConfiguration()的返回值生成RedisCacheManagerBuilder,用DefaultRedisCacheWriter完成I/O操作,见4.2
        //RedisCacheManager.builder将redisConnectionFactory放入DefaultRedisCacheWriter中,见4.3
        //调用了RedisCacheManagerBuilder的cacheDefaults()方法返回以determineConfiguration生成的redisCacheConfiguration
        //determineConfiguration为本类的方法,见下面
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
				determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
		List<String> cacheNames = cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
		}
		redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
        //使用RedisCacheManagerBuilder的build()方法创建RedisCacheManager并进行定制操作
		return cacheManagerCustomizers.customize(builder.build());
	}

    
    //determineConfiguration,生成RedisCacheManagerBuilder用到的参数/
    //注意此时的类是org.springframework.data.redis.cache.RedisCacheConfiguration,为导入的Redis的相关Jar包中的RedisCache配置类,用以配置cache初始化信息,因为与本类的方法同名,所以用全类名。
    //之所以该类要用这个名字,是因为其他的生成RedisCacheManager的命名规范如此,诈胡
	private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
			CacheProperties cacheProperties,
			ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
			ClassLoader classLoader) {
        //determineConfiguration()调用了createConfiguration(),也在该类中
		return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
	}

    
    //createConfiguration()定义了其序列化value的规则,这个方法的作用与RedisTemplate中的afterPropertiesSet方法一样
    //RedisCacheConfiguration见4.4
	private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
			CacheProperties cacheProperties, ClassLoader classLoader) {
		Redis redisProperties = cacheProperties.getRedis();
		org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
				.defaultCacheConfig();
        //使用jdk序列化器对value进行序列化
		config = config.serializeValuesWith(
				SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
        //设置properties文件中设置的各项属性
		if (redisProperties.getTimeToLive() != null) {
			config = config.entryTtl(redisProperties.getTimeToLive());
		}
        //获取key时,是否加上前缀,一般前缀为Redis的一个命名空间
		if (redisProperties.getKeyPrefix() != null) {
			config = config.prefixKeysWith(redisProperties.getKeyPrefix());
		}      
        //是否运行cache中的value为空
		if (!redisProperties.isCacheNullValues()) {
			config = config.disableCachingNullValues();
		}
        //使用key时,是否加上前缀,一般前缀为Redis的一个命名空间
		if (!redisProperties.isUseKeyPrefix()) {
			config = config.disableKeyPrefix();
		}
		return config;
	}

}

5.2 RedisCacheManager

RedisCacheManager的直接构造类,该类保存了配置类RedisCacheConfiguration,该配置在会传递给RedisCacheManager

public static class RedisCacheManagerBuilder {

		private final RedisCacheWriter cacheWriter;
    	//默认缓存配置使用RedisCacheConfiguration的默认配置
    	//该默认配置缓存时默认将k按字符串存储,v按jdk序列化数据存储(见下一代码块)
		private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
		private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
		private boolean enableTransactions;
		boolean allowInFlightCacheCreation = true;

		private RedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
			this.cacheWriter = cacheWriter;
		}
    
    //将connectionFactory放入DefaultRedisCacheWriter中,对redis的操作尤其接手,见4.3
    public static RedisCacheManagerBuilder builder(RedisConnectionFactory connectionFactory) {

		Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
	
		return RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);
	}


		
    	//传入RedisCacheManagerBuilder使用的缓存配置规则RedisCacheConfiguration类
		public RedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {

			Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");

			this.defaultCacheConfiguration = defaultCacheConfiguration;

			return this;
		}
    
    
    //使用默认defaultCacheConfiguration创建RedisCacheManager
    public RedisCacheManager build() {

		RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,allowInFlightCacheCreation);
        cm.setTransactionAware(enableTransactions);

			return cm;
		}

5.3 DefaultRedisCacheWriter

  • 其功能上有些像弱化版本的RedisTemplate
/*{@link RedisCacheWriter} implementation capable of reading/writing binary data from/to Redis in {@literal standalone}
 * and {@literal cluster} environments. Works upon a given {@link RedisConnectionFactory} to obtain the actual*/
class DefaultRedisCacheWriter implements RedisCacheWriter {

@Override
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!");

   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";
   });
}
    //获取连接并执行操作
    private <T> T execute(String name, Function<RedisConnection, T> callback) {

		RedisConnection connection = connectionFactory.getConnection();
		try {

			checkAndPotentiallyWaitUntilUnlocked(name, connection);
			return callback.apply(connection);
		} finally {
			connection.close();
		}
	}
}

5.4 RedisCacheConfiguration

RedisCacheConfiguration(导入的jar包中)保存了许多缓存规则,这些规则都保存在RedisCacheManagerBuilder的RedisCacheConfiguration defaultCacheConfiguration属性中,并且当RedisCacheManagerBuilder创建RedisCacheManager传递过去

public class RedisCacheConfiguration {

	private final Duration ttl;
	private final boolean cacheNullValues;
	private final CacheKeyPrefix keyPrefix;
	private final boolean usePrefix;

	private final SerializationPair<String> keySerializationPair;
	private final SerializationPair<Object> valueSerializationPair;

	private final ConversionService conversionService;
    
    //默认缓存配置
    public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {

            DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

            registerDefaultConverters(conversionService);
				
            return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
                                     SerializationPair.fromSerializer(RedisSerializer.string()),
                                               //key使用字符串
                                               SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
        //value按jdk序列化存储
    }

5.5 RedisCacheManager创建cache

RedisCacheManager在创建RedisCache时将RedisCacheConfiguration传递过去,并在创建RedisCache时通过createRedisCache()起作用

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

	private final RedisCacheWriter cacheWriter;
	private final RedisCacheConfiguration defaultCacheConfig;
	private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
	private final boolean allowInFlightCacheCreation;
    
    	protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
        // 每一个cache的创建都加入了RedisCacheManager创建时放入了RedisFactory的cacheWriter
        //如果调用该方法时RedisCacheConfiguration有值则使用定制的,否则则使用默认的RedisCacheConfiguration defaultCacheConfig,即RedisCacheManagerBuilder传递过来的配置,见4.6
		return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
	}

5.6 RedisCache

每一个RedisCache的操作

RedisCache,Redis缓存,具体负责将缓存数据序列化的地方,将RedisCacheConfiguration的序列化对SerializationPair提取出来并使用其定义的序列化方式分别对k和v进行序列化操作

public class RedisCache extends AbstractValueAdaptingCache {
    
    private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);

	private final String name;
	private final RedisCacheWriter cacheWriter;
	private final RedisCacheConfiguration cacheConfig;
	private final ConversionService conversionService;
    
    public void put(Object key, @Nullable Object value) {

		Object cacheValue = preProcessCacheValue(value);

		if (!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.",
					name));
		}

        //在put k-v时使用cacheConfig中的k-v序列化器分别对k-v进行序列化
        //均是用cacheWriter获取连接再用RedisClient和RedisProvide等进行操作,与RedisTemplate相似
		cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
	}
    
    //从cacheConfig(即RedisCacheConfiguration)中获取KeySerializationPair并写入key值
    protected byte[] serializeCacheKey(String cacheKey) {
		return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey));
	}
    
    
    //从cacheConfig(即RedisCacheConfiguration)中获取ValueSerializationPair并写入key值
    protected byte[] serializeCacheValue(Object value) {

        if (isAllowNullValues() && value instanceof NullValue) {
            return BINARY_NULL_VALUE;
        }

        return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
    }

分析到这也就不难理解,要使用json保存序列化数据时,需要自定义RedisCacheManager,在RedisCacheConfiguration中定义序列化转化规则,并向RedisCacheManager传入我们自己定制的RedisCacheConfiguration了,我定制的序列化规则会跟随RedisCacheConfiguration一直传递到RedisCache,并在序列化时发挥作用。

上一篇:超市收银系统


下一篇:Array.sort()排序