public class StandardManager extends ManagerBase implements Lifecycle, PropertyChangeListener, Runnable { //检验过期时间的时间间隔 private int checkInterval = 60; //类描述信息(名称/版本号) private static final String info = "StandardManager/1.0"; //为支持该组件的生命周期事件触发器 protected LifecycleSupport lifecycle = new LifecycleSupport(this); //允许的session活跃数。如果为-1则数量无限制 private int maxActiveSessions = -1; //描述类名称(为日志准备) protected static String name = "StandardManager"; //session持久化到磁盘的路径名称(如果是相对路径则会根据javax.servlet.context.tempdir) //绝对定位session位置 private String pathname = "SESSIONS.ser"; //容器是否被启动 private boolean started = false; //后台线程 private Thread thread = null; //后台线程是否完成标示 private boolean threadDone = false; //为后台线程注册的名称 private String threadName = "StandardManager"; public int getCheckInterval() { return (this.checkInterval); } public void setCheckInterval(int checkInterval) { int oldCheckInterval = this.checkInterval; this.checkInterval = checkInterval; support.firePropertyChange("checkInterval", new Integer(oldCheckInterval), new Integer(this.checkInterval)); } //设置管理器关联的容器,如果是上下文类型则会启动属性变更监听事件 public void setContainer(Container container) { // De-register from the old Container (if any) //写在旧的容器,触发属性变更监听器的remove事件 if ((this.container != null) && (this.container instanceof Context)) ((Context) this.container).removePropertyChangeListener(this); //为父类设置容器信息 super.setContainer(container); //注册新的容器 // Register with the new Container (if any) if ((this.container != null) && (this.container instanceof Context)) { setMaxInactiveInterval ( ((Context) this.container).getSessionTimeout()*60 ); ((Context) this.container).addPropertyChangeListener(this); } } //返回类描述信息 public String getInfo() { return (this.info); } public int getMaxActiveSessions() { return (this.maxActiveSessions); } public void setMaxActiveSessions(int max) { int oldMaxActiveSessions = this.maxActiveSessions; this.maxActiveSessions = max; support.firePropertyChange("maxActiveSessions", new Integer(oldMaxActiveSessions), new Integer(this.maxActiveSessions)); } //返实现类的short name public String getName() { return (name); } //返回持久化session路径 public String getPathname() { return (this.pathname); } public void setPathname(String pathname) { String oldPathname = this.pathname; this.pathname = pathname; support.firePropertyChange("pathname", oldPathname, this.pathname); } //根据Mnager的属性指定的缺省的设置创建并返回新的session对象。该session id将在该 //方法中被设置。如果session无法正确创建,则返回null public Session createSession() { if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) throw new IllegalStateException (sm.getString("standardManager.createSession.ise")); return (super.createSession()); } //重新加载持久化文件里面的session信息。如果不支持持久化,则会返回 public void load() throws ClassNotFoundException, IOException { if (debug >= 1) log("Start: Loading persisted sessions"); //初始化数据结构 recycled.clear(); sessions.clear(); // 开启指定路径的文件 File file = file(); if (file == null) return; if (debug >= 1) log(sm.getString("standardManager.loading", pathname)); FileInputStream fis = null; ObjectInputStream ois = null; Loader loader = null; ClassLoader classLoader = null; try { //设置文件输入流 fis = new FileInputStream(file.getAbsolutePath()); //设置输入流二进制缓存 BufferedInputStream bis = new BufferedInputStream(fis); if (container != null) //获取容器的加载器 loader = container.getLoader(); if (loader != null) //获取容器加载器中的类加载器 classLoader = loader.getClassLoader(); if (classLoader != null) { if (debug >= 1) log("Creating custom object input stream for class loader " + classLoader); //bis子类对象 ois = new CustomObjectInputStream(bis, classLoader); } else { if (debug >= 1) log("Creating standard object input stream"); ois = new ObjectInputStream(bis); } } catch (FileNotFoundException e) { if (debug >= 1) log("No persisted data file found"); return; } catch (IOException e) { log(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } //加载之前卸载的活跃sessions信息 synchronized (sessions) { try { //获得被卸载的数量 Integer count = (Integer) ois.readObject(); int n = count.intValue(); if (debug >= 1) log("Loading " + n + " persisted sessions"); for (int i = 0; i < n; i++) { StandardSession session = new StandardSession(this); //从ois中获取属性值并设置到session中 session.readObjectData(ois); //设置管理器 session.setManager(this); sessions.put(session.getId(), session); //将session置为活跃 ((StandardSession) session).activate(); } } catch (ClassNotFoundException e) { log(sm.getString("standardManager.loading.cnfe", e), e); if (ois != null) { try { //关闭输入流 ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } catch (IOException e) { log(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { //关闭一次出错在关闭一次 ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } finally { // 关闭输入流 try { if (ois != null) ois.close(); } catch (IOException f) { // ignored } // 删除持久化文件 if (file != null && file.exists() ) file.delete(); } } if (debug >= 1) log("Finish: Loading persisted sessions"); } //在不同的持久化机制中保存现有的活跃session信息,如果不支持持久化则返回 public void unload() throws IOException { if (debug >= 1) log("Unloading persisted sessions"); // 打开持久化文集 File file = file(); if (file == null) return; if (debug >= 1) log(sm.getString("standardManager.unloading", pathname)); FileOutputStream fos = null; ObjectOutputStream oos = null; try { //文件输出流 fos = new FileOutputStream(file.getAbsolutePath()); //ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream //该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中, // 而不必针对每次字节写入调用底层系统。 oos = new ObjectOutputStream(new BufferedOutputStream(fos)); } catch (IOException e) { log(sm.getString("standardManager.unloading.ioe", e), e); if (oos != null) { try { oos.close(); } catch (IOException f) { ; } oos = null; } throw e; } // 在写入session信息之前,首先写入活跃session个数信息。和读取过程对应 ArrayList list = new ArrayList(); synchronized (sessions) { if (debug >= 1) log("Unloading " + sessions.size() + " sessions"); try { //将活跃的session个数信息写入到写出流 oos.writeObject(new Integer(sessions.size())); Iterator elements = sessions.values().iterator(); while (elements.hasNext()) { StandardSession session = (StandardSession) elements.next(); //将session信息放入到list中 list.add(session); //将session置为非活跃 ((StandardSession) session).passivate(); //序列化session各个字段,并输出到输出流 session.writeObjectData(oos); } } catch (IOException e) { log(sm.getString("standardManager.unloading.ioe", e), e); if (oos != null) { try { oos.close(); } catch (IOException f) { ; } oos = null; } throw e; } } // Flush and close the output stream try { //flush 缓冲区,让缓冲区数据全部写出去 oos.flush(); //关闭输出流 oos.close(); //将对象引用也置为null(值得学习借鉴) oos = null; } catch (IOException e) { if (oos != null) { try { //关闭时候出错再关一次 oos.close(); } catch (IOException f) { ; } oos = null; } throw e; } // 将我们刚刚持久化的所有session置为过期Expire all the sessions we just wrote if (debug >= 1) log("Expiring " + list.size() + " persisted sessions"); Iterator expires = list.iterator(); while (expires.hasNext()) { StandardSession session = (StandardSession) expires.next(); try { session.expire(false); } catch (Throwable t) { ; } } if (debug >= 1) log("Unloading complete"); } // ------------------------------------------------------ Lifecycle Methods /** * Add a lifecycle event listener to this component. * * @param listener The listener to add */ public void addLifecycleListener(LifecycleListener listener) { lifecycle.addLifecycleListener(listener); } /** * Get the lifecycle listeners associated with this lifecycle. If this * Lifecycle has no listeners registered, a zero-length array is returned. */ public LifecycleListener[] findLifecycleListeners() { return lifecycle.findLifecycleListeners(); } /** * Remove a lifecycle event listener from this component. * * @param listener The listener to remove */ public void removeLifecycleListener(LifecycleListener listener) { lifecycle.removeLifecycleListener(listener); } /** * Prepare for the beginning of active use of the public methods of this * component. This method should be called after <code>configure()</code>, * and before any of the public methods of the component are utilized. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ public void start() throws LifecycleException { if (debug >= 1) log("Starting"); // Validate and update our current component state if (started) throw new LifecycleException (sm.getString("standardManager.alreadyStarted")); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; // Force initialization of the random number generator if (debug >= 1) log("Force random number initialization starting"); String dummy = generateSessionId(); if (debug >= 1) log("Force random number initialization completed"); // Load unloaded sessions, if any try { load(); } catch (Throwable t) { log(sm.getString("standardManager.managerLoad"), t); } // Start the background reaper thread threadStart(); } /** * Gracefully terminate the active use of the public methods of this * component. This method should be the last one called on a given * instance of this component. * * @exception LifecycleException if this component detects a fatal error * that needs to be reported */ public void stop() throws LifecycleException { if (debug >= 1) log("Stopping"); // Validate and update our current component state if (!started) throw new LifecycleException (sm.getString("standardManager.notStarted")); lifecycle.fireLifecycleEvent(STOP_EVENT, null); started = false; // Stop the background reaper thread threadStop(); // Write out sessions try { unload(); } catch (IOException e) { log(sm.getString("standardManager.managerUnload"), e); } // Expire all active sessions Session sessions[] = findSessions(); for (int i = 0; i < sessions.length; i++) { StandardSession session = (StandardSession) sessions[i]; if (!session.isValid()) continue; try { session.expire(); } catch (Throwable t) { ; } } // Require a new random number generator if we are restarted this.random = null; } // ----------------------------------------- PropertyChangeListener Methods /** * Process property change events from our associated Context. * * @param event The property change event that has occurred */ public void propertyChange(PropertyChangeEvent event) { // Validate the source of this event if (!(event.getSource() instanceof Context)) return; Context context = (Context) event.getSource(); // Process a relevant property change if (event.getPropertyName().equals("sessionTimeout")) { try { setMaxInactiveInterval ( ((Integer) event.getNewValue()).intValue()*60 ); } catch (NumberFormatException e) { log(sm.getString("standardManager.sessionTimeout", event.getNewValue().toString())); } } } //返回持久化文件路径下的文件对象 private File file() { //持久化路径为空则返回null if (pathname == null) return (null); //创建持久化文件 File file = new File(pathname); //如果不是绝对路径,则根据上下文中的工作路径 if (!file.isAbsolute()) { if (container instanceof Context) { ServletContext servletContext = ((Context) container).getServletContext(); File tempdir = (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR); if (tempdir != null) file = new File(tempdir, pathname); } } // if (!file.isAbsolute()) // return (null); return (file); } //将所有的过期session置为无效 /** * Invalidate all sessions that have expired. */ private void processExpires() { //获取当前时间 long timeNow = System.currentTimeMillis(); //获取到所有的活跃session Session sessions[] = findSessions(); //遍历session容器 for (int i = 0; i < sessions.length; i++) { StandardSession session = (StandardSession) sessions[i]; //如果该session信息无效,则继续 if (!session.isValid()) continue; //获取session的最长非活跃事件 int maxInactiveInterval = session.getMaxInactiveInterval(); //如果小于 0 ,则session始终保持活跃 if (maxInactiveInterval < 0) continue; int timeIdle = // 计算session未被访问的时间间隔 (int) ((timeNow - session.getLastAccessedTime()) / 1000L); //如果时间间隔大于最大非活跃时间间隔 if (timeIdle >= maxInactiveInterval) { try { //将session置为过期 session.expire(); } catch (Throwable t) { log(sm.getString("standardManager.expireException"), t); } } } } //将线程休眠指定时间 private void threadSleep() { try { Thread.sleep(checkInterval * 1000L); } catch (InterruptedException e) { ; } } //开启一个后台线程,周期性地检查session过期时间 private void threadStart() { //如果线程不为空,则返回。说明该线程任务尚未完成。不能开启新的任务 if (thread != null) return; //设置后台线程属性 threadDone = false; threadName = "StandardManager[" + container.getName() + "]"; thread = new Thread(this, threadName); thread.setDaemon(true); thread.setContextClassLoader(container.getLoader().getClassLoader()); thread.start(); } //关闭周期性检查session过期时间的线程 private void threadStop() { if (thread == null) return; threadDone = true; thread.interrupt(); try { thread.join(); } catch (InterruptedException e) { ; } thread = null; } // ------------------------------------------------------ Background Thread /** * The background thread that checks for session timeouts and shutdown. */ public void run() { // Loop until the termination semaphore is set while (!threadDone) { threadSleep(); processExpires(); } }
调用关系代码:
//invoke: http://localhost:8080/myApp/Session //System.getProperty("user.dir")工作目录 System.setProperty("catalina.base", System.getProperty("user.dir")); //连接器 Connector connector = new HttpConnector(); //servlet包装容器 Wrapper wrapper1 = new SimpleWrapper(); wrapper1.setName("Session"); wrapper1.setServletClass("SessionServlet"); //上下文容器 Context context = new StandardContext(); // StandardContext's start method adds a default mapper //设置上下文的uri路径 context.setPath("/myApp"); //设置上下文的文件路径(可以是相对路径、绝对路径、URL) context.setDocBase("myApp"); //将包装器放置到上下文的子容器中 context.addChild(wrapper1); //mapping 设置。 context.addServletMapping("/myApp/Session", "Session"); //添加配置监听器, LifecycleListener listener = new SimpleContextConfig(); ((Lifecycle) context).addLifecycleListener(listener); //加载器(里面包含类加载器) Loader loader = new WebappLoader(); // associate the loader with the Context context.setLoader(loader); //连接器包含容器信息 connector.setContainer(context); //session管理器 Manager manager = new StandardManager(); //设置上下文的session管理器 context.setManager(manager); try { //连接器初始化 connector.initialize(); //连接器开始 ((Lifecycle) connector).start(); //上下文容器开启 ((Lifecycle) context).start(); //可以再控制台输入数据触发容器关闭操作 System.in.read(); ((Lifecycle) context).stop(); } catch (Exception e) { e.printStackTrace(); } }