当我们开发的程序需要跑在多个tomcat容器或者多台机器上时,shiro的默认session存储就不能满足我们的需求了,其中shiro默认的session是存储在运行jvm内存中的,使用的AbstractSessionDAO抽象类的一个子类MemorySessionDAO,当我们需要做分布式session,可以将session存储在redis上,即只要重写AbstractSessionDAO抽象类,重写里面的方法。
将上一篇的博客添加如下几个类(SerializeUtils、RedisManager、RedisSessionDao),添加的一个配置文件(applicationContext-redis.xml) ,修改了的配置文件(applicationContext-shiro.xml、pom.xml)。
现在工程结构:
一个序列化工具类SerializeUtils:
/*** 序列化工具类*/
public class SerializeUtils {private static final JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();/*** 序列化对象** @param obj* @return*/public static <T> byte[] serialize(T obj) {try {return jdkSerializationRedisSerializer.serialize(obj);} catch (Exception e) {throw new RuntimeException("序列化失败!", e);}}/*** 反序列化对象** @param bytes 字节数组* @return*/@SuppressWarnings("unchecked")public static <T> T deserialize(byte[] bytes) {try {return (T) jdkSerializationRedisSerializer.deserialize(bytes);} catch (Exception e) {throw new RuntimeException("反序列化失败!", e);}}
}
redisManager类:
public class RedisManager {@Autowiredprivate RedisTemplate<Serializable, Serializable> redisTemplate;/*** 过期时间*/
// private Long expire;/*** 添加缓存数据(给定key已存在,进行覆盖)** @param key* @param obj* @throws DataAccessException*/public <T> void set(String key, T obj) throws DataAccessException {final byte[] bkey = key.getBytes();final byte[] bvalue = SerializeUtils.serialize(obj);redisTemplate.execute(new RedisCallback<Void>() {@Overridepublic Void doInRedis(RedisConnection connection) throws DataAccessException {connection.set(bkey, bvalue);return null;}});}/*** 添加缓存数据(给定key已存在,不进行覆盖,直接返回false)** @param key* @param obj* @return 操作成功返回true,否则返回false* @throws DataAccessException*/public <T> boolean setNX(String key, T obj) throws DataAccessException {final byte[] bkey = key.getBytes();final byte[] bvalue = SerializeUtils.serialize(obj);boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {return connection.setNX(bkey, bvalue);}});return result;}/*** 添加缓存数据,设定缓存失效时间** @param key* @param obj* @param expireSeconds 过期时间,单位 秒* @throws DataAccessException*/public <T> void setEx(String key, T obj, final long expireSeconds) throws DataAccessException {final byte[] bkey = key.getBytes();final byte[] bvalue = SerializeUtils.serialize(obj);redisTemplate.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {connection.setEx(bkey, expireSeconds, bvalue);return true;}});}/*** 获取key对应value** @param key* @return* @throws DataAccessException*/public <T> T get(final String key) throws DataAccessException {byte[] result = redisTemplate.execute(new RedisCallback<byte[]>() {@Overridepublic byte[] doInRedis(RedisConnection connection) throws DataAccessException {return connection.get(key.getBytes());}});if (result == null) {return null;}return SerializeUtils.deserialize(result);}/*** 删除指定key数据** @param key* @return 返回操作影响记录数*/public Long del(final String key) {if (StringUtils.isEmpty(key)) {return 0l;}Long delNum = redisTemplate.execute(new RedisCallback<Long>() {@Overridepublic Long doInRedis(RedisConnection connection) throws DataAccessException {byte[] keys = key.getBytes();return connection.del(keys);}});return delNum;}public Set<byte[]> keys(final String key) {if (StringUtils.isEmpty(key)) {return null;}Set<byte[]> bytesSet = redisTemplate.execute(new RedisCallback<Set<byte[]>>() {@Overridepublic Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {byte[] keys = key.getBytes();return connection.keys(keys);}});return bytesSet;}}
自定义的sessionDao:
public class RedisSessionDao extends AbstractSessionDAO {private Logger logger = LoggerFactory.getLogger(this.getClass());private RedisManager redisManager;/*** The Redis key prefix for the sessions*/private static final String KEY_PREFIX = "shiro_redis_session:";@Overridepublic void update(Session session) throws UnknownSessionException {this.saveSession(session);}@Overridepublic void delete(Session session) {if (session == null || session.getId() == null) {logger.error("session or session id is null");return;}redisManager.del(KEY_PREFIX + session.getId());}@Overridepublic Collection<Session> getActiveSessions() {Set<Session> sessions = new HashSet<Session>();Set<byte[]> keys = redisManager.keys(KEY_PREFIX + "*");if (keys != null && keys.size() > 0) {for (byte[] key : keys) {Session s = (Session) SerializeUtils.deserialize(redisManager.get(SerializeUtils.deserialize(key)));sessions.add(s);}}return sessions;}@Overrideprotected Serializable doCreate(Session session) {Serializable sessionId = this.generateSessionId(session);this.assignSessionId(session, sessionId);this.saveSession(session);return sessionId;}@Overrideprotected Session doReadSession(Serializable sessionId) {if (sessionId == null) {logger.error("session id is null");return null;}Session s = (Session) redisManager.get(KEY_PREFIX + sessionId);return s;}private void saveSession(Session session) throws UnknownSessionException {if (session == null || session.getId() == null) {logger.error("session or session id is null");return;}//设置过期时间long expireTime = 1800000l;session.setTimeout(expireTime);redisManager.setEx(KEY_PREFIX + session.getId(), session, expireTime);}public void setRedisManager(RedisManager redisManager) {this.redisManager = redisManager;}public RedisManager getRedisManager() {return redisManager;}
添加redis的配置 applicationContext-redis.xml:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-3.0.xsd"><bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="maxIdle" value="1"/><property name="maxTotal" value="5"/><property name="blockWhenExhausted" value="true"/><property name="maxWaitMillis" value="30000"/><property name="testOnBorrow" value="true"/></bean><bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><property name="hostName" value="192.168.80.123"/><property name="port" value="6379"/><property name="poolConfig" ref="jedisPoolConfig"/><property name="usePool" value="true"/></bean><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"><property name="connectionFactory" ref="jedisConnectionFactory"/><property name="keySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property><property name="valueSerializer"><bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/></property><property name="hashKeySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property><property name="hashValueSerializer"><bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/></property></bean></beans>
pom添加:
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>1.6.2.RELEASE</version></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency>
修改applicationContext-shiro.xml文件,在会话管理器注入自定义的redisSessionDao
<!-- 会话管理器 --><bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"><property name="sessionDAO" ref="redisSessionDao"/><!-- session的失效时长,单位毫秒 --><property name="globalSessionTimeout" value="600000"/><!-- 删除失效的session --><property name="deleteInvalidSessions" value="true"/></bean>
redisSessionDao注入配置:
<bean id="redisSessionDao" class="com.lijie.shiro.RedisSessionDao"><property name="redisManager" ref="redisManager"/></bean>
以及redisSessionDao依赖的RedisManager注入
<bean id="redisManager" class="com.lijie.redis.RedisManager"></bean>
以后session的序列化和反序列化就不会走shiro默认的单机内存,而是走redis,这样就实现了分布式session共享。