REDIS學習(4)spring boot redisTemplate 對REDIS的簡單封裝,以及對引用包的說明,以及對序列化的詳細說明
綜合1,2,3以及目前,我們所引用的redis包不過是
<dependency>
<groupId>org.springframework.boot</groupId><!-- 會附帶引進jedis-2.7.3的包 -->
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
新增進來後
引用包至少有
spring-boot-starter-redis-1.3.5.RELEASE.jar
spring-data-redis-1.6.4.RELEASE.jar
jedis-2.7.3.jar
三個包
結合前面的第三節,redis都是跟Spring一起做為通用快取介面使用
這一節使用的redisTemplate更像是一個數據庫的操作
@Service public class RedisService { @Autowired RedisTemplate<?, ?> redisTemplate; /**獲得客戶端列表 */ public List<?> getClients(){ return redisTemplate.getClientList(); } /**設定有超時時間的KV */ public Long set(String key, String value, long seconds) { return redisTemplate.execute(c -> { c.set(key.getBytes(), value.getBytes()); c.expire(key.getBytes(), seconds); return 1L; }); } /** *存入不會超時的KV */ public Long set(String key, String value) { return redisTemplate.execute(c -> { c.set(key.getBytes(), value.getBytes()); return 1L; }); } /** * redis資料庫條數 */ public Long dbSize() { return redisTemplate.execute(c -> c.dbSize()); } public String ping() { return redisTemplate.execute(c -> c.ping()); } /** * 刪除所有指定資料庫的資料 */ public long flushDB() { return redisTemplate.execute(c -> { c.flushDb(); return 1L; }); } /**判斷redis資料庫是否有對應的key*/ public boolean exist(String key){ return redisTemplate.execute(c->c.exists(key.getBytes())); } /**獲得redis資料庫所有的key*/ public Set<String> keys(String pattern){ return redisTemplate.execute(c->c.keys(pattern.getBytes()).stream().map(this::getUTF).collect(Collectors.toSet())); } private String getUTF(byte[] data){ try { return new String(data, "utf-8"); } catch (UnsupportedEncodingException e) { LogCore.BASE.error("parse bytes err:{}", e); return null; } } }
使用:
@SuppressWarnings({ "unchecked", "rawtypes" }) public long save(UserInfo usrInfo) { return redisTemplate.execute(c -> { RedisSerializer key_slz = redisTemplate.getKeySerializer(); RedisSerializer slz = redisTemplate.getValueSerializer(); LogCore.BASE.info("key_slz={},slz={}",key_slz.getClass().getSimpleName(),slz.getClass().getSimpleName()); c.set( key_slz.serialize(usrInfo.getClass().getSimpleName() + ":" + usrInfo.no), slz.serialize(usrInfo)); return 1L; }); } @SuppressWarnings({ "rawtypes", "unchecked" }) public UserInfo get(String no) { return (UserInfo) redisTemplate.execute(c -> { RedisSerializer key_slz = redisTemplate.getKeySerializer(); RedisSerializer slz = redisTemplate.getValueSerializer(); return slz.deserialize(c.get(key_slz.serialize(UserInfo.class.getSimpleName() + ":" + no))); }); }
根據上一節內容,我們知道如果沒有指定RedisTemplate,spring redis cache會選用javaAPI的序列化方式來將物件序列化,這種序列化方式效能一般,切後面增加欄位會造成麻煩,我覺的比較合適的序列化方式有protocol buffer,JSON,帶有遞迴的二進位制位元組流的方式等。
我們下面詳細分析RedisTempalte這個類,StringRedisTemplate是RedisTemplate的唯一子類。
這個類很簡單,我們甚至可以仿照此方法定義自己的MyRedisTemplate
[API]
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
setKeySerializer(stringSerializer);
setValueSerializer(stringSerializer);
setHashKeySerializer(stringSerializer);
setHashValueSerializer(stringSerializer);
}
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}
注意,子類的構造方法總會預設呼叫父類的無參構造方法。
RedisTemplate預設定義了兩個常用的序列化類
private RedisSerializer<?> defaultSerializer = new JdkSerializationRedisSerializer();
以及 private RedisSerializer<String> stringSerializer = new StringRedisSerializer();
我們可以如下注入我們的RedisTemplate,下面的例子將key的序列化方式定義為字串,將value的序列化使用了jackson
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisTemplate<?, ?> redisTemplate) {
RedisCacheManager manager = new RedisCacheManager(redisTemplate);
return manager;
}
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(connectionFactory);
setMySerializer(template);
template.afterPropertiesSet();
LogCore.BASE.info("template{}" ,ReflectionToStringBuilder.toString(template, ToStringStyle.SHORT_PREFIX_STYLE));
return template;
}
/**
* 設定序列化方法
*/
private void setMySerializer(RedisTemplate template) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setKeySerializer(template.getStringSerializer());
template.setValueSerializer(jackson2JsonRedisSerializer);
}
@Bean
public KeyGenerator smpkeyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()).append(":");//執行方法所在的類
sb.append(Stream.of(params).map(String::valueOf).collect(Collectors.joining("_")));
return sb.toString();
};
}
}
最後我們看一下Spring data redis定義的序列化介面和預設的JDK序列化的封裝,程式碼比較整潔,我們可以從中學習
[API]
public interface RedisSerializer<T> {
byte[] serialize(T t) throws SerializationException;
T deserialize(byte[] bytes) throws SerializationException;
}
[API]
public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {
private Converter<Object, byte[]> serializer = new SerializingConverter();
private Converter<byte[], Object> deserializer = new DeserializingConverter();
public Object deserialize(byte[] bytes) {
if (SerializationUtils.isEmpty(bytes)) {
return null;
}
try {
return deserializer.convert(bytes);
} catch (Exception ex) {
throw new SerializationException("Cannot deserialize", ex);
}
}
public byte[] serialize(Object object) {
if (object == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
throw new SerializationException("Cannot serialize", ex);
}
}
}
[API]
public interface Converter<S, T> {
T convert(S source);
}
[API]
public class SerializingConverter implements Converter<Object, byte[]> {
private final Serializer<Object> serializer;
public SerializingConverter() {
this.serializer = new DefaultSerializer();
}
public SerializingConverter(Serializer<Object> serializer) {
Assert.notNull(serializer, "Serializer must not be null");
this.serializer = serializer;
}
@Override
public byte[] convert(Object source) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);
try {
this.serializer.serialize(source, byteStream);
return byteStream.toByteArray();
}
catch (Throwable ex) {
throw new SerializationFailedException("Failed to serialize object using " +
this.serializer.getClass().getSimpleName(), ex);
}
}
}
[API]
public class DeserializingConverter implements Converter<byte[], Object> {
private final Deserializer<Object> deserializer;
public DeserializingConverter() {
this.deserializer = new DefaultDeserializer();
}
public DeserializingConverter(ClassLoader classLoader) {
this.deserializer = new DefaultDeserializer(classLoader);
}
public DeserializingConverter(Deserializer<Object> deserializer) {
Assert.notNull(deserializer, "Deserializer must not be null");
this.deserializer = deserializer;
}
@Override
public Object convert(byte[] source) {
ByteArrayInputStream byteStream = new ByteArrayInputStream(source);
try {
return this.deserializer.deserialize(byteStream);
}
catch (Throwable ex) {
throw new SerializationFailedException("Failed to deserialize payload. " +
"Is the byte array a result of corresponding serialization for " +
this.deserializer.getClass().getSimpleName() + "?", ex);
}
}
}
[API]序列化writeObject
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();
}
}
[API]反序列化是用類載入器
public class DefaultDeserializer implements Deserializer<Object> {
private final ClassLoader classLoader;
public DefaultDeserializer() {
this.classLoader = null;
}
<span style="color:#ff0000;"> public DefaultDeserializer(ClassLoader classLoader) {
this.classLoader = classLoader;
}</span>
@Override
@SuppressWarnings("resource")
public Object deserialize(InputStream inputStream) throws IOException {
ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
try {
return objectInputStream.readObject();
}
catch (ClassNotFoundException ex) {
throw new NestedIOException("Failed to deserialize object type", ex);
}
}
}