Недавно я тоже сталкивался с этой проблемой и согласился с предложением ChssPly76 решить ее. Я думал, что опубликую свои результаты здесь, чтобы обеспечить справочную реализацию. Он не был тщательно протестирован, поэтому, пожалуйста, дайте мне знать, если вы обнаружите какие-либо недостатки.
Я предполагаю, что каждый запрос к сервлету содержит параметр с именем uiid , который представляет идентификатор пользователя. Запрашивающая сторона должна отслеживать отправку нового идентификатора при каждом нажатии на ссылку, которая открывает новое окно. В моем случае этого достаточно, но вы можете использовать любой другой (возможно, более безопасный) метод здесь. Кроме того, я работаю с Tomcat 7 или 8. Возможно, вам придется расширять другие классы при работе с различными контейнерами сервлетов, но API не должны слишком сильно меняться.
В дальнейшем созданные сеансы называются подсессиями , исходный управляемый сеанс контейнера - это родительский сеанс . Реализация состоит из следующих пяти классов:
SingleSessionManager отслеживает создание, распространение и очистку всех подразделений. Он делает это, действуя как фильтр сервлета, который заменяет 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 и идентификатора родительского сеанса.
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
, которые будут возвращать 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 следующее 1041 *
<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>