在 手撸 Java Web RBAC 权限管理 中,我们自己实现了一个简易的 RBAC 权限管理框架,且我们也提到了一些缺陷,其中一点就是 : 每次请求需要授权的页面都会去数据库查询此用户对应的权限数据和角色数据,太耗费资源,应该进行缓存。
本章我们就来讲讲如何将 Shiro 中的授权数据缓存到 Redis 中。
Shiro 为授权数据的缓存提供了两个借口,一个是 CacheManager
,一个是 Cache
。
根据这两个接口,我们完全可以将授权数据缓存到任何地方,包括 redis
、ehcache
、内存等。
既然我们要缓存到 Redis 中,我们需要搭建 Redis 环境,并导入 Redis 工具类:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
新建配置文件 spring-redis.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<!-- Jedis 配置信息 -->
<constructor-arg name="poolConfig" ref="jedisPoolConfig"/>
<!-- Redis URL -->
<constructor-arg name="host" value="127.0.0.1"/>
<!-- Redis 端口-->
<constructor-arg name="port" value="6379"/>
<!-- Redis 密码 -->
<!--<constructor-arg value=""/>-->
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="500"/>
<!-- 最大闲置 -->
<property name="maxIdle" value="100"/>
<!-- 最小闲置 -->
<property name="minIdle" value="10"/>
<!-- 最大等待 -->
<property name="maxWaitMillis" value="5000"/>
<!-- 可以获取 -->
<property name="testOnBorrow" value="true"/>
</bean>
</beans>
我们来创建一个 RedisCache
继承自 org.apache.shiro.cache.Cache
,来实现它的方法:
package im.zhaojun.cache;
import im.zhaojun.util.JedisUtil;
import org.apache.log4j.Logger;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Set;
@Component
public class RedisCache<K, V> implements Cache<K, V> {
private static final Logger logger = Logger.getLogger(RedisCache.class);
@Resource
private JedisUtil jedisUtil;
private final String CACHE_PREFIX = "shiro-cache:";
private byte[] getKeyBytes(K k) {
return (CACHE_PREFIX + k).getBytes();
}
@Override
public V get(K k) throws CacheException {
logger.info("从 Redis 中读取授权信息...");
byte[] key = getKeyBytes(k);
byte[] value = jedisUtil.get(key);
if (value != null) {
return (V) SerializationUtils.deserialize(value);
}
return null;
}
@Override
public V put(K k, V v) throws CacheException {
byte[] key = getKeyBytes(k);
byte[] value = SerializationUtils.serialize(v);
jedisUtil.set(key, value);
jedisUtil.expire(key, 600);
return v;
}
@Override
public V remove(K k) throws CacheException {
byte[] key = getKeyBytes(k);
byte[] value = jedisUtil.get(key);
jedisUtil.del(key);
if (value != null) {
SerializationUtils.deserialize(value);
}
return null;
}
@Override
public void clear() throws CacheException {
jedisUtil.delKeysByPrefix(CACHE_PREFIX);
}
@Override
public int size() {
return jedisUtil.getKeysByPrefix(CACHE_PREFIX).size();
}
@Override
public Set<K> keys() {
return (Set<K>) jedisUtil.getKeysByPrefix(CACHE_PREFIX);
}
@Override
public Collection<V> values() {
return jedisUtil.getValuesByPrefix(CACHE_PREFIX);
}
}
其中没什么难点,只是对 redis 的基本增删改查操作,由于是存储到 redis 中,所以我们为缓存数据的 key 添加了前缀,以便再次获取。
我们创建一个 RedisCacheManager
类来继承自 org.apache.shiro.cache.AbstractCacheManager
,当然你也可以直接继承自 org.apache.shiro.cacheCacheManager
:
package im.zhaojun.cache;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.AbstractCacheManager;
import javax.annotation.Resource;
@Component
public class RedisCacheManager extends AbstractCacheManager {
@Resource
private RedisCache redisCache;
@Override
protected Cache createCache(String s) throws CacheException {
return redisCache;
}
}
这里在 createCache()
方法中返回我们的自定义 RedisCache
对象即可。
然后我们将 RedisCacheManager
配置到 securityManager
中:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<property name="cacheManager" ref="redisCacheManager"/>
</bean>
以及将 spring-redis.xml
配置到 web.xml
中:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring.xml,
classpath:spring-shiro.xml,
classpath:spring-redis.xml
</param-value>
</context-param>
然后我们分别在 Realm
的 doGetAuthorizationInfo
方法和 RedisCache
的 get
方法中分别打印一条日志,看何时会访问数据库,何时会访问 Redis
缓存的数据。
其实频繁从 Redis 中读取也是比较浪费资源的, Redis 的连接同样宝贵,最好的办法还是直接存储在内存中,但也是各有利弊,需要根据实际项目来决定使用哪种方案。
放到 Redis 的好处是:可以用来做跨项目/机器的数据缓存,可以集群,持久化等。 放到内存的好处是:速度快,使用方便快捷。 但使用这种缓存还有一个比较重要的事情,就是当数据库中的授权数据发生修改时,也要记得刷新缓存中的数据,不然会出现数据错乱,实现方式可以通过直接覆盖缓存,消息队列通知等方式,需要根据不同项目来选区不同方式,由于篇幅原因这里不再展开讲了。
本章代码地址 : https://github.com/zhaojun1998/Premission-Study/tree/master/Permission-Shiro-08/