shiro与spring共用一个CacheManger,使用redis

1 背景

现在大多数的项目中基本都用到缓存,笔者在整合shiro+spring的时候遇到了这个问题,也是经查阅资料,使得shiro与spring共用spring的CacheManger。
shiro自带的缓存管理器为org.apache.shiro.cache.CacheManager以及org.apache.shiro.cache.Cache
spring自带的缓存管理器为org.springframework.cache.CacheManager以及org.springframework.cache.Cache

当然,如果想要分别去实现shiro和spring的缓存管理器,这个也是一种思路。但本文需要讲的的是两者公共一个缓存管理器,这样做的目的在于如果更换不同的缓存,只需将实现修改一次即可。

2 实现

这里实现的让shiro去使用spring的CacheManager
首先先建一个类去实现shiro的CacheManager,如笔者建了EndShiroCacheManager类,代码如下:

package cn.itapes.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;

/**
 * @author cj
 * @version 1.0
 * @description 自定义shiro缓存管理器
 * @date 2019/9/4 19:50
 */
public class EndShiroCacheManager implements CacheManager {

    //定义spring的CacheManager
    private org.springframework.cache.CacheManager springCacheManager;

    /**
     * 实现shiro的CacheManager中的方法,
     * 这里返回的Cache当然是shiro的Cache
     * @param name
     * @param <K>
     * @param <V>
     * @return
     * @throws CacheException
     */
    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        //这里EndShiroCache是shiro的Cache的实现类
        return new EndShiroCache<K, V>(name, getSpringCacheManager());
    }

    /**
     * set
     * @param springCacheManager
     */
    public void setSpringCacheManager(org.springframework.cache.CacheManager springCacheManager) {
        this.springCacheManager = springCacheManager;
    }

    /**
     * get
     * @return
     */
    public org.springframework.cache.CacheManager getSpringCacheManager() {
        return springCacheManager;
    }
}

同样也需要实现shiro的Cache,实现的思路是将shiro的Cahce中的方法通过调用spring的Cahce来实现,如笔者建了EndShiroCache类,代码如下:

package cn.itapes.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.cache.CacheException;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
 * @author cj
 * @version 1.0
 * @description 自定义shiro缓存
 * @date 2019/9/4 19:37
 */
@Slf4j
public class EndShiroCache<K, V>  implements org.apache.shiro.cache.Cache<K, V> {

    //cache中key的前缀
    private String cachePrefix = "shiro:cache:";

    //定义spring的缓存管理器
    private CacheManager springCacheManager;
    //定义spring的缓存
    private Cache cache;

    /**
     * 对cache中的key进行处理,加上前缀
     * @param k
     * @return
     */
    private K getKey(K k) {
        return (K) (cachePrefix + (k==null?"*":k));
    }

    /**
     * 通过构造方法来对spring的CacheManager和Cache进行初始化
     * @param name
     * @param springCacheManager
     */
    public EndShiroCache(String name, CacheManager springCacheManager) {
        this.springCacheManager = springCacheManager;
        this.cache = springCacheManager.getCache(name);
    }

    /**
     * 实现shiro的Cache中的get方法
     * @param k
     * @return
     * @throws CacheException
     */
    @Override
    public V get(K k) throws CacheException {
        log.warn("从缓存中获取key:{}", k);
        //调用spring的Cache的get方法
        Cache.ValueWrapper valueWrapper = cache.get(getKey(k));
        if (valueWrapper == null) {
            return null;
        }
        return (V) valueWrapper.get();
    }

    /**
     * 实现shiro的Cache中的put方法
     * @param k
     * @param v
     * @return
     * @throws CacheException
     */
    @Override
    public V put(K k, V v) throws CacheException {
        log.warn("将key:{}存入缓存", k);
        //调用spring的Cache的put方法
        cache.put(getKey(k), v);
        return v;
    }

    /**
     * 实现shiro的Cache中的remove方法
     * @param k
     * @return
     * @throws CacheException
     */
    @Override
    public V remove(K k) throws CacheException {
        log.warn("将key:{}从缓存中删除", k);
        V v = get(k);
        //调用spring的Cache的evict方法
        cache.evict(getKey(k));
        return v;
    }

    /**
     * 实现shiro的Cache中的clear方法
     * @throws CacheException
     */
    @Override
    public void clear() throws CacheException {
        log.warn("清空name:{}的缓存", cache.getName());
        //调用spring的Cache的clear方法
        cache.clear();
    }

    /**
     * 实现shiro的Cache中的size方法
     * @return
     */
    @Override
    public int size() {
        int size = keys().size();
        log.warn("获取name:{}的cache的size:{}", cache.getName(), size);
        return size;
    }

    /**
     * 实现shiro的Cache中的keys方法
     * @return
     */
    @Override
    public Set<K> keys() {
        //调用spring的CacheManager的getCacheNames方法
        return (Set<K>) springCacheManager.getCacheNames();
    }

    /**
     * 实现shiro的Cache中的values方法
     * @return
     */
    @Override
    public Collection<V> values() {
        List<V> list = new ArrayList<>();
        Set<K> keys = keys();
        for(K k : keys) {
            list.add(get(k));
        }
        return list;
    }
}

以上即为实现的主要代码

3 使用

实现了上述主要代码后,那么如何进行使用呢,笔者以自己的项目为例:
由于笔者使用的是springboot框架,在配置中定义了相关Bean以及往EndShiroCacheManager中注入spring的CacheManager

package cn.itapes.configuration;

import cn.itapes.shiro.EndShiroCacheManager;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EndShiroConfiguration {
    //CacheManager在下面代码中进行注册成bean
    @Bean
    public EndShiroCacheManager endShiroCacheManager(CacheManager cacheManager) {
        EndShiroCacheManager endShiroCacheManager = new EndShiroCacheManager();
        endShiroCacheManager.setSpringCacheManager(cacheManager);
        return endShiroCacheManager;
    }
}

笔者使用的是redis作为缓存,所以代码中存在redis的相关内容,具体配置如下:

package cn.itapes.configuration;

import cn.itapes.core.RedisCacheManager;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.shiro.session.mgt.SimpleSession;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author cj
 * @version 1.0
 * @description redis自动配置类
 * @date 2019/8/22 2:00
 */
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RdisAutoConfiguration {

    @Bean //注册bean
    @ConditionalOnMissingBean(name = "redisTemplate") //
    public <K, V> RedisTemplate<K, V> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<K, V> redisTemplate = new RedisTemplate<K, V>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        //序列化配置
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);

        redisTemplate.setKeySerializer(new StringRedisSerializer());
//        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(new EndRedisSerializer(jackson2JsonRedisSerializer, SimpleSession.class));
//        redisTemplate.setValueSerializer(new FastJsonRedisSerializer<>(Object.class));
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
//        redisTemplate.setHashValueSerializer(new EndRedisSerializer<>(Object.class));

        return redisTemplate;
    }

    /**
     * 注册缓存管理器
     * @param redisTemplate
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisTemplate(redisTemplate);
        return redisCacheManager;
    }
}

以上差不多是如何进行使用,然后读者再对spring的CacheManager和Cache进行实现下就好了,笔者附上自己的实现,具体就不说明了,代码如下:

RedisCacheManager.java

package cn.itapes.core;

import org.springframework.cache.Cache;
import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * 自定义redis缓存管理器
 */
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

    private RedisTemplate redisTemplate;

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 实现AbstractCacheManager的loadCaches方法
     * @return
     */
    @Override
    protected Collection<? extends Cache> loadCaches() {
        List<Cache> cacheList = new LinkedList<>();
        Set<String> set = redisTemplate.keys("*");
        if(set!=null && set.size()>0) {
            for(String key : set) {
                cacheList.add(new RedisCache(key, redisTemplate));
            }
        }
        return cacheList;
    }

    /**
     * 重写AbstractCacheManager的getMissingCache方法
     * @param name
     * @return
     */
    @Override
    protected Cache getMissingCache(String name) {
        return new RedisCache(name, redisTemplate);
    }
}

RedisCache.java

package cn.itapes.core;

import com.alibaba.fastjson.JSONObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.*;
import java.util.concurrent.Callable;

public class RedisCache<K, V> implements Cache {

    private Logger logger = LogManager.getLogger(getClass());

    private RedisTemplate<K, V> redisTemplate;// 通过构造方法注入该对象
    private String name; //缓存名称
    private long expire; //超时时间

    public RedisCache() {
        super();
    }

    public RedisCache(String name, RedisTemplate<K, V> redisTemplate) {
        super();
        this.name = name;
        this.redisTemplate = redisTemplate;
    }

    public RedisCache(RedisTemplate<K, V> redisTemplate) {
        super();
        this.redisTemplate = redisTemplate;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    public long getExpire() {
        return expire;
    }

    public void setExpire(long expire) {
        this.expire = expire;
    }

    @Override
    public Object getNativeCache() {
        return this.redisTemplate.opsForHash().entries((K) this.name);
    }

    @Override
    public synchronized ValueWrapper get(final Object key) {
        logger.info("RedisCache get: key = "+key);
        if(key == null) {
            return null;
        }
        Object object = redisTemplate.opsForHash().get((K) this.name, key);
        return object == null?null: new SimpleValueWrapper(object);
    }

    @Override
    public synchronized <T> T get(final Object key, Class<T> clazz) {
        if(key == null) {
            return null;
        }
        Object value = redisTemplate.opsForHash().get((K) this.name, key);
        if (value != null && value instanceof JSONObject) {
            return ((JSONObject) value).toJavaObject(clazz);
        }
        return (T) value;
    }

    @Override
    public synchronized <T> T get(final Object key, Callable<T> callable) {
        if(key == null) {
            return null;
        }
        Object object = redisTemplate.opsForHash().get((K) this.name, key);
        if(object!=null) {
            return (T) object;
        }
        try {
            object = callable.call();
        } catch (Exception e) {
            throw new ValueRetrievalException(key, callable, e);
        }

        redisTemplate.opsForHash().put((K) this.name, key, object);
        return (T) object;
    }

    @Override
    public synchronized void put(final Object key, final Object value) {
        logger.info("RedisCache put: key = "+key+", value = "+value);
        redisTemplate.opsForHash().put((K) this.name, key, value);
    }

    @Override
    public synchronized ValueWrapper putIfAbsent(Object key, Object value) {
        boolean flag = redisTemplate.opsForHash().putIfAbsent((K) this.name, key, value);
        if(flag) {
            return null;
        }
        return new SimpleValueWrapper(value);
    }

    @Override
    public synchronized void evict(final Object key) {
        logger.info("RedisCache evict: key = "+key);
        if(key == null) {
            return;
        }
        redisTemplate.opsForHash().delete((K) this.name, key);
    }

    @Override
    public synchronized void clear() {
        logger.info("RedisCache clear");
        Set<Object> set = redisTemplate.opsForHash().keys((K) this.name);
        if(set!=null && set.size()>0) {
            redisTemplate.opsForHash().delete((K) this.name, set.toArray());
        }
    }
}

好了,就这样了,以上只是笔者多查阅到的进行整合并亲测过的…
https://www.itapes.cn

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值