zoukankan      html  css  js  c++  java
  • How Tomcat Works(十二)

    tomcat容器通过一个称为Session管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口表示;Session管理器必须与一个Context容器相关联(需要用到Context容器的相关上下文或方法)。

    默认情况下,Session管理器会将其所管理的 Session对象存放在内存中,不过在tomcat中,Session管理器也库将Session对象持久化,存储到文件存储器或通过JDBC写入到数据库中。

    下面我们来分析具体实现,在servlet编程方面中,Session对象由javax.servlet.http.HttpSession接口表示;在tomcat中该接口的标准实现是org.apache.catalina.session包下的StandardSession类,该类同时实现了org.apache.catalina.Session接口,在tomcat内部供Session管理器使用;而实际交给servlet实例使用的是Session接口的外观类StandardSessionFacade

    下面是Session管理器内部使用的Session接口

    public interface Session {
        
        public static final String SESSION_CREATED_EVENT = "createSession";
       
        public static final String SESSION_DESTROYED_EVENT = "destroySession";
       
        public String getAuthType();
       
        public void setAuthType(String authType);
    
        public long getCreationTime();
    
        public void setCreationTime(long time);
    
        public String getId();
    
        public void setId(String id);
    
        public String getInfo();
    
        public long getLastAccessedTime();
    
        public Manager getManager();
    
        public void setManager(Manager manager);
    
        public int getMaxInactiveInterval();
    
        public void setMaxInactiveInterval(int interval);
    
        public void setNew(boolean isNew);
    
        public Principal getPrincipal();
    
        public void setPrincipal(Principal principal);
    
        public HttpSession getSession();
    
        public void setValid(boolean isValid);
    
        public boolean isValid();
    
        public void access();
    
        public void addSessionListener(SessionListener listener);
     
        public void expire();
    
        public Object getNote(String name);
    
        public Iterator getNoteNames();
    
        public void recycle();
    
        public void removeNote(String name);
    
        public void removeSessionListener(SessionListener listener);
    
        public void setNote(String name, Object value);
    
    }

    Session对象总是存在于Session管理器中,可以通过setManager()方法将Session实例 与某个Session管理器相关联;Session管理器可以通过setId()方法设置Session标识符;同时会调用getLastAccessedTime()方法判断一个Session对象的有效性;setValid()方法用于重置该Session对象的有效性;每当访问一个Session实例时,会调用access()方法来修改Session对象的最后访问时间;最后,Session管理器会调用Session对象的expire()方法使其过期,通过getSession()方法获取一个经过外观类StandardSessionFacade包装的HttpSession对象

    StandardSession类是Session接口的标准实现,同时实现了javax.servlet.http.HttpSession接口和java.lang.Serializable接口

    (注:StandardSession类实现HttpSession接口方法的实现基本上都依赖于实现Session接口的方法对StandardSession实例的填充,因此我们可以想象,StandardSession类基本上类似与适配器模式中的Adapter角色,实现了原类型(Session接口类型)到目标接口的转换(HttpSession接口))

    其构造函数接受一个Manager接口的实例,迫使Session对象必须拥有一个Session管理器实例

    public StandardSession(Manager manager) {
    
            super();
            this.manager = manager;
            if (manager instanceof ManagerBase)
                this.debug = ((ManagerBase) manager).getDebug();
    
        }

    下面是StandardSession实例的一些比较重要的私有成员变量

    //存储session的键值对
    private HashMap attributes = new HashMap();
    private transient String authType = null;
    //创建时间
    private long creationTime = 0L;
    //是否过期
    private transient boolean expiring = false;
    //外观类
    private transient StandardSessionFacade facade = null;
    //session标识
    private String id = null;
    //最后访问时间
    private long lastAccessedTime = creationTime;
    //监听器聚集
    private transient ArrayList listeners = new ArrayList();
    //session管理器
    private Manager manager = null;
    //清理session过期时间
    private int maxInactiveInterval = -1;
    private boolean isNew = false;
    private boolean isValid = false;
    //当前访问时间
    private long thisAccessedTime = creationTime;

    其中getSession()方法通过传入自身实例来创建外观类StandardSessionFacade实例

    public HttpSession getSession() {
    
            if (facade == null)
                facade = new StandardSessionFacade(this);
            return (facade);
    
        }

    如果session管理器中的某个Session对象在某个时间长度内都没有被访问的话,会被Session管理器设置为过期,这个时间长度是由变量maxInactiveInterval的值来指定

    public void expire(boolean notify) {
    
            // Mark this session as "being expired" if needed
            if (expiring)
                return;
            expiring = true;
            setValid(false);
    
            // Remove this session from our manager's active sessions
            if (manager != null)
                manager.remove(this);
    
            // Unbind any objects associated with this session
            String keys[] = keys();
            for (int i = 0; i < keys.length; i++)
                removeAttribute(keys[i], notify);
    
            // Notify interested session event listeners
            if (notify) {
                fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
            }
    
            // Notify interested application event listeners
            // FIXME - Assumes we call listeners in reverse order
            Context context = (Context) manager.getContainer();
            Object listeners[] = context.getApplicationListeners();
            if (notify && (listeners != null)) {
                HttpSessionEvent event =
                  new HttpSessionEvent(getSession());
                for (int i = 0; i < listeners.length; i++) {
                    int j = (listeners.length - 1) - i;
                    if (!(listeners[j] instanceof HttpSessionListener))
                        continue;
                    HttpSessionListener listener =
                        (HttpSessionListener) listeners[j];
                    try {
                        fireContainerEvent(context,
                                           "beforeSessionDestroyed",
                                           listener);
                        listener.sessionDestroyed(event);
                        fireContainerEvent(context,
                                           "afterSessionDestroyed",
                                           listener);
                    } catch (Throwable t) {
                        try {
                            fireContainerEvent(context,
                                               "afterSessionDestroyed",
                                               listener);
                        } catch (Exception e) {
                            ;
                        }
                        // FIXME - should we do anything besides log these?
                        log(sm.getString("standardSession.sessionEvent"), t);
                    }
                }
            }
    
            // We have completed expire of this session
            expiring = false;
            if ((manager != null) && (manager instanceof ManagerBase)) {
                recycle();
            }
    
        }

    上面方法中从Session管理器移除该session实例并触发一些事件,同时设置一些内部变量的值。

    为了传递一个session对象给servlet实例,tomcat的session管理器会实例化StandardSession类,并填充该session对象;不过,为了阻止servlet程序员访问StandardSession实例中的一些敏感方法,tomcat容器将StandardSession实例封装为StandardSessionFacade类型的实例(实现HttpRequest接口的HttpRequestBase类的doGetSession方法里面调用session管理器的createSession()方法返回StandardSession类的实例,然后调用StandardSession实例 的getSession()方法返回StandardSessionFacade类型实例),该类仅仅实现了javax.servlet.http.HttpSession接口中的方法,这样servlet程序员就不能将HttpSession对象向下转型为StandardSession类型

    下面接下来描述session管理器,session管理器是org.apache.catalina.Manager接口的实例,抽象类ManagerBase实现了Manager接口,提供了常见功能的实现;ManagerBase类有两个直接子类,分别为StandardManager类和PersistentManagerBase类

    当tomcat运行时,StandardManager实例将session对象存储在内存中;当tomcat关闭时,它会将当前内存中所有的session对象序列化存储到文件中;当在此运行tomcat时,又会将这些session对象重新载入内存。

    另外,继承自PersistentManagerBase类的session管理器会将session对象存储到辅助存储器中,包括PersistentManager类和DistributedManager类(tomcat4中)

    下面是Manager接口的方法声明:

    public interface Manager {
        
        public Container getContainer();
       
        public void setContainer(Container container);
        
        public DefaultContext getDefaultContext();
       
        public void setDefaultContext(DefaultContext defaultContext);
        
        public boolean getDistributable();
       
        public void setDistributable(boolean distributable);
       
        public String getInfo();
        
        public int getMaxInactiveInterval();
        
        public void setMaxInactiveInterval(int interval);
       
        public void add(Session session);
       
        public void addPropertyChangeListener(PropertyChangeListener listener);
        
        public Session createSession();
        
        public Session findSession(String id) throws IOException;
       
        public Session[] findSessions();
       
        public void load() throws ClassNotFoundException, IOException;
       
        public void remove(Session session);
       
        public void removePropertyChangeListener(PropertyChangeListener listener);
       
        public void unload() throws IOException;
    
    }

    提供了setContainer()方法使用session管理器与Context容器相关联;一起一下添加、移除session实例的方法;设置session对象的最长存活时间;最后, load()方法与unload()方法用于从辅助存储器加载session对象到内存和序列化session对象并持久化到辅助存储器中。

    ManagerBase类为一个抽象类,提供了一些公共方法的实现,包括创建session对象、移除session对象等;这些活动的session对象都存储在一个名为sessions的HashMap变量中

    protected HashMap sessions = new HashMap();

    下面是创建session实例的方法

     public Session createSession() {
    
            // Recycle or create a Session instance
            Session session = null;
            synchronized (recycled) {
                int size = recycled.size();
                if (size > 0) {
                    session = (Session) recycled.get(size - 1);
                    recycled.remove(size - 1);
                }
            }
            if (session != null)
                session.setManager(this);
            else
                session = new StandardSession(this);
    
            // Initialize the properties of the new session and return it
            session.setNew(true);
            session.setValid(true);
            session.setCreationTime(System.currentTimeMillis());
            session.setMaxInactiveInterval(this.maxInactiveInterval);
            String sessionId = generateSessionId();
            String jvmRoute = getJvmRoute();
            // @todo Move appending of jvmRoute generateSessionId()???
            if (jvmRoute != null) {
                sessionId += '.' + jvmRoute;
                session.setId(sessionId);
            }
            /*
            synchronized (sessions) {
                while (sessions.get(sessionId) != null)        // Guarantee uniqueness
                    sessionId = generateSessionId();
            }
            */
            session.setId(sessionId);
    
            return (session);
    
        }

    上面创建的是StandardSession类型实例,在实现HttpRequest接口的HttpRequestBase类的相关方法中,调用getSession()方法返回StandardSessionFacade类型实例

    创建session对象需要调用受保护的方法返回session对象的唯一标识符

     /**
         * Generate and return a new session identifier.
         */
        protected synchronized String generateSessionId() {
    
            // Generate a byte array containing a session identifier
            Random random = getRandom();
            byte bytes[] = new byte[SESSION_ID_BYTES];
            getRandom().nextBytes(bytes);
            bytes = getDigest().digest(bytes);
    
            // Render the result as a String of hexadecimal digits
            StringBuffer result = new StringBuffer();
            for (int i = 0; i < bytes.length; i++) {
                byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
                byte b2 = (byte) (bytes[i] & 0x0f);
                if (b1 < 10)
                    result.append((char) ('0' + b1));
                else
                    result.append((char) ('A' + (b1 - 10)));
                if (b2 < 10)
                    result.append((char) ('0' + b2));
                else
                    result.append((char) ('A' + (b2 - 10)));
            }
            return (result.toString());
    
        }

    该标识符生成之后,会发送到客户端cookies里面,使客户端cookies里面的JSESSIONID值与之一致

    其他相关操作session对象方法如下,容易理解

        public void add(Session session) {
    
            synchronized (sessions) {
                sessions.put(session.getId(), session);
            }
    
        }
    
        public Session findSession(String id) throws IOException {
    
            if (id == null)
                return (null);
            synchronized (sessions) {
                Session session = (Session) sessions.get(id);
                return (session);
            }
    
        }
        
        public Session[] findSessions() {
    
            Session results[] = null;
            synchronized (sessions) {
                results = new Session[sessions.size()];
                results = (Session[]) sessions.values().toArray(results);
            }
            return (results);
    
        }
       
        public void remove(Session session) {
    
            synchronized (sessions) {
                sessions.remove(session.getId());
            }
    
        }

    StandardManager类是Manager接口的标准实现,继承自上面的ManagerBase抽象类,同时实现了Lifecycle接口,这有可以由其相关联的Context容器来启动和关闭,在Context容器调用它的start()方法和stop()方法时,会调用load()从辅助存储器加载session对象到内存和调用unload()方法从内存持久化session对象到辅助存储器

    同时session管理器还负责销毁那些失效的session对象,这是由一个专门的线程来实现的,StandardManager类实现了Runnable接口

    /**
         * The background thread that checks for session timeouts and shutdown.
         */
        public void run() {
    
            // Loop until the termination semaphore is set
            while (!threadDone) {
                threadSleep();
                processExpires();
            }
    
        }

    在线程休眠指定时间间隔后,调用processExpires()方法清理过期session对象

    /**
         * Invalidate all sessions that have expired.
         */
        private void processExpires() {
    
            long timeNow = System.currentTimeMillis();
            Session sessions[] = findSessions();
    
            for (int i = 0; i < sessions.length; i++) {
                StandardSession session = (StandardSession) sessions[i];
                if (!session.isValid())
                    continue;
                int maxInactiveInterval = session.getMaxInactiveInterval();
                if (maxInactiveInterval < 0)
                    continue;
                int timeIdle = // Truncate, do not round up
                    (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
                if (timeIdle >= maxInactiveInterval) {
                    try {
                        session.expire();
                    } catch (Throwable t) {
                        log(sm.getString("standardManager.expireException"), t);
                    }
                }
            }
    
        }

    我们可以看到,session对象的过期时间实际是session对象本身的成员变量的值

    int maxInactiveInterval = session.getMaxInactiveInterval()

    最后,servlet程序员需要调用javax.servlet.http.HttpSerletRequest接口的getSession()方法获取Session对象,当调用getSession()方法时,request对象必须调用与Context容器相关联的session管理器(创建session对象或返回一个已存在色session对象);request对象为了能够访问Session管理器,它必须能够访问Context容器。因此在SimpleWrapperValve类的invoke()方法中,需要调用org.apache.catalina.Request接口的setContext()方法传入Context容器实例

    public void invoke(Request request, Response response, ValveContext valveContext)
        throws IOException, ServletException {
    
        SimpleWrapper wrapper = (SimpleWrapper) getContainer();
        ServletRequest sreq = request.getRequest();
        ServletResponse sres = response.getResponse();
        Servlet servlet = null;
        HttpServletRequest hreq = null;
        if (sreq instanceof HttpServletRequest)
          hreq = (HttpServletRequest) sreq;
        HttpServletResponse hres = null;
        if (sres instanceof HttpServletResponse)
          hres = (HttpServletResponse) sres;
    
        //-- new addition -----------------------------------
        Context context = (Context) wrapper.getParent();
        request.setContext(context);
        //-------------------------------------
        // Allocate a servlet instance to process this request
        try {
          servlet = wrapper.allocate();
          if (hres!=null && hreq!=null) {
            servlet.service(hreq, hres);
          }
          else {
            servlet.service(sreq, sres);
          }
        }
        catch (ServletException e) {
        }
      }

    同时在org.apache.catalina.connector.HttpRequestBase类的私有方法diGetSession()里面会调用Context接口的getManager()方法来获取session管理器对象

    private HttpSession doGetSession(boolean create) {
            // There cannot be a session if no context has been assigned yet
            if (context == null)
                return (null);
    
            // Return the current session if it exists and is valid
            if ((session != null) && !session.isValid())
                session = null;
            if (session != null)
                return (session.getSession());
    
    
            // Return the requested session if it exists and is valid
            Manager manager = null;
            if (context != null)
                manager = context.getManager();
    
            if (manager == null)
                return (null);      // Sessions are not supported
    
            if (requestedSessionId != null) {
                try {
                    session = manager.findSession(requestedSessionId);
                } catch (IOException e) {
                    session = null;
                }
                if ((session != null) && !session.isValid())
                    session = null;
                if (session != null) {
                    return (session.getSession());
                }
            }
    
            // Create a new session if requested and the response is not committed
            if (!create)
                return (null);
            if ((context != null) && (response != null) &&
                context.getCookies() &&
                response.getResponse().isCommitted()) {
                throw new IllegalStateException
                  (sm.getString("httpRequestBase.createCommitted"));
            }
    
            session = manager.createSession();
            if (session != null)
                return (session.getSession());
            else
                return (null);
    
        }

    注意里面实质是通过StandardSession实例的getSession()方法获取一个经过外观类StandardSessionFacade包装的HttpSession对象

    --------------------------------------------------------------------------- 

    本系列How Tomcat Works系本人原创 

    转载请注明出处 博客园 刺猬的温驯 

    本人邮箱: chenying998179#163.com (#改为@

    本文链接http://www.cnblogs.com/chenying99/p/3237390.html

  • 相关阅读:
    java 读取配置文件
    oracle sql 截取表中某一字段的部分作为该字段查询结果
    大数据课堂
    网站保存
    Tensorflow+Keras 深度学习人工智能实践应用 Chapter Two 深度学习原理
    Tensorflow+Keras 深度学习人工智能实践应用 Chapter One人工智能 机器学习与深度学习简介
    Python 机器学习及实践 Coding 无监督学习经典模型 数据聚类 and 特征降维
    博客地址的保存
    备注
    个人笔记-
  • 原文地址:https://www.cnblogs.com/chenying99/p/3237390.html
Copyright © 2011-2022 走看看