Tomcat 之session 持久化2

时间:2023-03-08 16:28:09

通过前文 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 = ; 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 = ; 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 = ; 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.****.net/caomiao2006/article/details/51291005