# 实现Shiro的Cache(Redis)功能
SpringBoot + Shiro + JWT集成Redis缓存(Jedis)
我的项目地址
- Github:https://github.com/wang926454/ShiroJwt (opens new window)
- Gitee(码云):https://gitee.com/dolyw/ShiroJwt (opens new window)
# 1. 实现流程
- 建立JedisPool(启动注入JedisPool)(使用Jedis操作Redis)
- 重写Shiro的Cache保存读取和Cache管理器
# 2. 代码实现
# 2.1. JedisPool搭建
首先加入Jedis的Jar(Shiro的集成这里就不说了)
<!-- Redis-Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
- config.properties(Redis的配置属性)
# Redis服务器地址
redis.host=127.0.0.1
# Redis服务器连接端口
redis.port=6379
# Redis服务器连接密码(默认为空)
redis.password=
# 连接超时时间(毫秒)
redis.timeout=10000
# 连接池最大连接数(使用负值表示没有限制)
redis.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
redis.pool.max-wait=-1
# 连接池中的最大空闲连接
redis.pool.max-idle=8
# 连接池中的最小空闲连接
redis.pool.min-idle=0
- JedisConfig.java(JedisPool启动配置Bean)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Jedis配置,项目启动注入JedisPool
* http://www.cnblogs.com/GodHeng/p/9301330.html
* @author dolyw.com
* @date 2018/9/5 10:35
*/
@Configuration
@EnableAutoConfiguration
@PropertySource("classpath:config.properties")
@ConfigurationProperties(prefix = "redis")
public class JedisConfig {
private static Logger logger = LoggerFactory.getLogger(JedisConfig.class);
private String host;
private int port;
private String password;
private int timeout;
@Value("${redis.pool.max-active}")
private int maxActive;
@Value("${redis.pool.max-wait}")
private int maxWait;
@Value("${redis.pool.max-idle}")
private int maxIdle;
@Value("${redis.pool.min-idle}")
private int minIdle;
@Bean
public JedisPool redisPoolFactory(){
try {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWait);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMinIdle(minIdle);
// JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, null);
logger.info("初始化Redis连接池JedisPool成功!" + " Redis地址: " + host + ":" + port);
return jedisPool;
} catch (Exception e) {
logger.error("初始化Redis连接池JedisPool异常:" + e.getMessage());
}
return null;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getMaxActive() {
return maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMaxWait() {
return maxWait;
}
public void setMaxWait(int maxWait) {
this.maxWait = maxWait;
}
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
}
- JedisUtil(Jedis工具类)
import com.wang.exception.CustomException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Set;
/**
* JedisUtil(推荐存Byte数组,存Json字符串效率更慢)
* @author dolyw.com
* @date 2018/9/4 15:45
*/
@Component
public class JedisUtil {
/**
* Logger
*/
private static Logger logger = LoggerFactory.getLogger(JedisUtil.class);
/**
* Status-OK
*/
public final static String OK = "OK";
/**
* 静态注入JedisPool连接池
* 本来是正常注入JedisUtil,可以在Controller和Service层使用,但是重写Shiro的CustomCache无法注入JedisUtil
* 现在改为静态注入JedisPool连接池,JedisUtil直接调用静态方法即可
* https://blog.csdn.net/W_Z_W_888/article/details/79979103
*/
private static JedisPool jedisPool;
@Autowired
public void setJedisPool(JedisPool jedisPool) {
JedisUtil.jedisPool = jedisPool;
}
/**
* 获取Jedis实例
* @param
* @return redis.clients.jedis.Jedis
* @author dolyw.com
* @date 2018/9/4 15:47
*/
public static synchronized Jedis getJedis() {
try {
if (jedisPool != null) {
Jedis resource = jedisPool.getResource();
return resource;
} else {
return null;
}
} catch (Exception e) {
throw new CustomException("获取Jedis资源异常:" + e.getMessage());
}
}
/**
* 释放Jedis资源
* @param
* @return void
* @author dolyw.com
* @date 2018/9/5 9:16
*/
public static void closePool() {
try {
jedisPool.close();
}catch (Exception e){
throw new CustomException("释放Jedis资源异常:" + e.getMessage());
}
}
/**
* 获取redis键值-object
* @param key
* @return java.lang.Object
* @author dolyw.com
* @date 2018/9/4 15:47
*/
public static Object getObject(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
byte[] bytes = jedis.get(key.getBytes());
if(StringUtil.isNotNull(bytes)) {
return SerializableUtil.unserializable(bytes);
}
} catch (Exception e) {
throw new CustomException("获取Redis键值getObject方法异常:key=" + key + " cause=" + e.getMessage());
} finally {
if(jedis != null) {
jedis.close();
}
}
return null;
}
/**
* 设置redis键值-object
* @param key
* @param value
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 15:49
*/
public static String setObject(String key, Object value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.set(key.getBytes(), SerializableUtil.serializable(value));
} catch (Exception e) {
throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
} finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 设置redis键值-object-expiretime
* @param key
* @param value
* @param expiretime
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 15:50
*/
public static String setObject(String key, Object value, int expiretime) {
String result = "";
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
result = jedis.set(key.getBytes(), SerializableUtil.serializable(value));
if(OK.equals(result)) {
jedis.expire(key.getBytes(), expiretime);
}
return result;
} catch (Exception e) {
throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
} finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 获取redis键值-Json
* @param key
* @return java.lang.Object
* @author dolyw.com
* @date 2018/9/4 15:47
*/
public static String getJson(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.get(key);
} catch (Exception e) {
throw new CustomException("获取Redis键值getJson方法异常:key=" + key + " cause=" + e.getMessage());
} finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 设置redis键值-Json
* @param key
* @param value
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 15:49
*/
public static String setJson(String key, String value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.set(key, value);
} catch (Exception e) {
throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
} finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 设置redis键值-Json-expiretime
* @param key
* @param value
* @param expiretime
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 15:50
*/
public static String setJson(String key, String value, int expiretime) {
String result = "";
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
result = jedis.set(key, value);
if(OK.equals(result)) {
jedis.expire(key, expiretime);
}
return result;
} catch (Exception e) {
throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
} finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 删除key
* @param key
* @return java.lang.Long
* @author dolyw.com
* @date 2018/9/4 15:50
*/
public static Long delKey(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.del(key.getBytes());
}catch(Exception e) {
throw new CustomException("删除Redis的键delKey方法异常:key=" + key + " cause=" + e.getMessage());
}finally{
if(jedis != null) {
jedis.close();
}
}
}
/**
* key是否存在
* @param key
* @return java.lang.Boolean
* @author dolyw.com
* @date 2018/9/4 15:51
*/
public static Boolean exists(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.exists(key.getBytes());
}catch(Exception e) {
throw new CustomException("查询Redis的键是否存在exists方法异常:key=" + key + " cause=" + e.getMessage());
}finally{
if(jedis != null) {
jedis.close();
}
}
}
/**
* 模糊查询获取key集合
* @param key
* @return java.util.Set<java.lang.String>
* @author dolyw.com
* @date 2018/9/6 9:43
*/
public static Set<String> keysS(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.keys(key);
}catch(Exception e) {
throw new CustomException("模糊查询Redis的键集合keysS方法异常:key=" + key + " cause=" + e.getMessage());
}finally{
if(jedis != null) {
jedis.close();
}
}
}
/**
* 模糊查询获取key集合
* @param key
* @return java.util.Set<java.lang.String>
* @author dolyw.com
* @date 2018/9/6 9:43
*/
public static Set<byte[]> keysB(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.keys(key.getBytes());
}catch(Exception e) {
throw new CustomException("模糊查询Redis的键集合keysB方法异常:key=" + key + " cause=" + e.getMessage());
}finally{
if(jedis != null) {
jedis.close();
}
}
}
/**
* 获取过期剩余时间
* @param key
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/11 16:26
*/
public static Long getExpireTime(String key) {
Long result = -2L;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
result = jedis.ttl(key);
return result;
} catch (Exception e) {
throw new CustomException("获取Redis键过期剩余时间getExpireTime方法异常:key=" + key + " cause=" + e.getMessage());
} finally {
if(jedis != null) {
jedis.close();
}
}
}
}
- SerializableUtil(JedisUtil用到)
import com.wang.exception.CustomException;
import java.io.*;
/**
* Serializable工具(JDK)(也可以使用Protobuf自行百度)
* @author dolyw.com
* @date 2018/9/4 15:13
*/
public class SerializableUtil {
/**
* 序列化
* @param object
* @return byte[]
* @author dolyw.com
* @date 2018/9/4 15:14
*/
public static byte[] serializable(Object object) {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
return bytes;
} catch (IOException e) {
e.printStackTrace();
throw new CustomException("SerializableUtil工具类序列化出现IOException异常");
} finally {
try {
if(oos != null) {
oos.close();
}
if(baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 反序列化
* @param bytes
* @return java.lang.Object
* @author dolyw.com
* @date 2018/9/4 15:14
*/
public static Object unserializable(byte[] bytes) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new CustomException("SerializableUtil工具类反序列化出现ClassNotFoundException异常");
} catch (IOException e) {
e.printStackTrace();
throw new CustomException("SerializableUtil工具类反序列化出现IOException异常");
} finally {
try {
if(ois != null) {
ois.close();
}
if(bais != null) {
bais.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- StringUtil(JedisUtil用到)
public class StringUtil {
/**
* 定义下划线
*/
private static final char UNDERLINE = '_';
/**
* String为空判断(不允许空格)
* @param str
* @return boolean
* @author dolyw.com
* @date 2018/9/4 14:49
*/
public static boolean isBlank(String str) {
return str == null || "".equals(str.trim());
}
/**
* String不为空判断(不允许空格)
* @param str
* @return boolean
* @author dolyw.com
* @date 2018/9/4 14:51
*/
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
/**
* Byte数组为空判断
* @param bytes
* @return boolean
* @author dolyw.com
* @date 2018/9/4 15:39
*/
public static boolean isNull(byte[] bytes){
// 根据byte数组长度为0判断
return bytes.length == 0 || bytes == null;
}
/**
* Byte数组不为空判断
* @param bytes
* @return boolean
* @author dolyw.com
* @date 2018/9/4 15:41
*/
public static boolean isNotNull(byte[] bytes) {
return !isNull(bytes);
}
/**
* 驼峰转下划线工具
* @param param
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 14:52
*/
public static String camelToUnderline(String param) {
if (isNotBlank(param)) {
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; ++i) {
char c = param.charAt(i);
if (Character.isUpperCase(c)) {
sb.append(UNDERLINE);
sb.append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
} else {
return "";
}
}
/**
* 下划线转驼峰工具
* @param param
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 14:52
*/
public static String underlineToCamel(String param) {
if (isNotBlank(param)) {
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; ++i) {
char c = param.charAt(i);
if (c == 95) {
++i;
if (i < len) {
sb.append(Character.toUpperCase(param.charAt(i)));
}
} else {
sb.append(c);
}
}
return sb.toString();
} else {
return "";
}
}
/**
* 在字符串两周添加''
* @param param
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 14:53
*/
public static String addSingleQuotes(String param) {
return "\'" + param + "\'";
}
}
# 2.2. 重写Cache
重写Shiro的Cache保存读取和Shiro的Cache管理器
- CustomCache.java(Cache保存读取)
import com.wang.util.JWTUtil;
import com.wang.util.JedisUtil;
import com.wang.util.SerializableUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import java.util.*;
/**
* 重写Shiro的Cache保存读取
* @author dolyw.com
* @date 2018/9/4 17:31
*/
public class CustomCache<K,V> implements Cache<K,V> {
/**
* redis-key-前缀-shiro:cache:
*/
public final static String PREFIX_SHIRO_CACHE = "shiro:cache:";
/**
* 过期时间-5分钟
*/
private static final Integer EXPIRE_TIME = 5 * 60 * 1000;
/**
* 缓存的key名称获取为shiro:cache:account
* @param key
* @return java.lang.String
* @author dolyw.com
* @date 2018/9/4 18:33
*/
private String getKey(Object key){
return PREFIX_SHIRO_CACHE + JWTUtil.getUsername(key.toString());
}
/**
* 获取缓存
*/
@Override
public Object get(Object key) throws CacheException {
if(!JedisUtil.exists(this.getKey(key))){
return null;
}
return JedisUtil.getObject(this.getKey(key));
}
/**
* 保存缓存
*/
@Override
public Object put(Object key, Object value) throws CacheException {
// 设置Redis的Shiro缓存
return JedisUtil.setObject(this.getKey(key), value, EXPIRE_TIME);
}
/**
* 移除缓存
*/
@Override
public Object remove(Object key) throws CacheException {
if(!JedisUtil.exists(this.getKey(key))){
return null;
}
JedisUtil.delKey(this.getKey(key));
return null;
}
/**
* 清空所有缓存
*/
@Override
public void clear() throws CacheException {
JedisUtil.getJedis().flushDB();
}
/**
* 缓存的个数
*/
@Override
public int size() {
Long size = JedisUtil.getJedis().dbSize();
return size.intValue();
}
/**
* 获取所有的key
*/
@Override
public Set keys() {
Set<byte[]> keys = JedisUtil.getJedis().keys(new String("*").getBytes());
Set<Object> set = new HashSet<Object>();
for (byte[] bs : keys) {
set.add(SerializableUtil.unserializable(bs));
}
return set;
}
/**
* 获取所有的value
*/
@Override
public Collection values() {
Set keys = this.keys();
List<Object> values = new ArrayList<Object>();
for (Object key : keys) {
values.add(JedisUtil.getObject(this.getKey(key)));
}
return values;
}
}
- CustomCacheManager.java(缓存(Cache)管理器)
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
/**
* 重写Shiro缓存管理器
* @author dolyw.com
* @date 2018/9/4 17:41
*/
public class CustomCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return new CustomCache<K,V>();
}
}
最后在Shiro的配置Bean里设置我们重写的缓存(Cache)管理器
/**
* 配置使用自定义Realm,关闭Shiro自带的session
* 详情见文档 http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
* @param userRealm
* @return org.apache.shiro.web.mgt.DefaultWebSecurityManager
* @author dolyw.com
* @date 2018/8/31 10:55
*/
@Bean("securityManager")
public DefaultWebSecurityManager getManager(UserRealm userRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 使用自定义Realm
manager.setRealm(userRealm);
// 关闭Shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
// 设置自定义缓存(Cache)管理器
manager.setCacheManager(new CustomCacheManager());
return manager;
}
# 3. 最后测试
OK,我们现在可以在 Realm 的 doGetAuthorizationInfo() 方法打断点看下请求第一次后 Redis 多了一条缓存数据,下次就不会再调用 doGetAuthorizationInfo() 方法,除非缓存失效
当前博客源码:https://download.csdn.net/download/wang926454/10726052 (opens new window)
我的项目地址
- Github:https://github.com/wang926454/ShiroJwt (opens new window)
- Gitee(码云):https://gitee.com/dolyw/ShiroJwt (opens new window)
参考