1. 程式人生 > >REDIS學習(4)spring boot redisTemplate 對REDIS的簡單封裝,以及對引用包的說明,以及對序列化的詳細說明

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);
		}
	}

}