shiro shares a CacheManger with spring, using redis

Posted by jbalanski on Fri, 06 Sep 2019 13:50:30 +0200

1 Background

Most projects now use caching basically. The author encountered this problem when integrating shiro+spring. It is also through consulting data that shiro and spring share spring's CacheManger.
The cache managers included in Shiro are org.apache.shiro.cache.Cache Manager and org.apache.shiro.cache.Cache.
Spring's own cache managers are org. spring framework. cache. Cache Manager and org. spring framework. cache. Cache

Of course, if you want to implement shiro and spring's cache managers separately, this is also a way of thinking. But what we need to talk about in this article is that they share a common cache manager. The purpose of doing this is to change the implementation only once if different caches are replaced.

2 Implementation

The implementation here lets shiro use spring's Cache Manager
Firstly, we build a class to implement shiro's CacheManager. For example, we build EndShiroCacheManager class. The code is as follows:

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 Custom shiro cache manager
 * @date 2019/9/4 19:50
 */
public class EndShiroCacheManager implements CacheManager {

    //Define spring's Cache Manager
    private org.springframework.cache.CacheManager springCacheManager;

    /**
     * Implementing the method in shiro's Cache Manager,
     * The Cache returned here is, of course, shiro's Cache.
     * @param name
     * @param <K>
     * @param <V>
     * @return
     * @throws CacheException
     */
    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        //Here EndShiroCache is the implementation class of shiro's 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;
    }
}

It is also necessary to implement shiro's Cache. The idea is to call spring's Cache to implement the method in shiro's Cache. For example, the author has built EndShiroCache class, the code is as follows:

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 Custom shiro cache
 * @date 2019/9/4 19:37
 */
@Slf4j
public class EndShiroCache<K, V>  implements org.apache.shiro.cache.Cache<K, V> {

    //Prefix of key in cache
    private String cachePrefix = "shiro:cache:";

    //Define spring's cache manager
    private CacheManager springCacheManager;
    //Define spring's cache
    private Cache cache;

    /**
     * Processing key s in cache with prefix
     * @param k
     * @return
     */
    private K getKey(K k) {
        return (K) (cachePrefix + (k==null?"*":k));
    }

    /**
     * Initialization of spring's Cache Manager and Cache through constructive methods
     * @param name
     * @param springCacheManager
     */
    public EndShiroCache(String name, CacheManager springCacheManager) {
        this.springCacheManager = springCacheManager;
        this.cache = springCacheManager.getCache(name);
    }

    /**
     * get method in ache of shiro
     * @param k
     * @return
     * @throws CacheException
     */
    @Override
    public V get(K k) throws CacheException {
        log.warn("Get from the cache key: {}", k);
        //Call spring's Cache get method
        Cache.ValueWrapper valueWrapper = cache.get(getKey(k));
        if (valueWrapper == null) {
            return null;
        }
        return (V) valueWrapper.get();
    }

    /**
     * put method in ache of shiro
     * @param k
     * @param v
     * @return
     * @throws CacheException
     */
    @Override
    public V put(K k, V v) throws CacheException {
        log.warn("take key: {}Store in Cache", k);
        //Calling spring's Cache put method
        cache.put(getKey(k), v);
        return v;
    }

    /**
     * remove method in ache of shiro
     * @param k
     * @return
     * @throws CacheException
     */
    @Override
    public V remove(K k) throws CacheException {
        log.warn("take key: {}Delete from cache", k);
        V v = get(k);
        //Calling spring's Cache evict method
        cache.evict(getKey(k));
        return v;
    }

    /**
     * clear method in ache of shiro
     * @throws CacheException
     */
    @Override
    public void clear() throws CacheException {
        log.warn("empty name:{}Caching", cache.getName());
        //Call spring Cache's clear method
        cache.clear();
    }

    /**
     * The size method in ache of shiro
     * @return
     */
    @Override
    public int size() {
        int size = keys().size();
        log.warn("Obtain name: {}Of cache Of size: {}", cache.getName(), size);
        return size;
    }

    /**
     * The keys method in shiro's Cache
     * @return
     */
    @Override
    public Set<K> keys() {
        //Calling spring's CacheManager getCacheNames method
        return (Set<K>) springCacheManager.getCacheNames();
    }

    /**
     * values method in ache of shiro
     * @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;
    }
}

This is the main code for implementation.

3 Use

After realizing the above main code, how to use it, the author takes his own project as an example:
Because I use the spring boot framework, I define the relevant beans in the configuration and inject spring Cache Manager into EndShiro Cache Manager.

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 registers bean s in the following code
    @Bean
    public EndShiroCacheManager endShiroCacheManager(CacheManager cacheManager) {
        EndShiroCacheManager endShiroCacheManager = new EndShiroCacheManager();
        endShiroCacheManager.setSpringCacheManager(cacheManager);
        return endShiroCacheManager;
    }
}

The author uses redis as a cache, so there is redis related content in the code. The specific configuration is as follows:

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 Automatic Configuration Class
 * @date 2019/8/22 2:00
 */
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RdisAutoConfiguration {

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

        //Serialization configuration
        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;
    }

    /**
     * Register Cache Manager
     * @param redisTemplate
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisTemplate(redisTemplate);
        return redisCacheManager;
    }
}

The above is almost how to use it, and then the reader will implement spring's Cache Manager and Cache. The author attaches his own implementation, which is not specified in detail. The code is as follows:

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;

/**
 * Custom redis cache manager
 */
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

    private RedisTemplate redisTemplate;

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

    /**
     * Load Caches Method for Implementing AbstractCache Manager
     * @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;
    }

    /**
     * Rewriting getMissingCache Method of AbstractCache Manager
     * @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;// Injecting the object through a constructive method
    private String name; //Cache name
    private long expire; //timeout

    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());
        }
    }
}

Okay, that's it. The above is just what I read more about to integrate and personally test.
https://www.itapes.cn

Topics: Shiro Spring Redis Java