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