All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.iherus.shiro.cache.redis.RedisCache Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
/**
 * Copyright (c) 2016-2019, Bosco.Liao ([email protected]).
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.iherus.shiro.cache.redis;

import static org.iherus.shiro.cache.redis.Constant.DEFAULT_CACHE_EXPIRATION;
import static org.iherus.shiro.cache.redis.serializer.StringSerializer.UTF_8;
import static org.iherus.shiro.util.Utils.assertNotBlank;
import static org.iherus.shiro.util.Utils.assertNotNull;
import static org.iherus.shiro.util.Utils.invokeMethod;
import static org.iherus.shiro.util.Utils.isEmpty;
import static org.iherus.shiro.util.Utils.mergeAll;

import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.iherus.shiro.cache.redis.ExpiredCache.Named;
import org.iherus.shiro.cache.redis.connection.RedisConnection;
import org.iherus.shiro.util.Md5Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

缓存接口实现类

*

Description:实现Shiro缓存接口,提供集中式缓存对象的增删查改。

* * @author Bosco.Liao * @since 1.0.0 */ public class RedisCache implements Cache, ExpiredCache, Named { private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); private final String name; private final RedisOperations operations; private String keyPrefix; private Duration expiration; private Optional database; public RedisCache(String name, RedisOperations operations) { assertNotBlank(name, "Name must not be blank."); assertNotNull(operations, "RedisOperations must not be null."); this.name = name; this.operations = operations; this.database = Optional.empty(); } public RedisCache(String name, RedisOperations operations, String keyPrefix, Duration expiration, Integer database) { this(name, operations); this.keyPrefix = keyPrefix; this.expiration = expiration; this.database = Optional.ofNullable(database); } @Override public String getName() { return this.name; } public RedisOperations getOperations() { return operations; } public String getKeyPrefix() { return keyPrefix; } public void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; } public Duration getExpiration() { return expiration; } public void setExpiration(Duration expiration) { this.expiration = expiration; } public Optional getDatabase() { return database; } public void setDatabase(Integer database) { this.database = Optional.ofNullable(database); } protected void selectDatabase(RedisConnection connection) { this.database.filter(db -> db >= 0 && !connection.isClusterConnection()) .ifPresent(it -> invokeMethod(connection, "setDatabase", Integer.class, it)); } @SuppressWarnings("unchecked") @Override public V get(K key) throws CacheException { if (logger.isDebugEnabled()) { logger.debug("Getting object from cache [" + getName() + "] for key [" + key + "]"); } if (key == null) { return null; } try { return (V) operations.execute((connection) -> { selectDatabase(connection); byte[] value = connection.get(getKeyToBytes(key)); if (logger.isDebugEnabled() && (!isEmpty(value))) { logger.debug("Cache for [" + key + "] is exist, ready to deserialize it."); } return deserializeValue(value); }); } catch (Exception e) { throw new CacheException(e); } } @Override public V put(K key, V value) throws CacheException { return put(key, value, null); } @SuppressWarnings("unchecked") @Override public V put(K key, V value, Duration expired) throws CacheException { if (logger.isDebugEnabled()) { logger.debug("Putting object in cache [" + getName() + "] for key [" + key + "]"); } if (key == null) { return value; } try { return (V) operations.execute((connection) -> { selectDatabase(connection); Duration expirationToUse = expired != null ? expired : (getExpiration() == null ? DEFAULT_CACHE_EXPIRATION : getExpiration()); byte[] previous = connection.set(getKeyToBytes(key), serializeValue(value), expirationToUse); return deserializeValue(previous); }); } catch (Exception e) { throw new CacheException(e); } } @SuppressWarnings("unchecked") @Override public V remove(K key) throws CacheException { if (logger.isDebugEnabled()) { logger.debug("Removing object from cache [" + getName() + "] for key [" + key + "]"); } if (key == null) { return null; } try { return (V) operations.execute((connection) -> { selectDatabase(connection); byte[] previous = connection.del(getKeyToBytes(key)); if (logger.isDebugEnabled() && (!isEmpty(previous))) { logger.debug("Remove key [{}] successfully.", key); } return deserializeValue(previous); }); } catch (Exception e) { throw new CacheException(e); } } @Override public void clear() throws CacheException { if (logger.isDebugEnabled()) { logger.debug("Clear all cached objects."); } try { operations.execute((connection) -> { selectDatabase(connection); Set allKeys = connection.keys(UTF_8.serialize(getKeyPrefix() + "*")); if (logger.isDebugEnabled() && (!allKeys.isEmpty())) { logger.debug("Currently scanning to {} keys, ready to clear.", allKeys.size()); } Long c = connection.mdel(allKeys.toArray(new byte[allKeys.size()][])); if (logger.isDebugEnabled() && c != null) { logger.debug("After the cleanup is completed, this task clears {} cache objects.", c); } return c; }); } catch (Exception e) { throw new CacheException(e); } } @Override public int size() { try { return operations.execute((connection) -> { selectDatabase(connection); return connection.size(UTF_8.serialize(getKeyPrefix() + "*")); }).intValue(); } catch (Exception e) { throw new CacheException(e); } } /** * Because the final type of the cache instance is {@literal cache}, * the return type of the key is a string. */ @SuppressWarnings("unchecked") @Override public Set keys() { try { return operations.execute((connection) -> { selectDatabase(connection); Set allKeys = connection.keys(UTF_8.serialize(getKeyPrefix() + "*")); if (logger.isDebugEnabled() && (!allKeys.isEmpty())) { logger.debug("Currently scanning to {} keys.", allKeys.size()); } Set result = allKeys.stream().map(key -> ((K) deserializeKey(key))).collect(Collectors.toSet()); return Collections.unmodifiableSet(result); }); } catch (Exception e) { throw new CacheException(e); } } @Override public Collection values() { try { return operations.execute((connection) -> { selectDatabase(connection); Set allKeys = connection.keys(UTF_8.serialize(getKeyPrefix() + "*")); List allValues = connection.mget(allKeys.toArray(new byte[allKeys.size()][])); if (logger.isDebugEnabled() && (!allValues.isEmpty())) { logger.debug("Currently scanning to {} key-values.", allValues.size()); } @SuppressWarnings("unchecked") List result = allValues.stream().map(it -> ((V) deserializeValue(it))).collect(Collectors.toList()); return Collections.unmodifiableList(result); }); } catch (Exception e) { throw new CacheException(e); } } /** * Gets bytes key with key prefix. */ protected byte[] getKeyToBytes(K key) { return getKeyToBytes(key, getKeyPrefix()); } private byte[] getKeyToBytes(K key, String keyPrefix) { byte[] keyBytes = {}; if (key instanceof byte[]) { keyBytes = (byte[]) key; } else if (key instanceof String) { keyBytes = serializeKey(key.toString().replace(keyPrefix, "")); } else { /** * 此方案只适用于: * 在鉴权时 [org.apache.shiro.realm.AuthorizingRealm.doGetAuthenticationInfo(token)], * 返回的 [org.apache.shiro.authc.AuthenticationInfo]对象中,接口方法 [getPrincipals]的返回结果必须是:一成不变的。 * * 否则:建议在构建AuthorizingRealm派生类时,重写如下两个方法达到预期效果: * 1) AuthorizingRealm.getAuthorizationCacheKey(PrincipalCollection principals); * 2) AuthenticatingRealm.getAuthenticationCacheKey(PrincipalCollection principals). */ if (logger.isWarnEnabled()) { logger.warn("The current cache key is not byte[] or String type, and the key should be kept unchanged in subsequent operations."); } byte[] obytes = serializeValue(key); String keyToUse = Md5Utils.getMd5(obytes, key.getClass().getSimpleName()) + new String(obytes).hashCode(); keyBytes = serializeKey(keyToUse); } return mergeAll(serializeKey(keyPrefix), keyBytes); } private byte[] serializeKey(String key) { return this.operations.getKeySerializer().serialize(key); } private String deserializeKey(byte[] bytes) { if (isEmpty(bytes)) return null; return this.operations.getKeySerializer().deserialize(bytes); } private byte[] serializeValue(Object value) { return this.operations.getValueSerializer().serialize(value); } private Object deserializeValue(byte[] bytes) { if (isEmpty(bytes)) return null; return this.operations.getValueSerializer().deserialize(bytes); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy