版本概要
springboot版本2.1.2.RELEASE
kotlin版本1.3.11
新建项目请参考springboot初探和配置文件映射
redis集成
- 新增依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> - 新增配置项
1
2
3
4
5spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 1000ms - 配置redisTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class RedisConfig{
/**
* 将redisTemplate格式化为string,any格式
*
* @param factory redis连接工厂
* @return redisTemplate
*/
fun redisTemplate(factory: RedisConnectionFactory):RedisTemplate<String,Any> {
val template = RedisTemplate<String,Any>()
template.setConnectionFactory(factory)
val jackson2JsonRedisSerializer = Jackson2JsonRedisSerializer(Any::class.java)
val om = ObjectMapper()
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)
jackson2JsonRedisSerializer.setObjectMapper(om)
val stringRedisSerializer = StringRedisSerializer()
template.keySerializer = stringRedisSerializer
template.hashKeySerializer = stringRedisSerializer
template.valueSerializer = jackson2JsonRedisSerializer
template.hashValueSerializer = jackson2JsonRedisSerializer
template.afterPropertiesSet()
return template
}
} - 新建测试类,继承生成的测试类,并运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class RedisTest : KotlinSpringbootApplicationTests() {
private val log = LoggerFactory.getLogger(this.javaClass)
private val testKey = this.javaClass.name
lateinit var redisTemplate: RedisTemplate<String, Any>
fun testString() {
log.info("---设置值---")
redisTemplate.opsForValue().set(testKey, "hello")
val str = redisTemplate.opsForValue().get(testKey) as? String
log.info("---打印值:$str---")
redisTemplate.delete(testKey)
}
} - 尝试进行对象存储
1
2
3
4
5
6
7
8
9
10
11data class User(val username:String, val sex:String,val phone:Long)
//新增测试类
fun testAny() {
val user = User("maple","man",18011111111)
log.info("---设置对象---")
redisTemplate.opsForValue().set(testKey, user)
val user = redisTemplate.opsForValue().get(testKey) as? User
log.info("---打印值:$user---")
redisTemplate.delete(testKey)
}
我们会发现当前的序列化工具对于kotlin对象的序列化并不是那么理想,我们需要重写一个序列化工具方式1:使用hessian帮助我们进行序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55//依赖
<hessian.vesion>4.0.51</hessian.vesion>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>${hessian.vesion}</version>
</dependency>
//序列化工具包
object SerializeUtils{
fun hessianDeserialize(by: ByteArray?): Any {
by?: throw NullPointerException()
return hessianDeserialize(ByteArrayInputStream(by))
}
fun hessianDeserialize(input:InputStream): Any{
return HessianInput(input).readObject()
}
fun hessianSerialize(obj: Any?): ByteArray {
obj?: throw NullPointerException()
try {
val os = ByteArrayOutputStream()
val ho = HessianOutput(os)
ho.writeObject(obj)
return os.toByteArray()
} catch (e: Exception) {
throw e
}
}
fun hessianSerialize(obj: Any,out:OutputStream){
HessianOutput(out).writeObject(obj)
}
fun javaSerialize(obj: Any?):ByteArray{
obj?: throw NullPointerException()
val os = ByteArrayOutputStream()
val out = ObjectOutputStream(os)
out.writeObject(obj)
return os.toByteArray()
}
fun javaSerialize(by: ByteArray?):Any{
by?: throw NullPointerException()
val `is` = ByteArrayInputStream(by)
val `in` = ObjectInputStream(`is`)
return `in`.readObject()
}
} - 新建类实现RedisSerializer(对象需要实现序列化)并修改redis配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class HessianRedisSerializer<T>(private var clazz: Class<T>) : RedisSerializer<T>{
override fun serialize(t: T?): ByteArray? {
return SerializeUtils.hessianSerialize(t)
}
override fun deserialize(bt: ByteArray?): T? {
if(bt == null || bt.isEmpty()) return null
return SerializeUtils.hessianDeserialize(bt) as? T
}
}
//redis配置
fun redisTemplate(factory: RedisConnectionFactory):RedisTemplate<String,Any> {
val template = RedisTemplate<String,Any>()
template.setConnectionFactory(factory)
val hessianRedisSerializer = HessianRedisSerializer(Any::class.java)
val stringRedisSerializer = StringRedisSerializer()
template.keySerializer = stringRedisSerializer
template.hashKeySerializer = stringRedisSerializer
template.valueSerializer = hessianRedisSerializer
template.hashValueSerializer = hessianRedisSerializer
template.afterPropertiesSet()
return template
}
//修改实体类 让实体类实现序列化接口
data class User(val username:String, val sex:String,val phone:Long):Serializable - 再次测试,存取成功
方式2,使用java 对象输入输出流实现(对象需要实现序列化) 比较慢 不推荐
1
2
3
4
5
6
7
8
9
10
11
12class HessianRedisSerializer<T>(private var clazz: Class<T>) : RedisSerializer<T>{
override fun serialize(t: T?): ByteArray? {
return SerializeUtils.hessianSerialize(t)
}
override fun deserialize(bt: ByteArray?): T? {
if(bt == null || bt.isEmpty()) return null
return SerializeUtils.hessianDeserialize(bt) as? T
}
}方式3,使用fastjson,对象可以不用实现序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39//依赖
<fast-json.vesion>1.2.54</fast-json.vesion>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fast-json.vesion}</version>
</dependency>
//序列化类
class FastJsonRedisSerializer<T>(private val clazz:Class<T> ) : RedisSerializer<T>{
private val charset = Charset.forName("utf-8")!!
override fun serialize(t: T?): ByteArray? {
return JSON.toJSONString(t, SerializerFeature.WriteClassName).toByteArray(charset)
}
override fun deserialize(bt: ByteArray?): T? {
if(bt == null || bt.isEmpty()) return null
val str = String(bt, charset)
return JSON.parseObject<T>(str, clazz)
}
}
//配置
fun redisTemplate(factory: RedisConnectionFactory):RedisTemplate<String,Any> {
val template = RedisTemplate<String,Any>()
template.setConnectionFactory(factory)
var fastJsonRedisSerializer = FastJsonRedisSerializer(Any::class.java)
//配置白名单
ParserConfig.getGlobalInstance().addAccept("com.maple.kotlinspringboot.entity.User")
//或者直接关闭这个检测
//ParserConfig.getGlobalInstance().isAutoTypeSupport = true
val stringRedisSerializer = StringRedisSerializer()
template.keySerializer = stringRedisSerializer
template.hashKeySerializer = stringRedisSerializer
template.valueSerializer = fastJsonRedisSerializer
template.hashValueSerializer = fastJsonRedisSerializer
template.afterPropertiesSet()
return template
} - 做一个简单的redis工具包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class RedisUtils {
lateinit var redisTemplate: RedisTemplate<String, Any>
/**
* 获取指定缓存对象
*
* @param key 键
* @return any 值对象
*/
fun <T : Any> getT(key: String):T? {
return redisTemplate.opsForValue().get(key) as? T
}
/**
* 获取long值
*
* @param key 键
* @return any 值对象
*/
fun getLong(key: String):Long?{
val value = getAny(key)
return when(value){
is Byte -> value.toLong()
is Int -> value.toLong()
is Long -> value
is Float -> value.toLong()
is Double -> value.toLong()
is Char -> value.toLong()
is String -> value.toLongOrNull()
else -> null
}
}
/**
* 获取int值
*
* @param key 键
* @return any 值对象
*/
fun getInt(key: String):Int?{
val value = getAny(key)
return when(value){
is Byte -> value.toInt()
is Int -> value
is Long -> value.toInt()
is Float -> value.toInt()
is Double -> value.toInt()
is Char -> value.toInt()
is String -> value.toIntOrNull()
else -> null
}
}
/**
* 存入普通缓存对象
*
* @param key 键
* @param value 值对象
* @return Boolean 是否成功
*/
fun setAny(key:String,value:Any) {
redisTemplate.opsForValue().set(key,value)
}
/**
* 删除普通缓存对象
*
* @param key 键
* @return Boolean 是否成功
*/
fun delete(key:String):Boolean {
return redisTemplate.delete(key)
}
/**
* 获取普通缓存对象
*
* @param key 键
* @return any 值对象
*/
private fun getAny(key: String):Any? {
return redisTemplate.opsForValue().get(key)
}
}
redisson集成
- 引入依赖
1
2
3
4
5
6<redisson.version>3.10.0</redisson.version>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency> - 配置bean
1
2
3
4
5
6
7
8
9
10
lateinit var redisProperties: RedisProperties
fun redisClient(): RedissonClient {
val serverConfig = Config().apply {
this.useSingleServer()
.setAddress("redis://${redisProperties.host}:${redisProperties.port}").timeout = (redisProperties.timeout.seconds * 1000).toInt()
}
return Redisson.create(serverConfig)
} - 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41class RedisLockTest : BaseTest(){
private val log = LoggerFactory.getLogger(this.javaClass)
private val testRedisKey = this.javaClass.name
private val lockKey = "testLockKey"
lateinit var redisUtils: RedisUtils
lateinit var redisClient: RedissonClient
fun setUp() {
log.info("----------before-----------")
}
fun tearDown() {
log.info("----------after-----------")
}
fun setInt(){
redisUtils.setAny(testRedisKey,100)
println(redisUtils.getInt(testRedisKey))
}
fun testLock(){
val lock:RLock = redisClient.getLock(lockKey)
println("---尝试上锁---")
lock.tryLock(20, TimeUnit.SECONDS)
println("---进入循环---")
for (i in 0..10){
val stock = redisUtils.getInt(testRedisKey)
Thread.sleep(1000)
if(stock!! > 0){
redisUtils.setAny(testRedisKey, value = stock-1)
println("stock: $stock-1")
}
}
lock.unlock()
}
} - 先执行一次设值,再同时运行两次测试类,我们希望达到的效果是,当第一个执行完循环时,第二个才开始进入循环,可以看到,最终结果确如我们所愿
注解获取缓存对象
- 不论我们使用shiro,还是security或是其他的框架做项目的权限控制,我们可能需要将用户存入缓存中,这里我使用注解获取缓存中的用户对象
- 新建一个注解类
1
2
3
annotation class CurrentUser - 新建一个方法解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CurrentUserMethodArgumentResolver:HandlerMethodArgumentResolver{
lateinit var redisUtils: RedisUtils
override fun supportsParameter(parameter: MethodParameter): Boolean {
return parameter.parameterType.isAssignableFrom(User::class.java)
&& parameter.hasParameterAnnotation(CurrentUser::class.java)
}
override fun resolveArgument(parameter: MethodParameter, mavContainer: ModelAndViewContainer?, webRequest: NativeWebRequest, binderFactory: WebDataBinderFactory?): Any? {
return redisUtils.getT<User>("currentUser")
}
} - 修改spring配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SpringConfig : WebMvcConfigurer {
lateinit var currentUserResolver: CurrentUserMethodArgumentResolver
override fun addViewControllers(registry: ViewControllerRegistry) {
registry.addViewController("/").setViewName("forward:/hello")
registry.setOrder(Ordered.HIGHEST_PRECEDENCE)
}
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
super.addArgumentResolvers(resolvers)
resolvers.add(currentUserResolver)
}
} - 修改控制层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class HelloController {
lateinit var redisUtils: RedisUtils
fun hello(User) user:: String {
return user.toString()
}
fun testLogin(user:User): String {
redisUtils.setAny("currentUser",user)
return "success"
}
} - 依次运行
http://localhost:9001/testLogin?username=me&sex=man&phone=1000
http://localhost:9001/hello
可以看到我们成功取到了这个对象