Tomcat 之session 持久化2

通过前文 Tomcat 之session 持久化1 ,我们已经大概了解了这么个机制。但是我没能详细展开其底层的原理。

这篇文章,我想稍微深入一点点,再继续聊一聊其底层。

 

Tomcat 之session 持久化的作用:

这样做的好处是:减少系统资源的占用,如果Servlet容器突然关闭或重启,或Web应用重启,这些持久化了的HttpSession对象可以再重新加载进来,对于客户端,还是使用同一个Session。

 

Tomcat提供了哪些实现?

StandardManager 会在每次关闭tomcat的时候把所有的session 持久化到 SESSIONS.ser, 然后再次启动的时候读取 SESSIONS.ser, 然后删除这个文件

PersistentManager 呢, 为每一个session 生成一个session文件,或者数据库表的一行记录。如 8B66FF7606964BD4D4D6B3225170CCB2.session。

对于FileStore ,为每个session,创建一个.session 文件。 创建是在适当的时候进行的。 文件以 sessionId + .session 为名, 这点和其他的store 有所不同。

对于JDBCStore, 每个session 会被存储到表的一行,以sessionId为key。 注意,配置JDBCStore 的时候,不要有任何的错误,否则就无法持久化,而且呢,TMD,Tomcat竟然也不报错。比如我配置sessionValidCol这个属性的时候, session_valid 写成了 valid_session, 花了很长时间才发现,坑!

 

关于持久化的时机:

其实具体什么时候进行持久化, 默认都是在关闭tomcat的时候进行的 ,但具体是 配置的参数相关的:

debug 
设定Session Manager采用的跟踪级别,取值0到99,越小越跟踪信息越少,发布产品时,应该设置为0,以提高性能。 

saveOnRestart 
如果为true,则当Tomcat关闭时,所有的有效HttpSession对象都保存到Session Store中;当Tomcat重启时,加载这些HttpSession对象。 

maxActiveSessions 
设置处于活动状态的Session的最大数目,如果超过这一数目,Tomcat把一些超过的Sessin对象保存到Session Store中。-1表示不限制。 

minIdleSwap 
Session处于不活动状态的最小时间,单位为秒,超过这一时间,Tomcat有可能把这个Session对象移到Session Store中。 

maxIdleSwap 
Session处于不活动状态的最大时间,超过这一时间,Tomcat就一定会将这个Session对象移到Session Store中。

maxIdleBackup 
Session处于不活动状态的最大时间,超过这一时间,Tomcat就就会将这个Session对象拷贝到Session Store中进行备份。 

directory 
指定Session Store在哪个文件系统目录下存放持久化的Session对象的信息,文件名是Session ID.session。 

 

持久化,到底具体持久了什么?

我之前其实有个先入为主的观念是,Tomcat 可以把session , 那么是否就是说我们把浏览器关闭后, tomcat 也关闭了后, 重启,再次访问那个web 应用, 就可以不用登陆的吧!

 让我们通过源码仔细看看被持久化的session 具体有哪些内容吧! 

观察源码发现,关键代码就在于 StandardSession , 参照其中的readObject 方法, 我写了一个测试类:

package com.lk;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.WriteAbortedException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SessonDeserialization {
    
    public static void main(String[] args) throws Exception {
        
        String filePath = "D:\\soft\\apache-tomcat-7.0.69\\work\\Catalina\\localhost" +
//                "\\session\\D6C5454611334414893B1EB6E3E966BD.session";
             "\\session\\66792850BAEAF9A4BD17877F1A27B551.session";
        
        
        File file = new File(filePath); 
        FileInputStream fis = new FileInputStream(file.getAbsolutePath());
        BufferedInputStream bis = new BufferedInputStream(fis);
        ObjectInputStream ois = new ObjectInputStream(bis);
        readObject(ois);
        
    }
    

    protected static LkSession readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
//        this.authType = null;
        long creationTime = ((Long)stream.readObject()).longValue();
        long lastAccessedTime = ((Long)stream.readObject()).longValue();
        int maxInactiveInterval = ((Integer)stream.readObject()).intValue();
        boolean isNew = ((Boolean)stream.readObject()).booleanValue();
        boolean isValid = ((Boolean)stream.readObject()).booleanValue();
        long thisAccessedTime = ((Long)stream.readObject()).longValue();
        Object principal = null;
        String id = (String)stream.readObject();

        Map attributes = new ConcurrentHashMap();

        int n = ((Integer)stream.readObject()).intValue();// session 的个数 
        
        System.out.println(n);
        boolean isValidSave = isValid;
        isValid = true;

        for(int i = 0; i < n; ++i) {
            String name = (String)stream.readObject();

            Object value;
            try {
                value = stream.readObject();
            } catch (WriteAbortedException var8) {
//                if(var8.getCause() instanceof NotSerializableException) {
//                        continue; // 从这里可以看到, 如果不能序列化, 那么直接忽略
//                }
                throw var8;
            }

//            if(!this.exclude(name, value)) {
                attributes.put(name, value);
//            }
            
        }

        isValid = isValidSave;
        
        return new LkSession(creationTime, lastAccessedTime, id, maxInactiveInterval, isNew, isValid
                ,thisAccessedTime, attributes);
    }
    

}

可见,基本上很多有用的东西都被持久化了下来。它们按照一定的格式被组织了起来:

creationTime = [1509271063483], lastAccessedTime = [1509271929705], id = [66792850BAEAF9A4BD17877F1A27B551], maxInactiveInterval = [1800], isNew = [false], isValid = [true], thisAccessedTime = [1509271929705], attributes = [{aaa=AAAAAA, user=User [username=aa, password=bb], ccc=how are you ! 你好啊 ! , bb=111}]

 

而 writeObject 其实也很好理解,就是把可以序列化的内容序列化起来!:

    protected void writeObject(ObjectOutputStream stream) throws IOException {
        stream.writeObject(Long.valueOf(this.creationTime));
        stream.writeObject(Long.valueOf(this.lastAccessedTime));
        stream.writeObject(Integer.valueOf(this.maxInactiveInterval));
        stream.writeObject(Boolean.valueOf(this.isNew));
        stream.writeObject(Boolean.valueOf(this.isValid));
        stream.writeObject(Long.valueOf(this.thisAccessedTime));
        stream.writeObject(this.id);
        if(this.manager.getContainer().getLogger().isDebugEnabled()) {
            this.manager.getContainer().getLogger().debug("writeObject() storing session " + this.id);
        }

        String[] keys = this.keys();
        ArrayList saveNames = new ArrayList();
        ArrayList saveValues = new ArrayList();

        int n;
        for(n = 0; n < keys.length; ++n) {
            Object i = this.attributes.get(keys[n]);
            if(i != null) {
                if(this.isAttributeDistributable(keys[n], i) && !this.exclude(keys[n], i)) {
                    saveNames.add(keys[n]);
                    saveValues.add(i);
                } else {
                    this.removeAttributeInternal(keys[n], true);
                }
            }
        }

        n = saveNames.size();
        stream.writeObject(Integer.valueOf(n));

        for(int var9 = 0; var9 < n; ++var9) {
            stream.writeObject(saveNames.get(var9));

            try {
                stream.writeObject(saveValues.get(var9));
                if(this.manager.getContainer().getLogger().isDebugEnabled()) {
                    this.manager.getContainer().getLogger().debug("  storing attribute \'" + (String)saveNames.get(var9) + "\' with value \'" + saveValues.get(var9) + "\'");
                }
            } catch (NotSerializableException var8) {
                this.manager.getContainer().getLogger().warn(sm.getString("standardSession.notSerializable", new Object[]{saveNames.get(var9), this.id}), var8);
            }
        }

    }

 

简易登录拦截器

鉴于此, 我把我之前的代码有完善了下。 我增加了个 登录拦截器功能:

package com.lk;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class SessionAuthInterceptor {
    
    public static boolean isLoggedIn(HttpServletRequest req) {
        HttpSession session = req.getSession();

        String sessionid = session.getId();
        // System.out.println(sessionid);
        long lastAccessedTime = session.getLastAccessedTime();
        System.out.println(lastAccessedTime);
        
        User user = (User) session.getAttribute("user");//  User必须 implements Serializable, 否则这里获取到的就是 null  
        
        if (user != null) {
            System.out.println("已登陆:"+ sessionid);
            return true;
        }
        
        System.err.println("未登陆:"+ sessionid);
        return false;
    }

}

然后呢,在相关的servlet 代码里面,增加对session的判断, 如此就可以判断当前用户是否已经登录过了:

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        if (!SessionAuthInterceptor.isLoggedIn(req)) { // 这里可以改成用AOP 来实现。
            req.getRequestDispatcher("login.jsp").forward(req, resp);
            return;
        }
        ...
        }

 

参考:

http://blog.csdn.net/caomiao2006/article/details/51291005

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值