在Tomcat中,HttpSession的实际实现为StandardSession。
1.类接口分析

a.对于该类,首先肯定是要实现HttpSession接口,该接口为JAVA EE规范,关于HttpSession接口的一些方法在上一节中已经讲得很清楚了;
b.其次,StandardSession要实现Tomcat中的Session接口,该接口主要维护Tomcat内部对Session的处理,如SessionId相关,session失效相关处理,Tomcat内部生命周期事件,有效性访问等等,与JAVA EE规范中的HttpSession接口没有关系,而StandardSession是Tomcat中的一个实体组件,所以需要实现该接口;

上面的四个事件,是Tomcat的内部事件;
c.除此之外,在后续的分析中,我们可以看到Session在Tomcat中是可以进行持久化的,根据需要所以需要可以进行序列化,实现Serializable;
2.set/getAttributes

可以理解,因为Session中可以存储属性,put,get方法对应的就应该是Map;
而我们第二个反应就是,因为不同的客户端线程在Servlet中操纵Session,因此这肯定是需要并发处理的,因此就是ConcurrentHashmap;
值得说的一点是,在ConcurrentHashmap没出来之前,session访问属性效率较低,当时是通过Hashmap的Synchronized同步化来做的,加一个独占锁,而ConcurrentHashmap是CAS优化,大大提升了效率,我们从这里也可以看到,JDK的升级对服务器的效率提升有质的帮助。
3.sessionId产生原理与实现

在Tomcat中,每一个新创建的Session,都有一个唯一标识的字符串与之相对应,
对于session是如何创建的,这个是由StandardManager来进行管理的,其创建session就是调用其createSession方法:

首先创建一个空StandardSession类,然后设置一些属性,因为是第一次,所以设置New
设置之后,其通过generateSessionId产生SessionId这个唯一标识的字符串;
对于SessionId是怎么产生的呢?

我们暂且不管上面SessionId产生的算法,
我们分析SessionId是由配置的SessionIdLength的长度,随机数+算法,还有一个最重要的参数,JVMRoute来产生的。
什么是JVMRoute呢?
可以理解为这个属性就是tomcat中的路由标识,其作用主要是在tomcat集群中,用以区分两台tomcat主机,一般配置如下:
tomcat1\conf\server.xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
tomcat2\conf\server.xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">
这样在集群的过程中,每一台主机的jvmRoute都不同,因而生成的sessionId也会不同,如集群配置为轮询,我们来看看这种情况的
访问http://localhost:8080/1.jsp 可以看到session.getId()的值在原session id后面多了jvmRoute的值。
ID 46A5843FF4A1E0A84338225AC02F6430.tomcat1
随意添加key-value,可以看到session信息只在tomcat1中输出。
再打开一个浏览器,并访问http://localhost:8080/1.jsp 其session id可能变为
ID 11478E5BE5FE388E4845205B4133A30F.tomcat2
其值也只会在tomcat2中输出。
上面的. 后面的内容就是其对应的jvmRoute;
4.sessionId附着和跟踪机制
那么,我们不禁要问,上述的sessionId是如何查看的呢?
Tomcat中一共有几种sessionID附着与跟踪的方式,我们可以从一个Tomcat中的内部类看出来:

a.第一种方式是将sessionId写入到cookie中,客户端和服务器端每一次交互的时候,都从cookie中取出JsessionId,看是否有这个条目,如果存在JSESSIONID的cookies,

在Tomcat前端线程池到Tomcat后端容器的这一个过程中,CoyAdpator会解析对应的参数,并设置到Request中去

然后,Request会基于客户端传递过来的sessionId进行初始化,找寻的办法就是拿客户端的JSESSIONID,去这个查询服务器端前面提到的session的concurrentHashmap中查询

如果查询不到的话,那么说明当前session已经失效了,要重新创建一个Session:

那么最后,也需要在响应头中新建一个cookie,写回到客户端中;
b.第二种方式是采用url重写的session机制
什么是url重写,也就是说当cookie在浏览器端不支持的情况下(现如今一般很少有这样的情况,除非客户端浏览器已经禁用cookie),也就说说,JSESSIONID=xxxx 这段作为一个参数,在客户端访问该站点一直都作为url的尾巴:

这当然比较难看啦,一个超级长的链接,但这种办法也是权益之计,毕竟没有cookie省事,现在cookie不是用不了了吗!

URL重写的检查是放在cookie前面的,可以推断出来,url重写应该是默认支持的模式;
我们不用猜测,直接看一下SessionTrackingMode是怎么定义的:

对于URL重写,从代码上分析,肯定是默认支持;
其次,来判断当前应用Context(StandardContext)是否支持cookie,如果支持的话,在SessionTrackingMode集合中加上这个模式,加上这个模式后,前面的Request的代码中就可以看到,会多一步,在cookie中寻找;
当配置SSL的时候,并且需要在配置文件中配置SSLEnabled的话,SessionTrackingMode才启动SSL模式;
对于SSL交互,其遵循的协议是SSL/TLS了,因此SessionId创建是不同的,这块我们单独在SSL的文章讲解中专题讲座基于SSL 的SessionId;
5.失效与时间
session作为一个有限时间内的缓存,发挥着重要的作用同时,也受时间的限制;
在StandardSession类中,有好几个属性都能解决该Session是否没有效了,我们可以看一个方法就一目了然:

a. isValid属性:对应HttpSession就是invalidate方法,无论session有效期过没过,一下就置为无效
b.expire属性:该属性代表,session已经过期了,
c.ACTIVITY_CHECK:该属性是Tomcat的一个-D开关,其作用可以参考 Tomcat的属性页面的解释:
http://tomcat.apache.org/tomcat-8.5-doc/config/systemprops.html

当设置了这个属性的时候,Tomcat会对session进行计数,从图中也就是accesscount,也就是说在session的范围内,有几个有效的链接是起作用的,当链接过来,accesscount会加1,当关闭链接,accesscount会减1;
这相当于什么?
accesscount实际就是个针对于当前Session的在线活跃数的统计;
回到前面的isValid方法,当accesscount>0,说明当前session活跃用户数肯定不是0,这就意味着当前的session肯定有效;
d.maxInactiveInterval:这个属性是HttpSession的属性,标识着过了多久,客户端的请求连接能让session再续下去,maxInactiveInterval要想起作用,主要就是当前时间-上次记录的时间,

这里就有一个Tomcat的-D参数,其作用就是我们看到access方法,配置的内容是Tomcat中可以怎么掐这段时间:

当上述为true,在access方法刚调用的时候打一个时间戳,也就是thisAccessedTime,当为false,就是lastAccessedTime,在endaccess打一个时间戳;
二者的区别在于不将请求的时间算进去,因为有的时候请求是长事务,占用很长的时间;
总结
StandardSession实际上是Tomcat对session的实现,该类既完全实现了HttpSession接口,又针对Tomcat的内部生命周期事件给出了实现,并且基于SessionId给出了经典的解释,如果我们要自己实现session,这个类是一个非常好的教材!




