一、特殊名词介绍

二、背景、场景介绍

帆软报表后台计算之后会把计算过程中的相关模板、参数、结果等后续渲染或导出打印需要的中间对象保存到一个统一的报表sessioninfo对象中,并映射到一个ID上。方便其他业务单元使用。

那么在sessioninfo产生后和销毁前,就允许开发者通过SessionManageProcessor对当前的session对象进行一些自定义的监控和处理了。比如session产生时,对模板或者参数做预处理,销毁时,处理一些额外的缓存等等的场景都可以使用。

三、接口介绍

package com.fr.stable.fun;

import com.fr.stable.fun.mark.Immutable;

/**
 * Created by Administrator on 2015/11/5 0005.
 */
public interface SessionManageProcessor extends Immutable {

    String XML_TAG = "SessionManageProcessor";

    int CURRENT_LEVEL = 1;

    /**
     * 记录Session创建次数
     *
     * @param sessionID 会话id
     */
    void registSessionCreate(String sessionID);

    /**
     * 记录Session销毁次数
     *
     * @param sessionID 会话id
     */
    void registSesssionDestroy(String sessionID);

    /**
     * 获取当前服务器Session池状态
     */
    String getServerState();
}


package com.fr.web.core;

...

/**
 * Session处理中心,所有Session的创建、获取、销毁都是通过这个类来实现的。
 */
public class SessionPoolManager {

    ...

    /**
     * 将sessionID -> SessionIDInfor放进sessionIDMap中
     *
     * @param sessionIDInfor sessionID 对应的SessionIDInfor
     */
    public static String addSessionIDInfor(SessionProvider sessionIDInfor) throws Exception {
        if (sessionIDInfor == null) {
            FineLoggerFactory.getLogger().error("create empty sessionProvider!");
            return null;
        }

        String sessionID = SessionPoolManager.randomSessionID();
        ExtraClassManagerProvider pluginProvider = PluginModule.getAgent(PluginModule.ExtraCore);
        if (pluginProvider != null) {
            Set<SessionPrivilegeFilterProvider> set = pluginProvider.getArray(SessionPrivilegeFilterProvider.XML_TAG);
            for (SessionPrivilegeFilterProvider provider : set) {
                sessionID = provider.encodeSessionID(sessionID);
            }
        }

        while (sessionIDMap.containsKey(sessionID)) {
            sessionID = SessionPoolManager.randomSessionID();
        }
        // 保存sessionID与SessionIDInfor到sessionIDMap中
        sessionIDMap.put(sessionID, sessionIDInfor);
        //集群环境下
        if (ClusterStatusHelper.isClusterEnv()) {
            // 在状态服务器中保存一份
            sessionStateService.put(
                    sessionID,
                    new SessionShareInfo()
                            .startTime(sessionIDInfor.getStartTime())
                            .sessionId(sessionID)
                            .clusterNodeId(ClusterStatusHelper.getCurrentNodeId())
            );
        }

        recordSessionCreate(sessionID);
        Calculator.putThreadSavedNameSpace(SessionIDInfo.asNameSpace(sessionID));
        sessionIDInfor.setSessionID(sessionID);
        addDelayedTask(sessionID);
        return sessionID;
    }

    private static void recordSessionCreate(String sessionID) {
        ExtraClassManagerProvider pluginProvider = PluginModule.getAgent(PluginModule.ExtraCore);
        if (pluginProvider != null) {
            SessionManageProcessor sessionManageProcessor = pluginProvider.getSingle(SessionManageProcessor.XML_TAG);
            if (sessionManageProcessor != null) {
                sessionManageProcessor.registSessionCreate(sessionID);
            }
        }
    }

    private static void recordSessionDestroy(String sessionID) {
        ExtraClassManagerProvider pluginProvider = PluginModule.getAgent(PluginModule.ExtraCore);
        if (pluginProvider != null) {
            SessionManageProcessor sessionManageProcessor = pluginProvider.getSingle(SessionManageProcessor.XML_TAG);
            if (sessionManageProcessor != null) {
                sessionManageProcessor.registSesssionDestroy(sessionID);
            }
        }
    }

    

    /**
     * 关掉Session
     *
     * @param sessionID 报表session
     */
    public static void closeSession(final String sessionID) {
        synchronized (SessionPoolManager.class) {
            boolean addDirectly = sessionToDelete.add(sessionID);
            if (!addDirectly) {
                //false说明之前已经被添加过一次了
                return;
            }
        }
        //正常关闭session(关闭tab)时清理保存的过期session(如果有的话),避免Map越来越大
        TimeoutSessionRecorder.removeSession(sessionID);
        closeSessionPool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    processCloseSession(sessionID);
                    synchronized (SessionPoolManager.class) {
                        sessionToDelete.remove(sessionID);
                    }
                } catch (Throwable e) {
                    FineLoggerFactory.getLogger().error(e.getMessage(), e);
                }
            }
        });
    }

    private static void processCloseSession(String sessionID) {
        FineLoggerFactory.getLogger().info("Session closed: {}", sessionID);
        SessionProvider sessionIDInfor = sessionIDMap.remove(sessionID);
        if (sessionIDInfor != null) {
            // session有可能已经被删除了.
            AllHistoryAndLiveSession.getInstance().addHistroySession(sessionIDInfor);
            SessionHelper.remove(sessionID);
            sessionIDInfor.release();
            recordSessionDestroy(sessionID);

            WebContextProvider context = sessionIDInfor.getWebContext();
            if (context != null) {
                RemoteAddressManager.remove(context.getAddress(), sessionID);
            }
            // 从状态服务器中删除session信息
            try {
                sessionStateService.delete(sessionID);
            } catch (Exception e) {
                FineLoggerFactory.getLogger().error("Failed to delete session '" + sessionID + "' from state service", e);
            }
        }
    }

    ...
}


四、支持版本

产品线

版本

支持情况

备注

FR9.0支持
FR10.0支持

五、插件注册

<extra-core>
        <SessionManageProcessor class="your class name"/>
</extra-core>


六、原理说明

接口只能在设计器中被调用,在需要调用的地方通过SessionManageProcessor processor = PluginModule.getAgent(PluginModule.ExtraCore).getSingle(SessionManageProcessor.XML_TAG);获取到插件中所有申明的Session管理接口监听的最后一个。

默认的产品中主要在:上面的SessionPoolManager中进行监听生效。

七、特殊限制说明

目前10.0中,getServerState方法是没有用的。

注意,该接口是独占的接口,使用前需确认用户的系统环境中是否已经存在了相同的接口插件。避免冲突。

八、常用链接

demo地址:demo-session-manage-processor


九、开源案例

免责声明:所有文档中的开源示例,均为开发者自行开发并提供。仅用于参考和学习使用,开发者和官方均无义务对开源案例所涉及的所有成果进行教学和指导。若作为商用一切后果责任由使用者自行承担。