在各种Session 管理方案中, ThreadLocal 模式得到了大量使用。ThreadLocal 是
Java中一种较为特殊的线程绑定机制。通过ThreadLocal存取的数据,总是与当前线程相关,
也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出
现的并发访问问题提供了一种隔离机制。
首先,我们需要知道,SessionFactory负责创建Session,SessionFactory是线程
安全的,多个并发线程可以同时访问一个SessionFactory 并从中获取Session 实例。而
Session并非线程安全,也就是说,如果多个线程同时使用一个Session实例进行数据存取,
则将会导致Session 数据存取逻辑混乱。下面是一个典型的Servlet,我们试图通过一个类
变量session实现Session的重用,以避免每次操作都要重新创建:
public class TestServlet extends HttpServlet {
private Session session;
public void doGet( HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
session = getSession();
doSomething();
session.flush();
}
public void doSomething(){
......//基于session的存取操作
}
}
代码看上去正确无误,甚至在我们单机测试的时候可能也不会发生什么问题,但这样的代
Hibernate Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
码一旦编译部署到实际运行环境中,接踵而来的莫名其妙的错误很可能会使得我们摸不找头脑。
问题出在哪里?
首先,Servlet 运行是多线程的,而应用服务器并不会为每个线程都创建一个Servlet
实例,也就是说,TestServlet在应用服务器中只有一个实例(在Tomcat中是这样,其他的
应用服务器可能有不同的实现),而这个实例会被许多个线程并发调用,doGet 方法也将被不
同的线程反复调用,可想而知,每次调用doGet 方法,这个唯一的TestServlet 实例的
session 变量都会被重置,线程A 的运行过程中,其他的线程如果也被执行,那么session
的引用将发生改变,之后线程A 再调用session,可能此时的session 与其之前所用的
session就不再一致,显然,错误也就不期而至。
ThreadLocal的出现,使得这个问题迎刃而解。
我们对上面的例子进行一些小小的修改:
public class TestServlet extends HttpServlet {
private ThreadLocal localSession = new ThreadLocal();
public void doGet( HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
localSession.set(getSession());
doSomething();
session.flush();
}
public void doSomething(){
Session session = (Session)localSession.get();
......//基于session的存取操作
}
}
可以看到,localSession 是一个ThreadLocal 类型的对象,在doGet 方法中,我们
通过其set 方法将获取的session 实例保存,而在doSomething 方法中,通过get 方法取
出session实例。
这也就是ThreadLocal的独特之处,它会为每个线程维护一个私有的变量空间。实际上,
其实现原理是在JVM 中维护一个Map,这个Map的key 就是当前的线程对象,而value则是
线程通过ThreadLocal.set方法保存的对象实例。当线程调用ThreadLocal.get方法时,
ThreadLocal会根据当前线程对象的引用,取出Map中对应的对象返回。
这样,ThreadLocal通过以各个线程对象的引用作为区分,从而将不同线程的变量隔离开
来。
Hibernate官方开发手册的示例中,提供了一个通过ThreadLocal维护Session的好
榜样:
public class HibernateUtil {
private static SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory
sessionFactory = new
Configuration().configure().buildSessionFactory();
} catch (HibernateException ex) {
throw new RuntimeException(
"Configuration problem: " + ex.getMessage(),
ex
);
}
}
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
// Open a new Session, if this Thread has none yet
if (s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
在代码中,只要借助上面这个工具类获取Session 实例,我们就可以实现线程范围内的
Session 共享,从而避免了在线程中频繁的创建和销毁Session 实例。不过注意在线程结束
时关闭Session。
同时值得一提的是,新版本的Hibernate在处理Session的时候已经内置了延迟加载机
制,只有在真正发生数据库操作的时候,才会从数据库连接池获取数据库连接,我们不必过于担
心Session的共享会导致整个线程生命周期内数据库连接被持续占用。
对于Web程序
而言,我们可以借助Servlet2.3规范中新引入的Filter机制,轻松实现线程生命周期内的
Session管理(关于Filter的具体描述,请参考Servlet2.3规范)。
Filter的生命周期贯穿了其所覆盖的Servlet(JSP也可以看作是一种特殊的Servlet)
及其底层对象。Filter在Servlet被调用之前执行,在Servlet调用结束之后结束。因此,
在Filter 中管理Session 对于Web 程序而言就显得水到渠成。下面是一个通过Filter 进
行Session管理的典型案例:
public class PersistenceFilter implements Filter
{
protected static ThreadLocal hibernateHolder = new ThreadLocal();
public void doFilter(ServletRequest request, ServletResponse
response, FilterChain chain)
throws IOException, ServletException
{
hibernateHolder.set(getSession());
try
{
……
chain.doFilter(request, response);
……
}
finally
{
Session sess = (Session)hibernateHolder.get();
if (sess != null)
{
hibernateHolder.set(null);
try
{
sess.close();
}
catch (HibernateException ex) {
throw new ServletException(ex);
}
}
}
}
……
Hibernate Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
}
通过在doFilter中获取和关闭Session,并在周期内运行的所有对象(Filter链中其
余的Filter,及其覆盖的Servlet 和其他对象)对此Session 实例进行重用,保证了一个
Http Request处理过程中只占用一个Session,提高了整体性能表现。
在实际设计中,Session的重用做到线程级别一般已经足够,企图通过HttpSession实
现用户级的Session重用反而可能导致其他的问题。凡事不能过火,Session重用也一样。
===================================================================
jsp中页面关闭时关闭session,cookie,页面缓存
一、清除页面缓存
在jsp页里
<%response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
response.flushBuffer();%>
在html页里
二、清除cookie
<%
Cookie killMyCookie = new Cookie("mycookie", null);
killMyCookie.setMaxAge(0);
killMyCookie.setPath("/");
response.addCookie(killMyCookie);
%>
三、清除session
清除session方法
<%@ page language="java" %>
<%
session.invalidate();
%>
在页面关闭时清除session,需要捕获windows.onclose事件,再调用清除session方法
====================================================================
前两天看见有兄弟问cookie为什么删除不了,所以写了给小总结,希望对用cookie的各位兄弟有帮助
对于cookie,最主要的当然是读取和设置了,下面分两方面说明.
一、设置
Cookie是通过HttpServletResponse的addCookie方法加入到Set-Cookie应答头中的
例如:
Cookie userCookie = new Cookie("user", "admin");
response.addCookie(userCookie);
和设置有关系的还有以下两个重要方法
1.setMaxAge
设置Cookie过期之前的时间,以秒计。如果不设置该值,则Cookie只在当前会话内有效,而且这些Cookie不会保存到磁盘上。
注意:删除cookie就是通过该方法实现的。将要删除的cookie的过期之前的时间指定为0就可以达到删除该cookie的目的。
2.setPath
设置Cookie适用的路径。如果不指定路径,Cookie将返回给当前页面(JSP页面或者Servlet的映射)所在目录及其子目录下的所有页面。
注意:
A:所有的cookie都是有路径的
B:该方法设置的路径为客户端路径,即“/”代表服务器根目录,而不是WEB应用根目录
C:该方法设置路径时,“/myWeb/”与“/myWeb”是不同的,要特别注意;前者可以关联到服务器的myWeb目录下,而或者则不可以。
D:该方法设置路径时,没有相对目录可言,即不论在哪个目录下设置setPath(“/myWeb/”),该cookie都将关联到服务器的myWeb目录下(setPath(“/myWeb”)则不可以),而不是当前目录的myWeb的子目录下;同样,设置setPath(“myWeb/”)和setPath(“myWeb”)也不能关联到当前目录的myWeb的子目录下
这里有个奇怪的例子,就是在一个web应用下设置的cookie可以在另一个web应用下获得(两个web应用在同一个服务器下)
目录结构:在服务器根目录上有web1和web2两个目录,在web1下有setcookie.jsp和getcookie.jsp、在web2下有getcookie.jsp
web1下的setcookie.jsp
<%
Cookie userCookie = new Cookie("user", "admin");
userCookie.setMaxAge(24*60*60);
userCookie.setPath("/web2/");
response.addCookie(userCookie);
%>
web1下的getcookie.jsp
<%
Cookie[] cookie = request.getCookies();
String user = new String();
if ( cookie != null ) {
for (int i = 0; i < cookie.length; i++) {
Cookie myCookie = cookie[i];
if (myCookie.getName().equals("user")) {
user = myCookie.getValue();
}
}
}
out.println("user = " + user);
%>
web2下的getcookie.jsp
<%
Cookie[] cookie = request.getCookies();
String user = new String();
if ( cookie != null ) {
for (int i = 0; i < cookie.length; i++) {
Cookie myCookie = cookie[i];
if (myCookie.getName().equals("user")) {
user = myCookie.getValue();
}
}
}
out.println("user = " + user);
%>
先访问web1下的setcookie.jsp,然后分别访问web1和web2下面的getcookie.jsp文件,你会发现奇怪的现象,web1下的getcookie.jsp中user为空而web2下的getcookie.jsp中user却有值,这就实现了从一个web应用下设置的cookie在另一个web应用下获得。
大多数人删除cookie不成功都是因为目录原因。一个典型的原因是在某一个目录中设置了cookie(没有调用setPath方法)却在另一个目录中删除该cookie(其实是调用setMaxAge方法)
二、读取
从客户端读取Cookie时调用的是HttpServletRequest的getCookies方法。该方法返回一个与HTTP请求头中的内容对应的Cookie对象数组。得到这个数组之后,一般是用循环访问其中的各个元素,调用getName检查各个Cookie的名字,直至找到目标Cookie。然后对这个目标Cookie调用getValue,根据获得的结果进行其他处理。
注意:若JSP和Servlet所在目录(Servlet为其映射目录)的父目录中有同名cookie,则request.getCookie()方法得到的Cookie数组中保存的是其父目录中的cookie的信息;