天天看點

redistemplate使用_spring-boot-starter-data-redis源碼解析與使用實戰

我們以spring-boot-starter-data-redis-2.1.7為例,starter本身沒有包含任何代碼,隻是引入了spring-data-redis的依賴,是以肯定是在spring-boot-autoconfigure中加了自動配置:

redistemplate使用_spring-boot-starter-data-redis源碼解析與使用實戰

我們就看下這幾個配置類:

其中RedisAutoConfiguration裡面就配置了我們常用的RedisTemplate,RedisRepositoriesAutoConfiguration這裡面是實作了spring-data規範的一些配置,RedisReactiveAutoConfiguration是當需要用Reactive方式程式設計的時候用的,本文忽略。

RedisAutoConfiguration
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
//内置了對lettuce和jedis的支援
//但是預設隻添加了lettuce的依賴,是以預設是使用的lettuce
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
return template;
  }
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
return template;
  }
}
           

這裡面内置了對lettuce和jedis的支援,因為預設是引入的lettuce的jar,是以預設會使用lettuce去通路redis,同時這裡面建立了一個RedisTemplate和一個StringRedisTemplate,一般我們經常會直接注入StringRedisTemplate來通路redis。

首先看下StringRedisTemplate:

public StringRedisTemplate() {
    setKeySerializer(RedisSerializer.string());
    setValueSerializer(RedisSerializer.string());
    setHashKeySerializer(RedisSerializer.string());
    setHashValueSerializer(RedisSerializer.string());
}
           

從名字也可以看出來,它在序列化key、value、hash key和hash value的時候都是轉化成String存入redis的,RedisSerializer#string:

static RedisSerializer<String> string() {
    return StringRedisSerializer.UTF_8;
}
public static final StringRedisSerializer UTF_8 = 
    new StringRedisSerializer(StandardCharsets.UTF_8);
           

看下具體的序列化和反序列化的方法:

@Override
public String deserialize(@Nullable byte[] bytes) {
  return (bytes == null ? null : new String(bytes, charset));
}
@Override
public byte[] serialize(@Nullable String string) {
  return (string == null ? null : string.getBytes(charset));
}
           

一眼就能明白,沒什麼好說的。但是很顯然,它隻能處理String類型,如果是對象得需要自己手動轉化成String才可以,一般我們會把對象轉化成json字元串存儲到redis裡面。

我們回頭再來看下RedisTemplate,RedisTemplate#afterPropertiesSet:

@Override
  public void afterPropertiesSet() {
    boolean defaultUsed = false;
    //判斷是否幽默ren的序列化器
    if (defaultSerializer == null) {
      //預設是采用jdk自帶的序列化,
      //也就是說必須得實作Serializable接口才行
      defaultSerializer = new JdkSerializationRedisSerializer(
          classLoader != null ? classLoader : this.getClass().getClassLoader());
    }
    //預設j就是啟用的
    if (enableDefaultSerializer) {
      //key、value、hash key、hash value都使用預設的那個序列化器 
      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;
      }
    }
    。。。
  }
           

從名字也可以看出來,RedisTemplate預設是采用JDK自帶的序列化方式來做序列化器,看一下:

public JdkSerializationRedisSerializer(@Nullable ClassLoader classLoader) {
    this(new SerializingConverter(), new DeserializingConverter(classLoader));
 }
 public SerializingConverter() {
    this.serializer = new DefaultSerializer();
  }
public class DefaultSerializer implements Serializer<Object> {
  @Override
  public void serialize(Object object, OutputStream outputStream) throws IOException {
    if (!(object instanceof Serializable)) {
      throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " +
          "but received an object of type [" + object.getClass().getName() + "]");
    }
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    objectOutputStream.writeObject(object);
    objectOutputStream.flush();
  }
}
           

很清楚了,最終是使用ObjectOutputStream和ObjectInputStream來讀寫對象,是以對象必須得實作Serializable接口才可以,最終是以二進制的格式存儲到redis裡面,比如:

redistemplate使用_spring-boot-starter-data-redis源碼解析與使用實戰

很顯然,這種序列化方式的可讀性太不友好了。

RedisRepositoriesAutoConfiguration

它實作了spring-data規範,其實就是把redis以資料庫dao的形式來通路,這個東西相對就比較複雜了,但是在實際中使用的并不是很多,我們隻說一下如何使用。

首先定義一個dao,需要繼承CrudRepository:

@Repository
public interface UserDao extends CrudRepository<UserEntity,Long> {
}
           

定義一個資料庫實體:

@RedisHash("user")
public class UserEntity {
    @Id
    private Long id;
    private String username;
    private String password;
}
           

@RedisHash("user")會把user對象存儲到一個key是user:id的hash中,比如:

redistemplate使用_spring-boot-starter-data-redis源碼解析與使用實戰

定義service和controller:

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    public UserEntity save(UserEntity userEntity){
        userDao.save(userEntity);
        return userEntity;
    }
    public UserEntity findById(Long id){
        Optional<UserEntity> optional = userDao.findById(id);
        return optional.isPresent()?optional.get():null;
    }
}
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    private static AtomicLong id = new AtomicLong(0);
    @GetMapping("/add_user")
    public String addUser(String username, String password){
        long idLong = id.incrementAndGet();
        userService.save(new UserEntity(idLong, username, password));
        UserEntity entity = userService.findById(idLong);
        return entity.toString();
    }
}
           
總結一下

- 1.spring-boot-starter-data-redis預設是使用lettuce去通路redis

- 2.内置了StringRedisTemplate和RedisTemplate,應用可以直接使用。當存取對象的時候,StringRedisTemplate需要手動把對象轉化成String,RedisTemplate雖然可以直接存取對象,但是需要對象實作Serializable接口,同時在redis庫中的可讀性比較差。

- 3.由于存在以上的缺點,是以可以把這兩個template的優點給融合到一起,既可以直接存取對象,還可以友善人類閱讀,當然也可以存取List和基本類型,更重要的是還可以支援給key統一添加字首,趕快來使用吧:github位址:https://github.com/xjs1919/redis-client。

歡迎掃碼檢視更多文章:

redistemplate使用_spring-boot-starter-data-redis源碼解析與使用實戰