一、Springboot集成Redis
Maven方式导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
自定义RedisTemplate使用
这个包默认提供了Redistemplate和StringRedisTemplate进行操作,使用时直接自动注入即可,内部封装了大量对Redis读取操作,二者的区别基本上就StringRedisTemplate使用了string的序列化方式,从源码就能直接看出来。
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
this.setKeySerializer(RedisSerializer.string());
this.setValueSerializer(RedisSerializer.string());
this.setHashKeySerializer(RedisSerializer.string());
this.setHashValueSerializer(RedisSerializer.string());
}
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
this.setConnectionFactory(connectionFactory);
this.afterPropertiesSet();
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}
StringRedisTemplate将key和value的序列化方式都指定为stringRedisSerializer,也是官方推荐我们使用的,但是这东西实际上也有很多坑,而且存储类型有限,每次都需要手动转json字符串,而且在hash类型的value转换这块只能说谁用谁懂
举个简单的例子,因为hash类型的读取是返回一个map,写入是提供一个map存入Redis,然后这里map的序列化和反序列化都是使用的stringRedisSerializer,这里就要求map的key和value均为String类型,那这里就很坑了,当你将对象转成map存储的时候就会一直抛类型转换异常java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String,实际上也就是我对象属性列上有些值是整形或者其他非String类型,转成map之后这些值无法在内部被序列化成String类型,这就是底层采用stringRedisSerializer序列化方式所带来的的深坑
故此,我的建议是一定要选择自己封装RedistTemplate,可定制性强,而且可以自行选择value的序列器,在此我选择的springboot内置的jackson提供的一个序列器,代码如下
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//创建序列器对象
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer=new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
//设置string和hash类型key的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
//设置string和hash类型value序列化的方式
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
key由于都是String类型,所以直接选择Redis相关包内自带的stringRedisSerializer即可,value采用的jackson提供的genericJackson2JsonRedisSerializer
修改Redis相关配置
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
我们需要导入commons-pool2这个包,虽然spring-boot-starter-data-redis包下内置了lettuce,但是其默认是不开启连接池的,池化操作是提升连接性能的一个必需品,Java多线程池,MySQL连接池等等都是通过池化操作来提高性能的,一个高效的连接池能有效提高服务端与Redis服务器交互的性能,lettuce开启的连接池是依赖于commons-pool2这个包的
同时在application.yml中我们需要添加以下内容:
redis:
host: your-redis-server-ip
port: your-port
password: access-token #Redis配置中设置的密码
database: 0 #数据库索引,默认为0
connect-timeout: 30s #连接等待的最大超时时间
lettuce: #线程安全,比jedis更适合高并发环境,且是springboot内置默认使用的
pool:
max-active: 14 #设置最大连接数,大于Cpu*2,通常为CPU*2+2
max-idle: 12 #最大空闲连接数 CPU*2
min-idle: 0 #最小空闲连接数
max-wait: 5s #连接池资源耗尽时,任务申请资源最大的阻塞时间,超时将抛出异常JedisConnectionException
二、序列化工具选择
现在工具包很多,很多都提供了json序列化和反序列化的功能,但是其内部实现的不同也造成了质量的参差不齐,很多都存在序列化和反序列化的安全隐患,所以在此我们应该择优选择,而不是单纯以自己用着爽就行来选择
1、fastjson
Github官网的介绍是是这样的:
Fastjson is a Java library that can be used to convert Java Objects
into their JSON representation. It can also be used to convert a JSON
string to an equivalent Java object. Fastjson can work with arbitrary
Java objects including pre-existing objects that you do not have
source-code of.FASTJSON 2.0.x has been released, faster and more secure, we recommend
you upgrade to the latest version.
我的使用感觉就是简单易用,基本上需要用到的方法都给你封装好了,之前项目里使用的都是fastjson
上面的翻译过来就是fastjson现在推荐都升级到2.0版本,更快也更稳定,1.x版本的坑很多,甚至还出先了反序列化漏洞被远程方法调用从而直接攻击服务器,基本上就和控制了目标服务器差不多,都能在上面随便执行命令控制目标主机了,感兴趣的可以看 fastjson反序列化漏洞
这个漏洞也主要存在于1.x的版本,2.x据说被修复了,但是感觉这个漏洞带来的风险太大,所以国内很多原本选择使用fastjson都选择去寻求替代品了
2、jackson
代码规范,社区活跃,生态系统强大,是springboot内置的序列化工具,在发现fastjson存在很多代码不规范且漏洞频出的情况下,我选择了这个序列化工具来替代项目原有的fastjson的代码块,缺点就是需要自己进行方法的封装,然后有很多配置是需要自己去选择的,但是这种可定制化的情况也给我们提供了很多的便利,需要什么就可以封装成一个方法到工具类中
public class JacksonUtil {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false)
.registerModule(new SimpleModule().addDeserializer(
SimpleGrantedAuthority.class, new SimpleGrantedAuthorityDeserializer()));
/**
* 将obj对象解析成json字符串
*
* @param obj:
* @return java.lang.String
*/
public static String toJsonString(Object obj) {
if (obj == null) {
return null;
}
try {
return OBJECT_MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
/**
* 将json字符串解析成Java中集合对象
* 泛型T代表解析出来的对象类型
*
* @param json:
* @param tTypeReference:里面的泛型是复杂对象的类型,list,map等必须通过这个对象传入
* @return T
*/
public static <T> T parseObject(String json, TypeReference<T> tTypeReference) {
//对字符串进行专门判空
if (!StringUtils.hasText(json)) {
return null;
}
if (tTypeReference == null) {
return null;
}
try {
return OBJECT_MAPPER.readValue(json, tTypeReference);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取ObjectMapper
*
* @return com.fasterxml.jackson.databind.ObjectMapper
*/
public static ObjectMapper getObjectMapper() {
return OBJECT_MAPPER;
}
/**
* 构建ObjectNode
*
* @return com.fasterxml.jackson.databind.node.ObjectNode
*/
public static ObjectNode createObjectNode() {
return OBJECT_MAPPER.createObjectNode();
}
/*
public static Map beanToMap(Object object) {
Map<String, Object> map = new HashMap<String, Object>();
//利用反射获取对象中所有的属性名和对应值
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
map.put(field.getName(), field.get(object));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
*/
/**
* bean转map
* @param object
* @return
*/
public static Map beanToMap(Object object) {
Map<String, Object> map = new HashMap<String, Object>();
if(object!=null){
BeanMap beanMap=BeanMap.create(object);
Set<String> keys = beanMap.keySet();
for (String key : keys) {
map.put(key,beanMap.get(key))
; }
}
return map;
}
/**
* 将map集合转成Javabean对象
* @param map
* @param clazz:Javabean的类对象
* @param <T>
* @return
* @throws JsonProcessingException
*/
public static <T> T mapToBean(Map<String, Object> map, Class<T> clazz) {
String mapString= toJsonString(map);
return parseObject(mapString,clazz);
}
/**
* 将json字符串解析成JavaBean类型的对象,需要传入对象的Class类型
*
* @param json
* @param bean
* @param <T>
* @return
* @throws JsonProcessingException
*/
public static <T> T parseObject(String json, Class<T> bean) {
if (!StringUtils.hasText(json)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(json, bean);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
然后再提一嘴,springboot中内置的工具真的很多而且好用,规范安全又易用,比如说这个StringUtils,我一开始使用的common-lang3下的,后面又换成了hutool包下的,最后发现spring内置的这个功能全而且好用,不需要额外导包给自己的项目增加复杂度和维护难度