随记——shiro+redis分布式session共享—老项目改造

一、环境

  • redis集群,客户端使用JedisCluster
  • shiro + ssm项目

二、代码

1. 继承AbstractSessionDAO,实现相关方法,可以参照其他的实现去写,如:MemorySessionDAO

package com.sunline.webins.shiro;

import com.sunline.j2cache.util.SerializationUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;
/**
 * @author Administrator
 */
public class RedisSessionDao extends AbstractSessionDAO {
    private JedisCluster jedisCluster;
    /**
    * 登录过期时间默认一小时
    **/
    private final int EXPIRE_TIME = 60*60;
    private final String SHIRO_SESSION_PRI = "usersession:";
    public JedisCluster getJedisCluster() {
        return jedisCluster;
    }

    public void setJedisCluster(JedisCluster jedisCluster) {
        this.jedisCluster = jedisCluster;
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        //	键值
        byte[] bKes  = (SHIRO_SESSION_PRI + sessionId).getBytes();
        //	对象值(序列号)
        try {
            byte [] bValue = SerializationUtils.serialize (session);
            //jedisCluster不支持对象类型的数据,因此将session对象序列化后存入redis,切记不能转为json,一个大坑
            jedisCluster.set(bKes, bValue);
            jedisCluster.expire(bKes, EXPIRE_TIME);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            throw new NullPointerException("id argument cannot be null.");
        }
        Session session = null;
        byte []bKes  = (SHIRO_SESSION_PRI + sessionId).getBytes();
        //	获取缓存数据
        byte []resultBytes = jedisCluster.get(bKes);
        //	对象值(序列号)
        try {
            session = (Session) SerializationUtils.deserialize (resultBytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return session;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        String sessionId  = (String) session.getId();
        if (sessionId == null) {
            throw new NullPointerException("id argument cannot be null.");
        }

        //	键值
        byte[] bKes  = (SHIRO_SESSION_PRI + sessionId).getBytes();
        //	对象值(序列号)
        try {
            byte [] bValue = SerializationUtils.serialize (session);
            jedisCluster.set(bKes, bValue);
            jedisCluster.expire(bKes, EXPIRE_TIME);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void delete(Session session) {
        if (session == null) {
            throw new NullPointerException("session argument cannot be null.");
        } else {
            String sessionId  = (String) session.getId();
            if (sessionId != null) {
                byte[] bKes  = (SHIRO_SESSION_PRI + sessionId).getBytes();
                jedisCluster.del(bKes);
            }
        }
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<Session> sessionSet = new HashSet<>();
        //redis集群不支持keys查询,因此需要遍历所有集群节点,分别获取keys,这个效率比较低,而且会阻塞
        Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
        for (String s : clusterNodes.keySet()) {
            JedisPool jedisPool = clusterNodes.get(s);
            //key前缀转为byte[]依然可以查到需要的数据,就很神奇
            Set<byte[]> keys = jedisPool.getResource().keys((SHIRO_SESSION_PRI + "*").getBytes());
            for (byte[] k : keys) {
                byte[] bytes = jedisPool.getResource().get(k);
                try {
                    Session session = (Session) SerializationUtils.deserialize (bytes);
                    sessionSet.add(session);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return sessionSet;
    }
}

2. 一次请求会多次调用doReadSession,给redis造成很大压力,因此做如下改造,减小redis的压力

package com.sunline.webins.shiro;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;
import javax.servlet.ServletRequest;

import java.io.Serializable;
//原来使用的是DefaultWebSessionManager,因此直接继承重写方法即可
public class RedisWebSessionManager extends DefaultWebSessionManager {

    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = this.getSessionId(sessionKey);
        if (sessionId == null) {
            return null;
        } else {
            WebSessionKey webSessionKey = (WebSessionKey) sessionKey;
            ServletRequest servletRequest = webSessionKey.getServletRequest();
            //从request中取值,如果取到就直接返回,减少访问redis的频率
            Session session = (Session) servletRequest.getAttribute(sessionId + "");
            if (session != null) {
                return session;
            }
            //这里会去调用doReadSession方法
            session = this.retrieveSessionFromDataSource(sessionId);
            if (session == null) {
                String msg = "Could not find session with ID [" + sessionId + "]";
                throw new UnknownSessionException(msg);
            } else {
                //首次调用时将session放入request中
                servletRequest.setAttribute(sessionId + "", session);
                return session;
            }
        }
    }
}

3. 修改配置文件进行注入,这里只是我修改的部分,不是全部配置

    <bean id="sessionDAO" class="com.sunline.webins.shiro.RedisSessionDao">
		<property name="jedisCluster" ref="jedisCluster"></property>
	</bean>
	<bean id="sessionManager" class="com.sunline.webins.shiro.RedisWebSessionManager">
	<!-- 注入sessionDAO -->
		<property name="sessionDAO" ref="sessionDAO"></property>
	</bean>
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="authenticationRealm" />
		<property name="cacheManager" ref="shiroCacheManager" />
		<!-- DefaultWebSessionManager 改为 RedisWebSessionManager -->
		<property name="sessionManager" ref="sessionManager"></property>
	</bean>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值