springboot 整合 session,实现 session 共享

前言

session 和 cookie 是保存用户状态的两种方式,session在服务端,cookie 在客户端。
本文章将记录 springboot 整合 session 的 demo 示例。

session(会话)

  • 粘性会话
    如果某台服务器宕机,那么会话信息就没有了。
  • 复制会话
    每台机器都复制会话,如果量太大的话,不现实
  • 集中会话
    使用 mongo 、redis 等统一保持会话

pom 依赖

	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

启动类

@SpringBootApplication
@RestController
@EnableRedisHttpSession
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(SessionDemoApplication.class, args);
	}
	@RequestMapping("/hello")
	public String printSession(HttpSession session, String name) {
		String storedName = (String) session.getAttribute("name");
		if (storedName == null) {
			session.setAttribute("name", name);
			storedName = name;
		}
		return "hello " + storedName;
	}

}

@EnableRedisHttpSession 使用这个注解,开启 httpsession redis 的自动配置

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RedisHttpSessionConfiguration.class)
@Configuration
public @interface EnableRedisHttpSession {
}

然后看一下 RedisHttpSessionConfiguration

@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
		implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
		SchedulingConfigurer {

	static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";

	private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;

	private String redisNamespace = RedisOperationsSessionRepository.DEFAULT_NAMESPACE;

	private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE;

	private String cleanupCron = DEFAULT_CLEANUP_CRON;

	private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();

	private RedisConnectionFactory redisConnectionFactory;

	private RedisSerializer<Object> defaultRedisSerializer;

	private ApplicationEventPublisher applicationEventPublisher;

	private Executor redisTaskExecutor;

	private Executor redisSubscriptionExecutor;

	private ClassLoader classLoader;

	private StringValueResolver embeddedValueResolver;
	...

可以看到其实现了对 redis 的一些配置,同时RedisHttpSessionConfiguration 继承了 SpringHttpSessionConfiguration,那么看一下这个类的源码:

@Configuration
public class SpringHttpSessionConfiguration implements ApplicationContextAware {

	private final Log logger = LogFactory.getLog(getClass());

	private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver();

	private boolean usesSpringSessionRememberMeServices;

	private ServletContext servletContext;

	private CookieSerializer cookieSerializer;

	private HttpSessionIdResolver httpSessionIdResolver = this.defaultHttpSessionIdResolver;

	private List<HttpSessionListener> httpSessionListeners = new ArrayList<>();

	@PostConstruct
	public void init() {
		CookieSerializer cookieSerializer = (this.cookieSerializer != null)
				? this.cookieSerializer
				: createDefaultCookieSerializer();
		this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
	}

	@Bean
	public SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter() {
		return new SessionEventHttpSessionListenerAdapter(this.httpSessionListeners);
	}

	@Bean
	public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
			SessionRepository<S> sessionRepository) {
		SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
				sessionRepository);
		sessionRepositoryFilter.setServletContext(this.servletContext);
		sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
		return sessionRepositoryFilter;
	}
......

SpringHttpSessionConfiguration 也是一个配置类,并且其注册了一些 bean,我们看一下这个 过滤器Bean SessionRepositoryFilter

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
...
	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

		SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
				request, response, this.servletContext);
		SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
				wrappedRequest, response);

		try {
			filterChain.doFilter(wrappedRequest, wrappedResponse);
		}
		finally {
			wrappedRequest.commitSession();
		}
	}

...
}


abstract class OncePerRequestFilter implements Filter {

}

可以看到 SessionRepositoryFilter 继承了 OncePerRequestFilter,并且实现了 filter 过滤器。
我们看一下 doFilterInternal 这个方法,其对 request 进行了一次封装.

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

		@Override
		public HttpSessionWrapper getSession(boolean create) {
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
			S requestedSession = getRequestedSession();
			if (requestedSession != null) {
				if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
					requestedSession.setLastAccessedTime(Instant.now());
					this.requestedSessionIdValid = true;
					currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
					currentSession.setNew(false);
					setCurrentSession(currentSession);
					return currentSession;
				}
			}
			else {
				// This is an invalid session id. No need to ask again if
				// request.getSession is invoked for the duration of this request
				if (SESSION_LOGGER.isDebugEnabled()) {
					SESSION_LOGGER.debug(
							"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
				}
				setAttribute(INVALID_SESSION_ID_ATTR, "true");
			}
			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)"));
			}
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			session.setLastAccessedTime(Instant.now());
			currentSession = new HttpSessionWrapper(session, getServletContext());
			setCurrentSession(currentSession);
			return currentSession;
		}

我们看到 SessionRepositoryRequestWrapper 中还提供了 getSeesion 的方法,其拿 session 的逻辑就在这里,让后我看下 SessionRepositoryFilter.this.sessionRepository.createSession() 这个方法:
点开其实现

	@Override
	public RedisSession createSession() {
		RedisSession redisSession = new RedisSession();
		if (this.defaultMaxInactiveInterval != null) {
			redisSession.setMaxInactiveInterval(
					Duration.ofSeconds(this.defaultMaxInactiveInterval));
		}
		return redisSession;
	}

发现其使用了 redis。到这使用redis 作为 session的流程就基本走完了。

那么我们访问一下
http://localhost:8080/hello?name=spring
在这里插入图片描述
我们看到第一次请求中 reponse header 中返回了 session,并且返回结果

hello spring

改一下 name 试一下:
在这里插入图片描述
发现 Response header 中没有了 set-cookies,并且请求头 request header 中有了 cookie 信息。并且返回结果没有变:

hello spring

redis

查看一下 redis,我使用的 docker 运行 redis

> docker exec -it redis bash
> redis-cli
> keys *
 1) "spring:session:expirations:1560958020000"
 2) "spring:session:expirations:1560958140000"
 3) "spring:session:sessions:51ffe6c7-9007-4ab9-9e90-0767189e5bf7"
 4) "spring:session:sessions:056a46cf-2fbc-4f33-ace3-46d5122177eb"
 5) "spring:session:sessions:expires:51ffe6c7-9007-4ab9-9e90-0767189e5bf7"
 6) "spring:session:sessions:0fe747b1-691a-4ddf-93f1-8e0a5e538c71"
 7) "spring:session:sessions:expires:056a46cf-2fbc-4f33-ace3-46d5122177eb"
 8) "spring:session:sessions:expires:0fe747b1-691a-4ddf-93f1-8e0a5e538c71"

> hkeys "spring:session:sessions:0fe747b1-691a-4ddf-93f1-8e0a5e538c71"
1) "sessionAttr:name"
2) "creationTime"
3) "lastAccessedTime"
4) "maxInactiveInterval"

使用 redis 工具 rdb 查看:
在这里插入图片描述
到此本文章就结束了。

猜你喜欢

转载自blog.csdn.net/wjavadog/article/details/92851531