Серия SpringSession — переписывание запросов и ответов

задняя часть открытый источник контейнер

мы знаем,HttpServletRequsetиHttpServletResponseдаServletуказанный стандартомJavaязык иWebИнтерфейс для взаимодействия с контейнером. Сам интерфейс указывает толькоjavaязыковая параwebПоведение контейнера при доступе и конкретная реализация определяются разнымиwebКонтейнер реализован внутри него.

Затем во время выполнения, когда нам нужноHttpServletRequsetиHttpServletResponseПри расширении экземпляра по умолчанию мы можем наследоватьHttpServletRequestWrapperиHttpServletResponseWrapperреализовать.   

существуетSpringSessionпотому что мы хотим реализовать контейнер, который не зависит от самого контейнераgetSessionреализации, поэтому его необходимо расширитьHttpServletRequset, переписавgetSessionдля достижения распределенногоsessionСпособность. Давайте посмотрим нижеSpringSessionсреда дляHttpServletRequsetрасширение.

1. Запросить перезапись

SpringSessionДля перезаписи запросов способность в основном отражается в аспекте хранения, то естьgetSessionметод. существуетSessionRepositoryFilterВ этом классе реализован внутренний класс для реализацииHttpServletRequsetиHttpServletResponseрасширение.

1.1 Реализация расширения HttpServletRequset

private final class SessionRepositoryRequestWrapper
			extends HttpServletRequestWrapper {
	// HttpServletResponse 实例
	private final HttpServletResponse response;
	// ServletContext 实例
	private final ServletContext servletContext;
        // requestedSession session对象
        private S requestedSession; 
        // 是否缓存 session
        private boolean requestedSessionCached;
	// sessionId
	private String requestedSessionId;
	// sessionId 是否有效
	private Boolean requestedSessionIdValid;
	// sessionId 是否失效
	private boolean requestedSessionInvalidated;
	
	// 省略方法
}

1.2 Конструктор

private SessionRepositoryRequestWrapper(HttpServletRequest request,
		HttpServletResponse response, ServletContext servletContext) {
	super(request);
	this.response = response;
	this.servletContext = servletContext;
}

Конструктор будетHttpServletRequest,HttpServletResponseа такжеServletContextЭкземпляр передается для использования последующими расширениями.

1.3 Метод getSession

@Override
public HttpSessionWrapper getSession(boolean create) {
    // 从当前请求线程中获取 session
	HttpSessionWrapper currentSession = getCurrentSession();
	// 如果有直接返回
	if (currentSession != null) {
		return currentSession;
	}
	// 从请求中获取 session,这里面会涉及到从缓存中拿session的过程
	S requestedSession = getRequestedSession();
	if (requestedSession != null) {
	    // 无效的会话id(不支持的会话存储库)请求属性名称。
	    // 这里看下当前的sessionId是否有效
		if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
		    // 设置当前session的最后访问时间,用于延迟session的有效期
			requestedSession.setLastAccessedTime(Instant.now());
			// 将requestedSessionIdValid置为true
			this.requestedSessionIdValid = true;
			// 包装session
			currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
			// 不是新的session,如果是新的session则需要改变sessionId
			currentSession.setNew(false);
			// 将session设置到当前请求上下文
			setCurrentSession(currentSession);
			// 返回session
			return currentSession;
		}
	}
	else {
		// 这里处理的是无效的sessionId的情况,但是当前请求线程 session有效
		if (SESSION_LOGGER.isDebugEnabled()) {
			SESSION_LOGGER.debug(
					"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
		}
		// 将invalidSessionId置为true
		setAttribute(INVALID_SESSION_ID_ATTR, "true");
	}
	// 是否需要创建新的session
	if (!create) {
		return null;
	}
	if (SESSION_LOGGER.isDebugEnabled()) {
		SESSION_LOGGER.debug(
				"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
						+ SESSION_LOGGER_NAME,
				new RuntimeException(
						"For debugging purposes only (not an error)"));
	}
	// 创建新的session
	S session = SessionRepositoryFilter.this.sessionRepository.createSession();
	// 设置最后访问时间,也就是指定了当前session的有效期限
	session.setLastAccessedTime(Instant.now());
	// 包装下当前session
	currentSession = new HttpSessionWrapper(session, getServletContext());
	//设置到当前请求线程
	setCurrentSession(currentSession);
	return currentSession;
}

В приведенном выше коде есть несколько моментов, которые поясняются здесь отдельно.

  • getCurrentSession
    • Это для того, чтобы избежать повторного получения сессии из хранилища во время одного и того же процесса запроса.При поступлении новой установите текущую сессию на текущий запрос.Если вам нужен getSession в последующем процессе обработки, вам не нужно его хранить Снова возьми его в среду.
  • getRequestedSession
    • Это основано на запрошенной информацииsession, которая включает в себяsessionIdРазобрать, получить из хранилищаsessionобъекты и т.д.
  • создавать ли новыеsessionобъект
    • Ни в текущем запросе, ни в хранилищеsessionинформация, здесь будет основываться наcreateпараметр, определяющий, следует ли создавать новыйsession. Здесь обычный пользователь входит в систему в первый раз илиsessionПойдет, когда не получится.

1.4 getRequestedSession

Получено из запрошенной информацииsessionобъект

private S getRequestedSession() {
    // 缓存的请求session是否存在
	if (!this.requestedSessionCached) {
            // 获取 sessionId
            List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
            		.resolveSessionIds(this);
            // 通过sessionId来从存储中获取session
            for (String sessionId : sessionIds) {
            	if (this.requestedSessionId == null) {
            		this.requestedSessionId = sessionId;
            	}
            	S session = SessionRepositoryFilter.this.sessionRepository
            			.findById(sessionId);
            	if (session != null) {
            		this.requestedSession = session;
            		this.requestedSessionId = sessionId;
            		break;
            	}
            }
            this.requestedSessionCached = true;
	}
	return this.requestedSession;
}

Этот код все еще очень интересен, возьмите его здесьsessionIdЧто возвращается, так это список. Конечно вотSpringSessionстратегии внедрения, потому что поддержкаsession, поэтому здесь он возвращается в виде списка. ОК, продолжайте смотреть, как анализироватьsessionIdиз:

можно увидеть здесьSpringSessionзаsessionIdДве стратегии приобретения, одна основана наcookie, один основан наheader; Конкретную реализацию смотрите отдельно.

1.4.1 CookieHttpSessionIdResolver получает идентификатор сеанса

CookieHttpSessionIdResolverполучено вsessionIdОсновной код выглядит следующим образом:

Собственно, тут и говорить нечего, просто читайтеcookie. отrequestбудетcookieВыньте информацию, а затем пройдите, чтобы найти текущуюsessionIdсоответствующийcookie, здесь суждение тоже очень простое, если даSESSIONначало, значитSessionId,после всегоcookieделится не толькоsessionIdи, возможно, другой контент.

Кроме того, есть jvmRoute, который редко используется, потому что в большинстве случаев это значение равно null. Это мы анализируемCookieSerializerобъясню позже.

1.4.2 HeaderHttpSessionIdResolver для получения идентификатора сеанса

Это приобретение более прямое и грубое, основанное наheaderNameотheaderзначение в.

назадgetRequestedSession, ядро ​​остальной части кода иsessionRepositoryЭто связано, и эта часть будет включать в себя часть хранения. Это выходит за рамки анализа этой статьи и будет проанализировано в части реализации хранилища.

1.5 HttpSessionWrapper

В приведенном выше коде, когда мы получаемsessionЭкземпляр обычно упакован, поэтому используетсяHttpSessionWrapper.

HttpSessionWrapperнаследоватьHttpSessionAdapter,этоHttpSessionAdapterзаключается в преобразовании SpringSession в стандартныйHttpSessionКласс адаптера.HttpSessionAdapterстандартныйservletнормативныйHttpSessionинтерфейс.

1.5.1 HttpSessionWrapper

HttpSessionWrapperпереписанныйinvalidateметод. Из кода эффект вызова этого метода:

  • requestedSessionInvalidatedустановлен вtrue, который определяет текущийsessionневерный.
  • в текущем запросеsessionУстановить какnull, то при последующих обращениях к запросу черезgetCurrentSessionне получитsessionИнформация.
  • Текущая кэшированная сессия очищена, включая sessionId, экземпляр сессии и т. д.
  • Удалите объект сеанса на носителе данных.

1.5.2 HttpSessionAdapter

SpringSessionи стандартныйHttpSessionкласс конфигуратора. Как это понять, посмотрите на следующий кусок кода:

@Override
public Object getAttribute(String name) {
	checkState();
	return this.session.getAttribute(name);
}

Для реализаций на основе самого контейнераHttpSessionСказать,getAttributeРеализация также определяется самим контейнером. Но после преобразования здесь,getAttributeпройдетSpringSessionполучить решение, реализованное в. другиеAPIАдаптация также основана на этой реализации.

SessionCommittingRequestDispatcher

ДостигнутоRequestDispatcherинтерфейс. оRequestDispatcherВы можете обратиться к этой статье[Сервлет] Принцип работы RequestDispatcher.SessionCommittingRequestDispatcherправильноforwardповедение не изменилось. заincludeвincludeпредставлено доsession. Зачем это делать?

так какincludeспособ сделать оригиналServletи отправленоServletможет выводить ответную информацию, то есть оригиналServletВы также можете продолжать выводить ответную информацию, т. е. после того, как запрос будет переадресован, исходныйServletВы также можете продолжать выводить ответную информацию, которая пересылаетсяServletОтвет на запрос будет объединен с исходнымServletв объекте ответа.

Так что этоincludeпозвонить перед звонкомcommit, что гарантирует, что включенныйServletПрограмма не может изменить код состояния и заголовки ответа ответного сообщения.

2 Ответ переписать

Цель перезаписи ответа — обеспечить возможность сохранения сеанса при отправке запроса. посмотриSessionRepositoryResponseWrapperРеализация класса:

Реализация здесь состоит в том, чтобы переписатьonResponseCommitted, то есть, как упоминалось выше, при отправке запроса эта функция обратного вызова может быть использована дляsessionСохраните в контейнер для хранения.

2.1 представление сеанса

Наконец, взгляните на commitSession

Этот процесс больше не пойдет в контейнер храненияsessionинформации, а непосредственно из текущего запроса. Если не получится, напишитеcookieизменит текущийsessionсоответствующийcookieЗначение установлено пустым, чтобы оно было перенесено при поступлении следующего запроса.sessionCookieпуст, что приведет к повторному входу в систему.

Если получено, очистить текущий запросsessionинформацию, а затемsessionв емкость для хранения иsessionIdнаписать ответcookieсередина.

резюме

Эта статья в основном посвященаSpringSessionпереписать вRequestиResponseПроанализировано. путем переписыванияRequestпросьба прийтиsessionХранилище связано с контейнером хранилища путем переопределенияResponseиметь дело сsessionпредставить, будетsessionСохраните в контейнер для хранения.

Позже мы продолжим анализSpringSessionисходный код. Я также недавно узнаю о технологиях отслеживания ссылок, и я собираюсь написать об этом, заинтересованные студенты могут обсудить это вместе.

прикрепил