Can "session" scope beans be used with Spring Session and Pivotal GemFire?
When using Spring Session for "session" scope beans, Spring creates an extra HttpSession
for this bean. Is this an existing issue?
What is the solution for this?
Can "session" scope beans be used with Spring Session and Pivotal GemFire?
When using Spring Session for "session" scope beans, Spring creates an extra HttpSession
for this bean. Is this an existing issue?
What is the solution for this?
Regarding...
Can session scoped beans be used with Spring Session and GemFire?
Yes!
In fact, it does not matter which underlying "provider" is used with Spring Session either. For example, either Spring Session with GemFire/Geode (docs) or Spring Session with Redis (docs), etc, can be used and it will work just the same (in the same way).
As for...
If using Spring Session for session scoped beans Spring creates extra HttpSession for this bean, is this an existing issue?
Well, this is not exactly true.
You have to understand the underlying technologies in play here and how they all work together, including Spring Session, the Spring Framework, the Servlet Framework, your Web container (e.g. Tomcat), which is bound by the contract specified in the Java EE Servlet spec, and any other technologies you may have applied (e.g. Spring Security's Web support).
If we dive deep into Spring's architecture/infrastructure, you will begin to understand how it works, why it works and why your particular statement ("Spring creates an extra HttpSession for this bean") is not correct.
First, Spring Session registers an all important Servlet Filter
, the o.s.session.web.http.SessionRepositoryFilter
.
There are many different ways to do this, and the Javadoc for javax.servlet.Filter
essentially hints that this is done via the Web Application "deployment descriptor".
Of course, given our menagerie of configuration options today, a Web Application deployment descriptor is pretty loosely defined, but we commonly know this to mean web.xml
. However, that is not the only way in which we can configure, essentially, the Web Application's ServletContext
.
Spring supports both web.xml
based deployment descriptors as well as JavaConfig using the Servlet (3.0+) API.
In web.xml
you would register (for example) the Spring Frameworks' o.s.web.filter.DelegatingFilterProxy
, that delegates to an actual javax.servlet.Filter
implementation (when Spring Session is in play, that would be the o.s.session.web.http.SessionRepositoryFilter
, of course) which is also declared/defined as a "bean" (first this, then this) in the Spring container. This is necessary in order to auto-wire (inject) the appropriate Spring Session o.s.session.SessionRepository
implementation (also a Spring managed bean defined in the container, e.g. for Redis) that knows how to delegate (HTTP) Session
state management to the underlying "provider".
In the JavaConfig approach, the registration is performed via the core Spring Framework's o.s.web.WebApplicationInitializer
concept. Read the Javadoc for more details.
Well, Spring Session provides such a WebApplicationInitializer
to initialize (HTTP) Session management, the o.s.session.web.context.AbstractHttpSessionApplicationInitializer
. Typically, when using Spring's Java-based Container Configuration and/or Annotation configuration approach, a developer would create a class that extends this Spring Session provided class and register the necessary configuration (e.g. connection criteria) for the underlying Session management provider; for example (see also this). The Config
class is annotated with @EnableRedisHttpSession
which imports the Spring @Configuration
class that declares/defines the appropriate Spring Session SessionRepository
implementation for the "provider" (e.g. again Redis), which is needed by the Servlet Filter
(again SessionRepositoryFilter
).
If you look at what the Spring Session AbstractHttpSessionApplicationInitializer
does, you will see that it registers the Spring Session, SessionRepositoryFilter
, indirectly via Spring Framework's DelegatingFilterProxy
... from insert, then here, then here and finally, here.
As you can see, the Spring Session SessionRepositoryFilter
is positioned first in the chain of Servlet Filters
. The !insertBeforeOtherFilters
is negated since the parameter in javax.servlet.FilterRegistration.Dynamic.addMappingForUrlPatterns(dispatcherTypes, isMatchAfter, urlPatterns...)
is "isMatchAfter".
This is essential, since the Spring Session's o.s.session.web.http.SessionRepositoryFilter
replaces both of the javax.servlet.http.HttpServletRequest
and javax.servlet.http.HttpServletResponse
. Specifically, by replacing the javax.servlet.http.HttpServletRequest
, Spring Session can provide an implementation of javax.servlet.http.HttpSession
(when HttpServletRequest.getSession(..)
is called) that is backed by Spring Session and the provider of the developer's choice (e.g. Redis, GemFire), the whole purpose of Spring Session in the first place.
So, the Servlet Filters
see the HTTP request/response before any framework code (e.g. Spring Framework's session scoped bean infrastructure), and especially before any of the Web Application's Controllers
or Servlets
, get to see the HTTP request/response.
So, when the core Spring Framework's session scoped bean infrastructure sees the (HTTP) Servlet request/response, it sees what Spring Session handed it, which is just an implementation the regular javax.servlet
interfaces (e.g. HttpSession
) backed by Spring Session.
Looking at the core Spring Framework's o.s.web.context.request.SessionScope
"custom" implementation (which handles bean references/bean lifecycles for session scoped beans declared/defined in the Spring container), which extends o.s.web.context.request.AbstractRequestAttributesScope
, you see that it just delegates to the o.s.web.context.request.SessionRequestAttributes
class. This class is created primarily by Spring's DispatcherServlet
, and defines all of its operations (e.g. setAttribute(name, value, scope)
) in terms of the "provided" scope defined by the bean (definition) in question. See the source for more details. So the bean gets added to the appropriate HTTP session.
Sure, Spring "will" create a new javax.servlet.http.HttpSession
on the first HTTP request, but not without Spring Session's infrastructure knowing about it, since what Spring is using in this case is an implementation of javax.servlet.http.HttpSession
backed by Spring Session's "Session
".
Also, getSession(true)
is also just an indication that the HttpSession
is "allowed" to be created if it does not already exist! A Servlet container simply does not keep creating new HTTP sessions for every HTTP request, so long as the session ID can be determined from the HTTP request (which is done via either URL injection... jsessionid
or with a cookie, typically). See the javax.servlet.HttpServletRequest.getSession(boolean)
for more details.
Anyway, the only other caveat to this entire story is, you need to make sure, especially for GemFire, that...
The Spring "session" scoped beans defined in the container are serializable, either using Java Serialization, or 1 of GemFire's serialization strategies. This includes whatever the bean references (other beans, object types, etc) unless those "references" are declared transient
. NOTE: I am not entirely certain GemFire Reflection-based PDX serialization approach is entirely "aware" of "transient" fields. Be conscious of this.
You must make certain that the classes serialized in the session are on the GemFire Servers classpath.
I am working on a configuration option for Spring Session Data Geode/GemFire at the moment to support PDX, but that is not available yet.
Anyway, I hope this helps clear up the muddy waters a bit. I know it is a lot to digest, but it all should work as the user expects.
I will also add that I have not tested this either. However, after reviewing the code, I am pretty certain this should work.
I have made it a task to add tests and samples to cover this situation in the near future.
Cheers! -John