Shiro使用Redis作为缓存(解决Shiro频繁访问Redis)

2022-08-14 09:26:40

原文:https://blog.csdn.net/qq_34021712/article/details/80791219

之前写过一篇博客,使用的一个开源项目,实现了redis作为缓存 缓存用户的权限 和 session信息,还有两个功能没有修改,一个是用户并发登录限制,一个是用户密码错误次数.本篇中几个类 也是使用的开源项目中的类,只不过是拿出来了,redis单独做的配置,方便进行优化。

整合过程

1.首先是整合Redis

Redis客户端使用的是RedisTemplate,自己写了一个序列化工具继承RedisSerializer

SerializeUtils.java

package com.springboot.test.shiro.global.utils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.SerializationException;import java.io.*;/**
 * @author: wangsaichao
 * @date: 2018/6/20
 * @description: redis的value序列化工具
 */publicclassSerializeUtilsimplementsRedisSerializer{privatestatic Logger logger= LoggerFactory.getLogger(SerializeUtils.class);publicstaticbooleanisEmpty(byte[] data){return(data== null|| data.length==0);}/**
     * 序列化
     * @param object
     * @return
     * @throws SerializationException
     */@Overridepublicbyte[]serialize(Object object)throws SerializationException{byte[] result= null;if(object== null){returnnewbyte[0];}try(
                ByteArrayOutputStream byteStream=newByteArrayOutputStream(128);
                ObjectOutputStream objectOutputStream=newObjectOutputStream(byteStream)){if(!(objectinstanceofSerializable)){thrownewIllegalArgumentException(SerializeUtils.class.getSimpleName()+" requires a Serializable payload "+"but received an object of type ["+ object.getClass().getName()+"]");}

            objectOutputStream.writeObject(object);
            objectOutputStream.flush();
            result=  byteStream.toByteArray();}catch(Exception ex){
            logger.error("Failed to serialize",ex);}return result;}/**
     * 反序列化
     * @param bytes
     * @return
     * @throws SerializationException
     */@Overridepublic Objectdeserialize(byte[] bytes)throws SerializationException{

        Object result= null;if(isEmpty(bytes)){return null;}try(
                ByteArrayInputStream byteStream=newByteArrayInputStream(bytes);
                ObjectInputStream objectInputStream=newObjectInputStream(byteStream)){
            result= objectInputStream.readObject();}catch(Exception e){
            logger.error("Failed to deserialize",e);}return result;}}

RedisConfig.java

package com.springboot.test.shiro.config;import com.springboot.test.shiro.global.utils.SerializeUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;import redis.clients.jedis.JedisPoolConfig;/**
 * @author: wangsaichao
 * @date: 2017/11/23
 * @description: redis配置
 */@ConfigurationpublicclassRedisConfig{/**
     * redis地址
     */@Value("${spring.redis.host}")private String host;/**
     * redis端口号
     */@Value("${spring.redis.port}")private Integer port;/**
     * redis密码
     */@Value("${spring.redis.password}")private String password;/**
     * JedisPoolConfig 连接池
     * @return
     */@Beanpublic JedisPoolConfigjedisPoolConfig(){
        JedisPoolConfig jedisPoolConfig=newJedisPoolConfig();//最大空闲数
        jedisPoolConfig.setMaxIdle(300);//连接池的最大数据库连接数
        jedisPoolConfig.setMaxTotal(1000);//最大建立连接等待时间
        jedisPoolConfig.setMaxWaitMillis(1000);//逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
        jedisPoolConfig.setMinEvictableIdleTimeMillis(300000);//每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
        jedisPoolConfig.setNumTestsPerEvictionRun(10);//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);//是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
        jedisPoolConfig.setTestOnBorrow(true);//在空闲时检查有效性, 默认false
        jedisPoolConfig.setTestWhileIdle(true);return jedisPoolConfig;}/**
     * 配置工厂
     * @param jedisPoolConfig
     * @return
     */@Beanpublic JedisConnectionFactoryjedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
        JedisConnectionFactory jedisConnectionFactory=newJedisConnectionFactory();//连接池
        jedisConnectionFactory.setPoolConfig(jedisPoolConfig);//IP地址
        jedisConnectionFactory.setHostName(host);//端口号
        jedisConnectionFactory.setPort(port);//如果Redis设置有密码
        jedisConnectionFactory.setPassword(password);//客户端超时时间单位是毫秒
        jedisConnectionFactory.setTimeout(5000);return jedisConnectionFactory;}/**
     * shiro redis缓存使用的模板
     * 实例化 RedisTemplate 对象
     * @return
     */@Bean("shiroRedisTemplate")public RedisTemplateshiroRedisTemplate(RedisConnectionFactory redisConnectionFactory){

        RedisTemplate redisTemplate=newRedisTemplate();
        redisTemplate.setKeySerializer(newStringRedisSerializer());
        redisTemplate.setHashKeySerializer(newStringRedisSerializer());
        redisTemplate.setHashValueSerializer(newSerializeUtils());
        redisTemplate.setValueSerializer(newSerializeUtils());//开启事务//stringRedisTemplate.setEnableTransactionSupport(true);
        redisTemplate.setConnectionFactory(redisConnectionFactory);return redisTemplate;}}

RedisManager.java

package com.springboot.test.shiro.config.shiro;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.dao.DataAccessException;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.core.*;import org.springframework.util.CollectionUtils;import java.util.*;import java.util.concurrent.TimeUnit;/**
 *
 * @author wangsaichao
 * 基于spring和redis的redisTemplate工具类
 */publicclassRedisManager{@Autowiredprivate RedisTemplate<String, Object> redisTemplate;//=============================common============================/**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     */publicvoidexpire(String key,long time){
        redisTemplate.expire(key, time, TimeUnit.SECONDS);}/**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */public BooleanhasKey(String key){return redisTemplate.hasKey(key);}/**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */@SuppressWarnings("unchecked")publicvoiddel(String... key){if(key!=null&&key.length>0){if(key.length==1){
                redisTemplate.delete(key[0]);}else{
                redisTemplate.delete(CollectionUtils.arrayToList(key));}}}/**
     * 批量删除key
     * @param keys
     */publicvoiddel(Collection keys){
        redisTemplate.delete(keys);}//============================String=============================/**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */public Objectget(String key){return redisTemplate.opsForValue().get(key);}/**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     */publicvoidset(String key,Object value){
        redisTemplate.opsForValue().set(key, value);}/**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     */publicvoidset(String key,Object value,long time){if(time>0){
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);}else{set(key, value);}}/**
     * 使用scan命令 查询某些前缀的key
     * @param key
     * @return
     */public Set<String>scan(String key){
        Set<String> execute=this.redisTemplate.execute(newRedisCallback<Set<String>>(){@Overridepublic Set<String>doInRedis(RedisConnection connection)throws DataAccessException{

                Set<String> binaryKeys=newHashSet<>();

                Cursor<byte[]> cursor= connection.scan(newScanOptions.ScanOptionsBuilder().match(key).count(1000).build());while(cursor.hasNext()){
                    binaryKeys.add(newString(cursor.next()));}return binaryKeys;}});return execute;}/**
     * 使用scan命令 查询某些前缀的key 有多少个
     * 用来获取当前session数量,也就是在线用户
     * @param key
     * @return
     */public LongscanSize(String key){long dbSize=this.redisTemplate.execute(newRedisCallback<Long>(){@Overridepublic LongdoInRedis(RedisConnection connection)throws DataAccessException{long count=0L;
                Cursor<byte[]> cursor= connection.scan(ScanOptions.scanOptions().match(key).count(1000).build());while(cursor.hasNext()){
                    cursor.next();
                    count++;}return count;}});return dbSize;}}

2.使用Redis作为缓存需要shiro重写cache、cacheManager、SessionDAO

RedisCache.java

package com.springboot.test.shiro.config.shiro;import com.springboot.test.shiro.global.exceptions.PrincipalIdNullException;import com.springboot.test.shiro.global.exceptions.PrincipalInstanceException;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheException;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.util.CollectionUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.*;/**
 * @author: wangsaichao
 * @date: 2018/6/22
 * @description: 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis
 */publicclassRedisCache<K, V>implementsCache<K, V>{privatestatic Logger logger= LoggerFactory.getLogger(RedisCache.class);private RedisManager redisManager;private String keyPrefix="";privateint expire=0;private String principalIdFieldName= RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME;/**
     * Construction
     * @param redisManager
     */publicRedisCache(RedisManager redisManager, String prefix,int expire, String principalIdFieldName){if(redisManager== null){thrownewIllegalArgumentException("redisManager cannot be null.");}this.redisManager= redisManager;if(prefix!= null&&!"".equals(prefix)){this.keyPrefix= prefix;}if(expire!=-1){this.expire= expire;}if(principalIdFieldName!= null&&!"".equals(principalIdFieldName)){this.principalIdFieldName= principalIdFieldName;}}@Overridepublic Vget(K key)throws CacheException{
        logger.debug("get key [{}]",key);if(key== null){return null;}try{
            String redisCacheKey=getRedisCacheKey(key);
            Object rawValue= redisManager.get(redisCacheKey);if(rawValue== null){return null;}
            V value=(V) rawValue;return value;}catch(Exception e){thrownewCacheException(e);}}@Overridepublic Vput(K key, V value)throws CacheException{
        logger.debug("put key [{}]",key);if(key== null){
            logger.warn("Saving a null key is meaningless, return value directly without call Redis.");return value;}try{
            String redisCacheKey=getRedisCacheKey(key);
            redisManager.set(redisCacheKey, value!= null? value: null, expire);return value;}catch(Exception e){thrownewCacheException(e);}}@Overridepublic Vremove(K key)throws CacheException{
        logger.debug("remove key [{}]",key);if(key== null){return null;}try{
            String redisCacheKey=getRedisCacheKey(key);
            Object rawValue= redisManager.get(redisCacheKey);
            V previous=(V) rawValue;
            redisManager.del(redisCacheKey);return previous;}catch(Exception e){thrownewCacheException(e);}}private StringgetRedisCacheKey(K key){
  • 作者:来自大山深处的Doge_
  • 原文链接:https://blog.csdn.net/qq_45822970/article/details/109582568
    更新时间:2022-08-14 09:26:40