【问题标题】:Multiple sessions for one servlet in JavaJava中一个servlet的多个会话
【发布时间】:2010-12-04 06:33:06
【问题描述】:

我有一个 servlet 负责处理多个站点,因此我希望为不同的站点设置不同的会话,即使是同一个用户。

在 Java 中是否对此有任何支持,或者我是否需要在属性名称前加上前缀?我想前缀不是一个好主意。

/Br Johannes

【问题讨论】:

  • 我可以手动设置路径来帮助解决这种情况吗?
  • 请定义术语“多个站点”
  • 是的,我同意,我的定义很糟糕:)。同一个 servlet 将根据不同的 url 参数显示不同的 html 页面。对于不同的 url 参数,我希望有不同的会话,即使它是同一个用户。

标签: java session


【解决方案1】:

这不能仅基于 URL 参数在 servlet 容器中完成;你必须自己做。但是,管理“单独”会话的最简单方法是通过过滤器,而不是处理 servlet 中的属性前缀:

  1. 为 HttpSession 编写一个简单的包装类。让它持有一个属性映射并通过该映射返回所有属性/值方法;将所有其他方法委托给您要包装的实际会话。覆盖 invalidate() 方法以删除会话包装,而不是终止整个“真实”会话。
  2. 编写一个servlet过滤器;映射它以拦截所有适用的 URL。
  3. 将会话包装器的集合作为真实会话中的属性进行维护。
  4. 在您的过滤器的 doFilter() 方法中,从集合中提取适当的会话包装器,并将其注入到您通过链下传递的请求中,方法是将原始请求包装到其 getSession() 方法被覆盖的 HttpServletRequestWrapper 中。
  5. 您的 servlet / JSP / 等...将享受“单独的”会话。

请注意,Sessions 的“lastAccessedTime”与此方法共享。如果您需要将它们分开,您必须编写自己的代码来维护此设置并让会话包装器过期。

【讨论】:

    【解决方案2】:

    我最近也遇到了这个问题,我按照 ChssPly76 的建议来解决它。我想我会在这里发布我的结果以提供参考实现。它还没有经过广泛的测试,所以如果你发现任何弱点,请告诉我。

    我假设对 servlet 的每个请求都包含一个名为 uiid 的参数,它代表一个用户 ID。每次单击打开新窗口的链接时,请求者必须跟踪发送新 ID。在我的情况下,这已经足够了,但您可以在这里随意使用任何其他(也许更安全)的方法。此外,我使用的是 Tomcat 7 或 8。在使用不同的 servlet 容器时,您可能需要扩展其他类,但 API 不应更改太多。

    在下文中,创建的会话称为子会话,原始容器管理的会话是父会话。该实现由以下五个类组成:

    SingleSessionManager 跟踪所有子会话的创建、分发和清理。它通过充当一个 servlet 过滤器来做到这一点,该过滤器将 ServletRequest 替换为一个返回适当子会话的包装器。调度程序会定期检查过期的子会话……是的,它是一个单例。抱歉,我还是喜欢他们。

    package session;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.UUID;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.ScheduledFuture;
    import java.util.concurrent.TimeUnit;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    /**
     * A singleton class that manages multiple sessions on top of a regular container managed session.
     * See web.xml for information on how to enable this.
     *
     */
    public class SingleSessionManager implements Filter {
    
        /**
         * The default session timeout in seconds to be used if no explicit timeout is provided.
         */
        public static final int DEFAULT_TIMEOUT = 900;
    
        /**
         * The default interval for session validation checks in seconds to be used if no explicit
         * timeout is provided.
         */
        public static final int DEFAULT_SESSION_INVALIDATION_CHECK = 15;
    
        private static SingleSessionManager instance;
    
        private ScheduledExecutorService scheduler;
        protected int timeout;
        protected long sessionInvalidationCheck;
    
        private Map<SubSessionKey, HttpSessionWrapper> sessions = new ConcurrentHashMap<SubSessionKey, HttpSessionWrapper>();
    
        public SingleSessionManager() {
            sessionInvalidationCheck = DEFAULT_SESSION_INVALIDATION_CHECK;
            timeout = DEFAULT_TIMEOUT;
        }
    
        public static SingleSessionManager getInstance() {
            if (instance == null) {
                instance = new SingleSessionManager();
            }
            return instance;
        }
    
        @Override
        public void destroy() {
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper((HttpServletRequest) request);
            chain.doFilter(wrapper,  response);
        }
    
        @Override
        public void init(FilterConfig cfg) throws ServletException {
            String timeout = cfg.getInitParameter("sessionTimeout");
            if (timeout != null && !timeout.trim().equals("")) {
                getInstance().timeout = Integer.parseInt(timeout) * 60;
            }
    
            String sessionInvalidationCheck = cfg.getInitParameter("sessionInvalidationCheck");
            if (sessionInvalidationCheck != null && !sessionInvalidationCheck.trim().equals("")) {
                getInstance().sessionInvalidationCheck = Long.parseLong(sessionInvalidationCheck);
            }
    
            getInstance().startSessionExpirationScheduler();
        }
    
        /**
         * Create a new session ID.
         * 
         * @return A new unique session ID.
         */
        public String generateSessionId() {
            return UUID.randomUUID().toString();
        }
    
        protected void startSessionExpirationScheduler() {
            if (scheduler == null) {
                scheduler = Executors.newScheduledThreadPool(1);
                final Runnable sessionInvalidator = new Runnable() {
                    public void run() {
                        SingleSessionManager.getInstance().destroyExpiredSessions();
                    }
                };
                final ScheduledFuture<?> sessionInvalidatorHandle =
                        scheduler.scheduleAtFixedRate(sessionInvalidator
                                , this.sessionInvalidationCheck
                                , this.sessionInvalidationCheck
                                , TimeUnit.SECONDS);
            }
        }
    
        /**
         * Get the timeout after which a session will be invalidated.
         * 
         * @return The timeout of a session in seconds.
         */
        public int getSessionTimeout() {
            return timeout;
        }
    
        /**
         * Retrieve a session.
         * 
         * @param uiid
         *            The user id this session is to be associated with.
         * @param create
         *            If <code>true</code> and no session exists for the given user id, a new session is
         *            created and associated with the given user id. If <code>false</code> and no
         *            session exists for the given user id, no new session will be created and this
         *            method will return <code>null</code>.
         * @param originalSession
         *            The original backing session created and managed by the servlet container.
         * @return The session associated with the given user id if this session exists and/or create is
         *         set to <code>true</code>, <code>null</code> otherwise.
         */
        public HttpSession getSession(String uiid, boolean create, HttpSession originalSession) {
            if (uiid != null) {
                SubSessionKey key = new SubSessionKey(originalSession.getId(), uiid);
                if (!sessions.containsKey(key) && create) {
                    HttpSessionWrapper sw = new HttpSessionWrapper(uiid, originalSession);
                    sessions.put(key, sw);
                }
                HttpSessionWrapper session = sessions.get(key);
                session.setLastAccessedTime(System.currentTimeMillis());
                return session;
            }
            return null;
        }
    
        public HttpSessionWrapper removeSession(SubSessionKey key) {
            return sessions.remove(key);
        }
    
        /**
         * Destroy a session, freeing all it's resources.
         * 
         * @param session
         *            The session to be destroyed.
         */
        public void destroySession(HttpSessionWrapper session) {
            String uiid = ((HttpSessionWrapper)session).getUiid();
            SubSessionKey key = new SubSessionKey(session.getOriginalSession().getId(), uiid);
            HttpSessionWrapper w = getInstance().removeSession(key);
            if (w != null) {
                System.out.println("Session " + w.getId() + " with uiid " + uiid + " was destroyed.");
            } else {
                System.out.println("uiid " + uiid + " does not have a session.");
            }
        }
    
        /**
         * Destroy all session that are expired at the time of this method call.
         */
        public void destroyExpiredSessions() {
            List<HttpSessionWrapper> markedForDelete = new ArrayList<HttpSessionWrapper>();
            long time = System.currentTimeMillis() / 1000;
            for (HttpSessionWrapper session : sessions.values()) {
                if (time - (session.getLastAccessedTime() / 1000) >= session.getMaxInactiveInterval()) {
                    markedForDelete.add(session);
                }
            }
            for (HttpSessionWrapper session : markedForDelete) {
                destroySession(session);
            }
        }
    
        /**
         * Remove all subsessions that were created from a given parent session.
         * 
         * @param originalSession
         *            All subsessions created with this session as their parent session will be
         *            invalidated.
         */
        public void clearAllSessions(HttpSession originalSession) {
            Iterator<HttpSessionWrapper> it = sessions.values().iterator();
            while (it.hasNext()) {
                HttpSessionWrapper w = it.next();
                if (w.getOriginalSession().getId().equals(originalSession.getId())) {
                    destroySession(w);
                }
            }
        }
    
        public void setSessionTimeout(int timeout) {
            this.timeout = timeout;
        }
    
    }
    

    子会话由 SubSessionKey 标识。这些关键对象取决于父会话的 uiid 和 ID。

    package session;
    
    /**
     * Key object for identifying a subsession.
     *
     */
    public class SubSessionKey {
    
        private String sessionId;
        private String uiid;
    
        /**
         * Create a new instance of {@link SubSessionKey}.
         * 
         * @param sessionId
         *            The session id of the parent session.
         * @param uiid
         *            The users's id this session is associated with.
         */
        public SubSessionKey(String sessionId, String uiid) {
            super();
            this.sessionId = sessionId;
            this.uiid = uiid;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode());
            result = prime * result + ((uiid == null) ? 0 : uiid.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SubSessionKey other = (SubSessionKey) obj;
            if (sessionId == null) {
                if (other.sessionId != null)
                    return false;
            } else if (!sessionId.equals(other.sessionId))
                return false;
            if (uiid == null) {
                if (other.uiid != null)
                    return false;
            } else if (!uiid.equals(other.uiid))
                return false;
            return true;
        }
    
        @Override
        public String toString() {
            return "SubSessionKey [sessionId=" + sessionId + ", uiid=" + uiid + "]";
        }
    
    }
    

    HttpServletRequestWrapper 包装了一个 HttpServletRequest 对象。除了 getSession 方法之外,所有方法都被重定向到封装的请求,该方法将根据此请求参数中的用户 ID 返回 HttpSessionWrapper

    package session;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    /**
     * Wrapper class that wraps a {@link HttpServletRequest} object. All methods are redirected to the
     * wrapped request except for the <code>getSession</code> which will return an
     * {@link HttpSessionWrapper} depending on the user id in this request's parameters.
     *
     */
    public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {
    
        private HttpServletRequest req;
    
        public HttpServletRequestWrapper(HttpServletRequest req) {
            super(req);
            this.req = req;
        }
    
        @Override
        public HttpSession getSession() {
            return getSession(true);
        }
    
        @Override
        public HttpSession getSession(boolean create) {
            String[] uiid = getParameterMap().get("uiid");
            if (uiid != null && uiid.length >= 1) {
                return SingleSessionManager.getInstance().getSession(uiid[0], create, req.getSession(create));
            }
            return req.getSession(create);
        }
    }
    

    HttpSessionWrapper 代表一个子会话。

    package session;
    
    import java.util.Collections;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    
    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.HttpSessionContext;
    
    /**
     * Implementation of a HttpSession. Each instance of this class is created around a container
     * managed parent session with it's lifetime linked to it's parent's.
     *
     */
    @SuppressWarnings("deprecation")
    public class HttpSessionWrapper implements HttpSession {
    
        private Map<String, Object> attributes;
        private Map<String, Object> values;
        private long creationTime;
        private String id;
        private String uiid;
        private boolean isNew;
        private long lastAccessedTime;
        private HttpSession originalSession;
    
        public HttpSessionWrapper(String uiid, HttpSession originalSession) {
            creationTime = System.currentTimeMillis();
            lastAccessedTime = creationTime;
            id = SingleSessionManager.getInstance().generateSessionId();
            isNew = true;
            attributes = new HashMap<String, Object>();
            Enumeration<String> names = originalSession.getAttributeNames();
            while (names.hasMoreElements()) {
                String name = names.nextElement();
                attributes.put(name, originalSession.getAttribute(name));
            }
            values = new HashMap<String, Object>();
            for (String name : originalSession.getValueNames()) {
                values.put(name, originalSession.getValue(name));
            }
            this.uiid = uiid;
            this.originalSession = originalSession;
        }
    
        public String getUiid() {
            return uiid;
        }
    
        public void setNew(boolean b) {
            isNew = b;
        }
    
        public void setLastAccessedTime(long time) {
            lastAccessedTime = time;
        }
    
        @Override
        public Object getAttribute(String arg0) {
            return attributes.get(arg0);
        }
    
        @Override
        public Enumeration<String> getAttributeNames() {
            return Collections.enumeration(attributes.keySet());
        }
    
        @Override
        public long getCreationTime() {
            return creationTime;
        }
    
        @Override
        public String getId() {
            return id;
        }
    
        @Override
        public long getLastAccessedTime() {
            return lastAccessedTime;
        }
    
        @Override
        public int getMaxInactiveInterval() {
            return SingleSessionManager.getInstance().getSessionTimeout();
        }
    
        @Override
        public ServletContext getServletContext() {
            return originalSession.getServletContext();
        }
    
        @Override
        public HttpSessionContext getSessionContext() {
            return new HttpSessionContext() {
    
                @Override
                public Enumeration<String> getIds() {
                    return Collections.enumeration(new HashSet<String>());
                }
    
                @Override
                public HttpSession getSession(String arg0) {
                    return null;
                }
    
            };
        }
    
        @Override
        public Object getValue(String arg0) {
            return values.get(arg0);
        }
    
        @Override
        public String[] getValueNames() {
            return values.keySet().toArray(new String[values.size()]);
        }
    
        @Override
        public void invalidate() {
            SingleSessionManager.getInstance().destroySession(this);
        }
    
        @Override
        public boolean isNew() {
            return isNew;
        }
    
        @Override
        public void putValue(String arg0, Object arg1) {
            values.put(arg0, arg1);
        }
    
        @Override
        public void removeAttribute(String arg0) {
            attributes.remove(arg0);
        }
    
        @Override
        public void removeValue(String arg0) {
            values.remove(arg0);
        }
    
        @Override
        public void setAttribute(String arg0, Object arg1) {
            attributes.put(arg0, arg1);
        }
    
        @Override
        public void setMaxInactiveInterval(int arg0) {
            SingleSessionManager.getInstance().setSessionTimeout(arg0);
        }
    
        public HttpSession getOriginalSession() {
            return originalSession;
        }
    
    }
    

    SessionInvalidator 是一个HttpSessionListener,负责清理所有子会话,以防其父会话失效。

    package session;
    
    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    
    /**
     * Session listener that listens for the destruction of a container managed session and takes care
     * of destroying all it's subsessions.
     * <p>
     * Normally this listener won't have much to do since subsessions usually have a shorter lifetime
     * than their parent session and therefore will timeout long before this method is called. This
     * listener will only be important in case of an explicit invalidation of a parent session.
     * </p>
     *
     */
    public class SessionInvalidator implements HttpSessionListener {
    
        @Override
        public void sessionCreated(HttpSessionEvent arg0) {
        }
    
        @Override
        public void sessionDestroyed(HttpSessionEvent arg0) {
            SingleSessionManager.getInstance().clearAllSessions(arg0.getSession());
        }
    
    }
    

    通过将以下内容放入您的 web.xml 来启用一切

    <filter>
      <filter-name>SingleSessionFilter</filter-name>
      <filter-class>de.supportgis.sgjWeb.session.SingleSessionManager</filter-class>
      <!-- The timeout in minutes after which a subsession will be invalidated. It is recommended to set a session timeout for the servled container using the parameter "session-timeout", which is higher than this value. -->
      <init-param>
        <param-name>sessionTimeout</param-name>
        <param-value>1</param-value>
      </init-param>
      <init-param>
        <!-- The intervall in seconds in which a check for expired sessions will be performed. -->
        <param-name>sessionInvalidationCheck</param-name>
        <param-value>15</param-value>
      </init-param>
    </filter>
    <filter-mapping>
      <filter-name>SingleSessionFilter</filter-name>
      <!-- Insert the name of your servlet here to which the session management should apply, or use url-pattern instead. --> 
      <servlet-name>YourServlet</servlet-name>
    </filter-mapping>
    <listener>
      <listener-class>session.SessionInvalidator</listener-class>
    </listener>
    
    <!-- Timeout of the parent session -->
    <session-config>
      <session-timeout>40</session-timeout>
      <!-- Session timeout interval in minutes -->
    </session-config>

    【讨论】:

    • 我已经回复了下面的一些错误修复
    【解决方案3】:

    我认为您正在寻找类似Apache Tomcat 的内容。它将管理各个 servlet 应用程序的各个会话。

    【讨论】:

    • 是的,我正在使用 Tomcat。同一个 servlet 将根据不同的 url 参数显示不同的 html 页面。对于不同的 url 参数,我希望有不同的会话,即使它是同一个用户
    • @Johannes:你是如何处理这种情况的。我和你两年前的位置一样...... :'(
    【解决方案4】:

    会话对于用户和 Web 应用程序的组合是唯一的。您当然可以在同一个 Tomcat 实例上的多个 Web 应用程序中部署您的 servlet,但是您将无法仅根据 URL 参数将 HTTP 请求路由到不同的 Web 应用程序,除非您在第二个 servlet 中评估 URL 参数并重定向浏览器到特定 Web 应用程序的新 URL。

    不同的 servlet 容器或 J2EE 应用服务器可能有不同的选项将请求路由到特定的 Web 应用程序,但 AFAIK 开箱即用,Tomcat 只能根据主机名或基目录委托请求,例如:

    【讨论】:

      【解决方案5】:

      这里是针对 user3792852 的回复的错误修复

      public HttpSession getSession(String uiid, boolean create, HttpSession originalSession)
      {
          if (uiid != null && originalSession != null)
          {
              SubSessionKey key = new SubSessionKey(originalSession.getId(), uiid);
              synchronized (sessions)
              {
                  HttpSessionWrapper session = sessions.get(key);
                  if (session == null && create)
                  {
                      session = new HttpSessionWrapper(uiid, originalSession);
                      sessions.put(key, session);
                  }
                  if (session != null)
                  {
                      session.setLastAccessedTime(System.currentTimeMillis());
                  }
                  return session;
              }
          }
          return null;
      }
      

      【讨论】:

        猜你喜欢
        • 2012-07-10
        • 2011-02-09
        • 1970-01-01
        • 2013-12-06
        • 2011-08-06
        • 1970-01-01
        • 2018-06-05
        • 1970-01-01
        • 2012-03-19
        相关资源
        最近更新 更多