首先这是一个“伪话题”,原因有下面2点:
1. 现在的应用部署越来越多采用分布式集群部署,从而对于session的使用越来越少。采用session来存储数据的方式逐渐都转移到了缓存中实现。
2. 前后端分离的应用开发模式越来越多,而 jsp 的使用也是越来越少。
但是,我为什么还是要把这个话题拿出来在这里再叙述一遍呢?原因是看到外面大量关于这个话题的文章,基本上都是错的,为了防止读者受到错误影响,我写下了这篇文章,希望对你有所帮助。
JSESSIONID的产生机制
tomcat只有在程序中使用了 xxx.getSession() 的时候才会创建session(JSESSIONID是它的标识),其他任何时候都不会主动去创建。
这个时候有人问了,我的程序里明确没有调用 xxx.getSession(),为什么还会创建session?原因是用到了jsp作为渲染机制,你去看一下在tomcat的工作目录中所对应的jsp编译好的.class或者也有.java文件,能够看到:session = pageContext.getSession();这样一段代码。这个session也就是jsp几个内置对象中含有session对象的来源。所以,只要你用到了 jsp,那么就会默认产生session。
那不用jsp,而用 servlet 的话,会不会主动创建 session 呢?不会的,因为对于Controller或者Action来说,本质上还是servlet。不会的,除非你自己写 xxx.getSession()。
那我也不用jsp,用其他的模板引擎,比如 freemarker 或者 Thymeleaf,会不会主动创建 session 呢?不会的,除非这个模板引擎中也主动调用xxx.getSession,否则不会。
那我既不用jsp,也不用servlet,用的是 spring的Controller或者 struts 的Action,那会不会主动创建 session 呢?不会的,因为对于Controller或者Action来说,本质上还是servlet。
禁用tomcat的session
通过上面的机制分析,很明显禁用tomcat的session的最好途径就是告诉jsp,不要主动创建session,那么方式是在每个jsp页面中加入:
<%@ page session="false" %>
加好以后,创新一下该jsp页面,会发现编译好的.class或者.java中 session = pageContext.getSession(); 这句话已经消失不见了。
加好以后,在浏览器中清除cookie后,再看一下 JSESSIONID 已经不再出现。
那在每个页面中加入 <%@ page session="false" %>,太麻烦了,一般在jsp工程中,大家都会在开始就用 <%@ include file="xxx.jsp"%> 这样来引入一些每个页面中都需要的元素,那么就把 <%@ page session="false" %> 加入到 xxx.jsp 中。
另一种做法:禁用tomcat的session
上一种方法是非常不错的,但是会出现一些纰漏的地方:
1. 会不会有个别jsp页面没有加入 <%@ page session="false" %>
2. 在servlet中,会不会某个程序员随心写了句 session = request.getSession(),但是 session 却没有去用
为了堵住这些纰漏的地方,还有一种采用连根拔起的方式方法。
import java.io.IOException;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
public class SessionManager extends ManagerBase implements Lifecycle {
private Session globalSession;
@Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
setState(LifecycleState.STARTING);
}
@Override
protected synchronized void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void unload() throws IOException {
}
@Override
public Session createSession(String sessionId) {
if(globalSession == null) {
globalSession = super.createSession(sessionId);
} else {
globalSession.setValid(true);
}
return globalSession;
}
@Override
public Session createEmptySession() {
if(globalSession == null) {
globalSession = super.createSession(sessionId);
} else {
globalSession.setValid(true);
}
return globalSession;
}
}
SessionManager的原理其实是用同一个 globalSession 来代替每次创建新的 session,不创建这个 session 的话,那么会在访问 jsp 页面的时候报错:Page needs a session and none is available。
还需要注意这个globalSession需要进行激活,否则在tomcat的session失效后,会将此session标注为失效,那么尽管globalSession还是存在的,但是是失效的状态,还是会报错说session不存在。所以globalSession.setValid(true);这句很重要。
然后,在tomcat的server.xml中进行设置,大致如下:
<Context docBase="D:\test" path="/test" reloadable="false" sessionCookieName="yoursessionname">
<Manager className="xxx.xxx.SessionManager" />
</Context>
上面的sessionCookieName默认是JSESSIONID,其实这个是有点点缺陷的,能够让别人知道你的应用是用java开发的,所以,换个其他的名字会更好一些。
springboot中使用上面例子的SessionManager
上面这个方法是用传统方式开发应用,那么在springboot下,因为内嵌了tomcat,使得更加简便
@Configuration
public class TomcatConfig {
@Bean
public EmbeddedServletContainerFactory servletContainer(){
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
SessionManager sessionManager = new SessionManager();
context.setManager(sessionManager);
context.setSessionCookieName("yoursessionname");
}
});
return factory;
}
}
这种方式,最好做成 Auto-configuration 的jar,在pom.xml中引入,更加方便,具体可以参考一下这篇文章:@SpringBootApplication中几点解惑
综述
- 最好在jsp页面中加入 <%@ page session="false" %>
- 用上面的SessionManager方式
最后说一下,现代的java应用开发,几乎不考虑 session 了,所以这篇文章对您可能没有多大用处,对一些传统应用还是有些帮助的。