Spring Boot系列(十五) 安全框架Apache Shiro(二)缓存-基于Hazelcast的分布式缓存

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xtiawxf/article/details/52606653

通常所说的“分布式”、“集群服务”、“网格式内存数据”、“分布式缓存“、“弹性可伸缩服务”这些非常牛逼高大上名词拿到哪都是ITer装逼的不二之选。在Javaer的世界,有这样一个开源项目,只需要引入一个jar包、只需简单的配置和编码即可实现以上高端技能,他就是 Hazelcast。

在上一篇Shiro缓存中,已经提到了,Shiro 1.3.x版本中,增加了一项非常重要,并且又很方便的功能,就是基于HazelCast的Session集群(分布式缓存)。当然,也可以使用比较流行的Redis作为缓存,只是实现起来要复杂一点,使用Redis缓存,会在后续章节中补充。

下面说一下Spring Boot中采用Hazelcast作为Shiro的分布式缓存的案例。大家可以提前往后看一下Spring Boot 缓存章节内容:
Spring Boot(十八)缓存

当在pom中引入Hazelcast的jar包时,Spring Boot会自动初始化HazelcastInstance 和 HazelcastCacheManager,这时我们可以利用Spring自动加载的com.hazelcast.spring.cache.HazelcastCacheManager 来作为Shiro的缓存(当然在ShiroConfiguration中直接初始化org.apache.shiro.hazelcast.cache.HazelcastCacheManager,但是这样就启动两个Hazelcast节点),此时需要将spring的缓存转换为shiro的缓存。具体实现步骤如下(基于Shiro Ehcache缓存示例基础上):

步骤1: 替换shiro包版本,移除ehcache缓存的jar包,添加hazelcast相关jar包:

<!-- 使用Shiro认证 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-hazelcast</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-spring</artifactId>
</dependency>

步骤2: 添加hazelcast.xml配置文件,并在application.properties中指定路径,如果不配置,则会使用hazelcast.jar包的hazelcast-default.xml文件。主要配置(其他省略,拷贝过来修改即可,除下面列出的参数以外,其他参数会在后续单独补充hazelcast的章节中说明)如下:

<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.6.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    ...
    <!-- 开启连接hazelcast的管理中心,并指向管理中心url,默认是false-->
    <management-center enabled="true">http://localhost:8080/mancenter</management-center>
    ...
    <map name="default">
        ...
        <!-- 缓存有效时间(如果不更新缓存,缓存有效的时间,超过时间会自动移除),默认0,无限时间 -->
        <time-to-live-seconds>120</time-to-live-seconds>
        <!-- 缓存最大空闲时间(如果不访问该缓存,如果超过最大闲置时间,则自动移除),默认0,无限时间 -->
        <max-idle-seconds>120</max-idle-seconds>
        ...
    </map>
    ...
</hazelcast>
...
# hazelcast配置文件,如果不指定,使用jar中默认的,
spring.cache.type=HAZELCAST
spring.cache.hazelcast.config=classpath:META-INF/hazelcast/hazelcast.xml
...

这里说下为什么配置map节点参数,因为shiro使用hazelcast的缓存结构是 线程安全的Map接口ConcurrentMap,支持高并发。

步骤三:创建将 com.hazelcast.spring.cache.HazelcastCacheManager转换为org.apache.shiro.hazelcast.cache.HazelcastCacheManager 的类:SpringCacheManagerWrapper

import java.util.Map;

import javax.annotation.Resource;

import org.apache.shiro.ShiroException;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MapCache;
import org.apache.shiro.util.Destroyable;
import org.apache.shiro.util.Initializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.spring.cache.HazelcastCacheManager;

public class SpringCacheManagerWrapper implements  CacheManager, Initializable, Destroyable {

    public static final Logger log = LoggerFactory.getLogger(SpringCacheManagerWrapper.class);
    @Resource
    private HazelcastCacheManager cacheManager;
    private boolean implicitlyCreated = false;
    private HazelcastInstance hazelcastInstance;
    private Config config;

    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        Map<K, V> map = hazelcastInstance.getMap(name); //returned map is a ConcurrentMap
        return new MapCache<K, V>(name, map);
    }
    protected HazelcastInstance ensureHazelcastInstance() {
        if (this.hazelcastInstance == null) {
            this.hazelcastInstance = createHazelcastInstance();
            this.implicitlyCreated = true;
        }
        return this.hazelcastInstance;
    }
    public void init() throws ShiroException {
        if(this.cacheManager == null){
            ensureHazelcastInstance();
        }else{
            hazelcastInstance = cacheManager.getHazelcastInstance();
        }
    }
    protected HazelcastInstance createHazelcastInstance() {
        return Hazelcast.newHazelcastInstance(this.config);
    }
    protected final boolean isImplicitlyCreated() {
        return this.implicitlyCreated;
    }
    public void destroy() throws Exception {
        if (this.implicitlyCreated) {
            try {
                this.hazelcastInstance.getLifecycleService().shutdown();
            } catch (Throwable t) {
                if (log.isWarnEnabled()) {
                    log.warn("Unable to cleanly shutdown implicitly created HazelcastInstance.  " +
                            "Ignoring (shutting down)...", t);
                }
            } finally {
                this.hazelcastInstance = null;
                this.implicitlyCreated = false;
            }
        }
    }
    public HazelcastInstance getHazelcastInstance() {
        return hazelcastInstance;
    }
    public void setHazelcastInstance(HazelcastInstance hazelcastInstance) {
        this.hazelcastInstance = hazelcastInstance;
    }
    public Config getConfig() {
        return config;
    }
    public void setConfig(Config config) {
        this.config = config;
    }
}

步骤4:在ShiroConfiguration中注入缓存管理器(分析源码时,需要提前注入SessionManager,之后再注入缓存管理器时,会尝试向SessionManager中注入当前缓存管理器)

@Bean
public SecurityManager securityManager() {
    logger.info("注入Shiro的Web过滤器-->securityManager", ShiroFilterFactoryBean.class);
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //设置Realm,用于获取认证凭证
    securityManager.setRealm(userRealm());
    //注入session管理器
    securityManager.setSessionManager(sessionManager());
    //注入缓存管理器
       securityManager.setCacheManager(hazelcastCacheManager());//这个如果执行多次,也是同样的一个对象;

    return securityManager;
}
@Bean
public DefaultWebSessionManager sessionManager(){
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setSessionDAO(sessionDAO());
    return sessionManager;
}
/**
 * Shiro CacheManager 定义 Shiro 缓存管理器
 * 
 * 需要加入到安全管理器:securityManager
 * @return
 */
@Bean
public SpringCacheManagerWrapper hazelcastCacheManager() {
    logger.info("注入Shiro的缓存管理器-->hazelcastCacheManager", SpringCacheManagerWrapper.class);
    SpringCacheManagerWrapper cacheManager = new SpringCacheManagerWrapper();
    return cacheManager;
}

@Bean
public EnterpriseCacheSessionDAO sessionDAO(){
    EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
    return sessionDAO;
}

步骤5:启动两个应用,端口分别为8088和8089,可以看到启动日志:
启动8089时日志
启动8088时日子
当启动8089时,启动了hazelcast的一个节点5701,当启动8088时,启动了hazelcast的第二个节点5702,并且这两个节点已经实现互为备份的集群关系。

步骤6:测试,通过在浏览器中打开http://localhost:8080/mancenter 来监控hazelcast。(前提需要下载并运行mamcenter.war)
当登录后,可以看到Maps节点中shiro-activeSessionCache、authorizationCache、authenticationCache的缓存情况:
Hazelcast 管理中心
由于设置了缓存时间是2分钟(120s),所以2分钟内不刷新浏览器会自动过期此时上图画红线标注的session消失了(失效自动移除)。

展开阅读全文

没有更多推荐了,返回首页