这是本人打算长篇更新的博客内容,关于Sa-token这个国人开发的鉴权框架,这是我第一个尝试看源码的框架,同时也去尝试了解之前不敢涉足的一些关于Springboot底层的一些代码

本文没有先后顺序,后期会理顺整理一下

Sa-token-dao-redis

我第一次看模块就点开了这个模块,主要是最近想回顾redis,然后觉得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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
@Component
public class SaTokenDaoRedis implements SaTokenDao {
/**
* String专用
*/
public StringRedisTemplate stringRedisTemplate;
/**
* Object专用
*/
public RedisTemplate<String, Object> objectRedisTemplate;
/**
* 标记:是否已初始化成功
*/
public boolean isInit;

@Autowired
public void init(RedisConnectionFactory connectionFactory) {
// 不重复初始化
if(this.isInit) {
return;
}
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();
// 构建StringRedisTemplate
StringRedisTemplate stringTemplate = new StringRedisTemplate();
stringTemplate.setConnectionFactory(connectionFactory);
stringTemplate.afterPropertiesSet();
// 构建RedisTemplate
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();

// 开始初始化相关组件
this.stringRedisTemplate = stringTemplate;
this.objectRedisTemplate = template;
this.isInit = true;
}


/**
* 获取Value,如无返空
*/
@Override
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}

/**
* 写入Value,并设定存活时间 (单位: 秒)
*/
@Override
public void set(String key, String value, long timeout) {
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
stringRedisTemplate.opsForValue().set(key, value);
} else {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
}

/**
* 修改指定key-value键值对 (过期时间不变)
*/
@Override
public void update(String key, String value) {
long expire = getTimeout(key);
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.set(key, value, expire);
}

/**
* 删除Value
*/
@Override
public void delete(String key) {
stringRedisTemplate.delete(key);
}

/**
* 获取Value的剩余存活时间 (单位: 秒)
*/
@Override
public long getTimeout(String key) {
return stringRedisTemplate.getExpire(key);
}

/**
* 修改Value的剩余存活时间 (单位: 秒)
*/
@Override
public void updateTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getTimeout(key);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.set(key, this.get(key), timeout);
}
return;
}
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}


/**
* 获取Object,如无返空
*/
@Override
public Object getObject(String key) {
return objectRedisTemplate.opsForValue().get(key);
}

/**
* 写入Object,并设定存活时间 (单位: 秒)
*/
@Override
public void setObject(String key, Object object, long timeout) {
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
objectRedisTemplate.opsForValue().set(key, object);
} else {
objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS);
}
}

/**
* 更新Object (过期时间不变)
*/
@Override
public void updateObject(String key, Object object) {
long expire = getObjectTimeout(key);
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.setObject(key, object, expire);
}

/**
* 删除Object
*/
@Override
public void deleteObject(String key) {
objectRedisTemplate.delete(key);
}

/**
* 获取Object的剩余存活时间 (单位: 秒)
*/
@Override
public long getObjectTimeout(String key) {
return objectRedisTemplate.getExpire(key);
}

/**
* 修改Object的剩余存活时间 (单位: 秒)
*/
@Override
public void updateObjectTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getObjectTimeout(key);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久,那么再次set一次
this.setObject(key, this.getObject(key), timeout);
}
return;
}
objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}



/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<String>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
}


}

初看感觉很简单,就是用SpringDataRedis来进行对Sa-token的id一些存取操作

个人觉得难点在于初始化那块

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
/**
* String专用
*/
public StringRedisTemplate stringRedisTemplate;

/**
* Object专用
*/
public RedisTemplate<String, Object> objectRedisTemplate;

/**
* 标记:是否已初始化成功
*/
public boolean isInit;

@Autowired
public void init(RedisConnectionFactory connectionFactory) {
// 不重复初始化
if(this.isInit) {
return;
}
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();
// 构建StringRedisTemplate
StringRedisTemplate stringTemplate = new StringRedisTemplate();
stringTemplate.setConnectionFactory(connectionFactory);
stringTemplate.afterPropertiesSet();
// 构建RedisTemplate
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
// 开始初始化相关组件
this.stringRedisTemplate = stringTemplate;
this.objectRedisTemplate = template;
this.isInit = true;
}

首先这个init方法至关重要,初始化RedisTemplate,同时方法是@Autowired注解修饰,这意味着方法会自动调用,参数会自动注入

那么问题来了,既然这个参数会自动注入,然后参数类型是RedisConnectionFactory,所以我们就要找了。

最终找到了唯一调用这个方法的类

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
@Configuration
public class SaAloneRedisInject implements EnvironmentAware{

/**
* 配置信息的前缀
*/
public static final String ALONE_PREFIX = "sa-token.alone-redis";

/**
* Sa-Token 持久层接口
*/
@Autowired(required = false)
public SaTokenDao saTokenDao;

/**
* 开始注入
*/
@Override
public void setEnvironment(Environment environment) {
try {
// 如果为空或者默认实现,则不进行任何操作
if(saTokenDao == null || saTokenDao instanceof SaTokenDaoDefaultImpl) {
return;
}
// 如果配置文件不包含相关配置,则不进行任何操作
if(environment.getProperty(ALONE_PREFIX + ".host") == null) {
return;
}

// ------------------- 开始注入

// 获取cfg对象
RedisProperties cfg = Binder.get(environment).bind(ALONE_PREFIX, RedisProperties.class).get();

// 1. Redis配置
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(cfg.getHost());
redisConfig.setPort(cfg.getPort());
redisConfig.setDatabase(cfg.getDatabase());
redisConfig.setPassword(RedisPassword.of(cfg.getPassword()));

// 2. 连接池配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// pool配置
Lettuce lettuce = cfg.getLettuce();
if(lettuce.getPool() != null) {
RedisProperties.Pool pool = cfg.getLettuce().getPool();
// 连接池最大连接数
poolConfig.setMaxTotal(pool.getMaxActive());
// 连接池中的最大空闲连接
poolConfig.setMaxIdle(pool.getMaxIdle());
// 连接池中的最小空闲连接
poolConfig.setMinIdle(pool.getMinIdle());
// 连接池最大阻塞等待时间(使用负值表示没有限制)
poolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
}
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
// timeout
if(cfg.getTimeout() != null) {
builder.commandTimeout(cfg.getTimeout());
}
// shutdownTimeout
if(lettuce.getShutdownTimeout() != null) {
builder.shutdownTimeout(lettuce.getShutdownTimeout());
}
// 创建Factory对象
LettuceClientConfiguration clientConfig = builder.poolConfig(poolConfig).build();
LettuceConnectionFactory factory = new LettuceConnectionFactory(redisConfig, clientConfig);
factory.afterPropertiesSet();

// 3. 开始初始化 SaTokenDao
// 如果是SaTokenDaoRedis
try {
Class.forName("cn.dev33.satoken.dao.SaTokenDaoRedis");
SaTokenDaoRedis dao = (SaTokenDaoRedis)saTokenDao;
dao.isInit = false;
dao.init(factory);
return;
} catch (ClassNotFoundException e) {
}
// 如果是SaTokenDaoRedisJackson
try {
Class.forName("cn.dev33.satoken.dao.SaTokenDaoRedisJackson");
SaTokenDaoRedisJackson dao = (SaTokenDaoRedisJackson)saTokenDao;
dao.isInit = false;
dao.init(factory);
return;
} catch (ClassNotFoundException e) {
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 骗过编辑器,增加配置文件代码提示
* @return 配置对象
*/
@ConfigurationProperties(prefix = ALONE_PREFIX)
public RedisProperties getSaAloneRedisConfig() {
return new RedisProperties();
}

}

首先很明显这个类由@Configuration修饰,注入到IOC容器中,以便Springboot默认执行setEnvironment()方法

然后,下面这段话,将配置文件中的属性对应成对象,并实例化获得

1
Binder.get(environment).bind(ALONE_PREFIX, RedisProperties.class).get();
1
2
3
4
5
6
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(cfg.getHost());
redisConfig.setPort(cfg.getPort());
redisConfig.setDatabase(cfg.getDatabase());
redisConfig.setPassword(RedisPassword.of(cfg.getPassword()));
//这边就是配置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
//接下来就是连接池配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// pool配置
Lettuce lettuce = cfg.getLettuce();
if(lettuce.getPool() != null) {
RedisProperties.Pool pool = cfg.getLettuce().getPool();
// 连接池最大连接数
poolConfig.setMaxTotal(pool.getMaxActive());
// 连接池中的最大空闲连接
poolConfig.setMaxIdle(pool.getMaxIdle());
// 连接池中的最小空闲连接
poolConfig.setMinIdle(pool.getMinIdle());
// 连接池最大阻塞等待时间(使用负值表示没有限制)
poolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
}
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
// timeout
if(cfg.getTimeout() != null) {
builder.commandTimeout(cfg.getTimeout());
}
// shutdownTimeout
if(lettuce.getShutdownTimeout() != null) {
builder.shutdownTimeout(lettuce.getShutdownTimeout());
}
1
2
3
4
// 创建Factory对象 
LettuceClientConfiguration clientConfig = builder.poolConfig(poolConfig).build();
LettuceConnectionFactory factory = new LettuceConnectionFactory(redisConfig, clientConfig);
factory.afterPropertiesSet();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 初始化SaTokenDao 
// 如果是SaTokenDaoRedis
try {
Class.forName("cn.dev33.satoken.dao.SaTokenDaoRedis");
SaTokenDaoRedis dao = (SaTokenDaoRedis)saTokenDao;
dao.isInit = false;
dao.init(factory);
return;
} catch (ClassNotFoundException e) {
}
// 如果是SaTokenDaoRedisJackson
try {
Class.forName("cn.dev33.satoken.dao.SaTokenDaoRedisJackson");
SaTokenDaoRedisJackson dao = (SaTokenDaoRedisJackson)saTokenDao;
dao.isInit = false;
dao.init(factory);
return;
} catch (ClassNotFoundException e) {
}
} catch (Exception e) {
e.printStackTrace();
}
//尝试去找到对应包下的类,根据找到的情况初始化dao

对于之前那个init上面用@Autowired的问题,还是有许多疑问,比如在SaTokenDaoRedis先注入进IOC,然后再SaAloneRedisInject里面调用setEnvironment()方法进行注入,这个时候会调用init()方法,然后调用默认的JedisConnectionFactory容器对象。

最好的解释是,setEnvironment()先运行,然后调用SaTokenDaoRedis的init()方法对内部进行初始化,如果没有的话,才执行默认的连接工厂。

为此我特地去查了下Springboot中的依赖注入顺序,发现setEnvironment确实先运行