This guide describes how to build a Spring Boot Web application configured with Spring Session to transparently
manage a Web application’s javax.servlet.http.HttpSession
using Apache Geode in a clustered (distributed),
replicated, highly available and optionally, durable manner.
In addition, this samples explores the effects of using Spring Session and Apache Geode to manage the HttpSession
when the Spring Boot Web application also declares both "session" and "request" scope bean definitions to process
client HTTP requests.
This sample is based on a StackOverflow post, which posed the following question…
Can session scope beans be used with Spring Session and Pivotal GemFire?
The poster of the question when on to state and ask…
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?
The answer to the first question is most definitely, yes. And, the second statement/question is not correct/valid, as explained in the answer.
This sample uses Apache Geode’s client/server topology with a pair of Spring Boot applications, one to configure
and run a Geode server, and another to configure and run a Geode client, which is also a Spring MVC Web application
making use of an HttpSession
.
The completed guide can be found below, in section Sample Spring Boot Web Application using Apache Geode-managed HttpSessions with Request and Session Scoped Proxy Beans. |
1. Updating Dependencies
Before using Spring Session, you must ensure that the required dependencies are included.
If you are using Maven, include the following dependencies
in your pom.xml
:
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-geode</artifactId>
<version>${spring-session-data-geode-version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
If you are using Pivotal GemFire instead of Apache Geode, you may substitute the spring-session-data-gemfire
artifact for spring-session-data-geode .
|
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
If you are using Maven, include the following repository
declaration in your pom.xml
:
<repositories>
<!-- ... -->
<repository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
2. Spring Boot Configuration
After adding the required dependencies and repository declarations, we can create the Spring configuration
for both our Apache Geode client and server using Spring Boot. The Spring configuration is responsible for
creating a Servlet Filter
that replaces the HttpSession
with an implementation backed by Spring Session
and Apace Geode.
2.1. Spring Boot, Apache Geode Cache Server
We start with a Spring Boot application to configure and bootstrap the Apache Geode server process…
@SpringBootApplication (1)
@CacheServerApplication(name = "SpringSessionDataGeodeServerWithScopedProxiesBootSample") (2)
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 10) (3)
@EnableManager(start = true) (4)
public class GemFireServer {
public static void main(String[] args) {
new SpringApplicationBuilder(GemFireServer.class)
.web(WebApplicationType.NONE)
.build()
.run(args);
}
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
CacheServerConfigurer cacheServerPortConfigurer(
@Value("${spring.session.data.geode.cache.server.port:40404}") int port) { (5)
return (beanName, cacheServerFactoryBean) ->
cacheServerFactoryBean.setPort(port);
}
}
1 | First, we annotate the GemFireServer class with @SpringBootApplication declaring that this is a Spring Boot
application, which allows us to leverage all of Spring Boot’s features (e.g. auto-configuration). |
2 | Next, we also annotate the GemFireServer class with Spring Data Geode’s @CacheServerApplication ,
which creates a peer, cache server allowing cache clients to connect. |
3 | (Optional) Then, we declare the @EnableGemFireHttpSession annotation to create the necessary server-side Region
(by default, "ClusteredSpringSessions") used to store the HttpSession state. This step is optional since
the Region used to store session state could be manually created. Using @EnableGemFireHttpSession is easy
and convenient, and ensures that our client and server-side Regions match by name, which is required by Apache Geode. |
4 | (Optional) Additionally, we also enable Apache Geode’s embedded Management service, which allows JMX clients (e.g. Apache Geode’s Gfsh shell tool) to connect to the server in order to manage the server or the entire cluster. |
5 | Finally, we adjust the port that the CacheServer uses to listen for cache client connections by declaring
a CacheServerConfigurer bean, which gives us access to SDG’s CacheServerFactoryBean in order to modify the port.
Property placeholders can be used to externalize the cache server port configuration. The cache server port
defaults to 40404 if not explicitly configured. |
2.2. Spring Boot, Apache Geode Cache Client Web application
Now, we create a Spring Boot Web application exposing our Web service with Spring MVC, running as an Apache Geode
cache client connected to our Spring Boot, Apache Geode server. The Web application will use Spring Session
backed by Apache Geode to manage HttpSession
state in a clustered (i.e. distributed), replicated and highly available
manner.
@SpringBootApplication (1)
@Controller (2)
public class Application {
static final String INDEX_TEMPLATE_VIEW_NAME = "index";
static final String PING_RESPONSE = "PONG";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@ClientCacheApplication(name = "SpringSessionDataGeodeClientWithScopedProxiesBootSample",
pingInterval = 5000L, readTimeout = 15000, retryAttempts = 1, subscriptionEnabled = true) (3)
@EnableGemFireHttpSession(poolName = "DEFAULT") (4)
static class ClientCacheConfiguration {
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
ClientCacheConfigurer clientCacheServerPortConfigurer(
@Value("${spring.session.data.geode.cache.server.port:40404}") int port) { (5)
return (beanName, clientCacheFactoryBean) ->
clientCacheFactoryBean.setServers(Collections.singletonList(
new ConnectionEndpoint("localhost", port)));
}
}
@Configuration
static class SpringWebMvcConfiguration { (6)
@Bean
public WebMvcConfigurer webMvcConfig() {
return new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName(INDEX_TEMPLATE_VIEW_NAME);
}
};
}
}
@Autowired
private RequestScopedProxyBean requestBean;
@Autowired
private SessionScopedProxyBean sessionBean;
@ExceptionHandler
@ResponseBody
public String errorHandler(Throwable error) {
StringWriter writer = new StringWriter();
error.printStackTrace(new PrintWriter(writer));
return writer.toString();
}
@RequestMapping(method = RequestMethod.GET, path = "/ping")
@ResponseBody
public String ping() {
return PING_RESPONSE;
}
@RequestMapping(method = RequestMethod.GET, path = "/counts")
public String requestAndSessionInstanceCount(HttpServletRequest request, HttpSession session, Model model) { (7)
model.addAttribute("sessionId", session.getId());
model.addAttribute("requestCount", this.requestBean.getCount());
model.addAttribute("sessionCount", this.sessionBean.getCount());
return INDEX_TEMPLATE_VIEW_NAME;
}
}
1 | Like the server, we declare our Web application to be a Spring Boot application
by annotating our Application class with @SpringBootApplication . |
2 | @Controller is a Spring Web MVC annotation enabling our MVC request mapping handler methods (i.e. methods
annotated with @RequestMapping ) to process client HTTP requests (e.g. <6>) |
3 | We also declare our Web application to be an Apache Geode cache client by annotating our Application class
with @ClientCacheApplication . Additionally, we adjust a few basic, "DEFAULT" client Pool settings. |
4 | Next, we declare that the Web application will use Spring Session backed by Apache Geode to manage
the HttpSession’s state by annotating the nested ClientCacheConfiguration class with @EnableGemFireHttpSession .
This will create the necessary client-side PROXY Region (by default, "ClusteredSpringSessions") corresponding to
the same server Region by name. All session state will be sent from the client to the server through Region
data access operations. The client-side Region uses the "DEFAULT" Pool (of connections) to communicate
with the server. |
5 | Then, we adjust the port used by the client Pool to connect to the cache server using Spring Data Geode’s
ClientCacheConfigurer . This callback interface is similar in purpose to the CacheServerConfigurer we saw
in the server’s configuration. In this case, the ClientCacheConfigurer gives us access to the underlying
SDG ClientCacheFactoryBean in order to adjust the configuration of the Apache Geode ClientCache . |
6 | We adjust the Spring Web MVC configuration to set the home page, and finally… |
7 | We declare the /counts HTTP request mapping handler method to keep track of the number of instances
created by the Spring container for both "request" and "session" scoped proxy beans, of types
RequestScopedProxyBean and SessionScopedProxyBean , respectively, each and every time a request is processed by
the handler method. |
In typical Apache Geode production deployments, where the cluster includes potentially hundreds or thousands of servers (a.k.a. data nodes), it is more common for clients to connect to 1 or more Geode Locators running in the same cluster. A Locator passes meta-data to clients about the servers available in the cluster, the individual server load and which servers have the client’s data of interest, which is particularly important for direct, single-hop data access and latency-sensitive operations. See more details about the Client/Server Deployment in the Apache Geode User Guide. |
For more information on configuring Spring Data Geode, refer to the Reference Guide. |
2.2.1. Enabling GemFire HttpSession Management
@EnableGemFireHttpSession
enables a developer to configure certain aspects of both Spring Session and Apache Geode
out-of-the-box using the following attributes:
-
clientRegionShortcut
- specifies the Apache Geode data management policy used on the client with ClientRegionShortcut (default isPROXY
). This attribute is only used when configuring the clientRegion
. -
indexableSessionAttributes
- Identifies session attributes by name that should be indexed for querying purposes. Only session attributes explicitly identified by name will be indexed. This is useful in situations where your application is looking up theHttpSession
by the currently authenticated principal’s name, for example. -
maxInactiveIntervalInSeconds
- controlsHttpSession
idle-timeout expiration (TTI; defaults to 30 minutes). -
poolName
- name of the dedicated Apache Geode Pool used by a client to connect to a cluster of servers (defaults to "gemfirePool"). -
regionName
- specifies the name of the Apache Geode Region used to store and manageHttpSession
state (defaults to "ClusteredSpringSessions"). -
serverRegionShortcut
- specifies the Apache Geode data management policy used on the server with RegionShortcut (default isPARTITION
). This attribute is only used when configuring server Regions, or when Apache Geode’s P2P topology is employed. -
sessionSerializerBeanName
- refers to the name of the bean that handles serialization of theHttpSession
state between the client and the server.
It is important to remember that the Apache Geode client Region name must match a server Region by the same name
if the client Region is either a PROXY or CACHING_PROXY . Client and server Region names are not required to match
if the client Region used to store session state is LOCAL only. However, keep in mind that session state will not
be propagated to the server when the client Region is only LOCAL to the client. Additionally, you lose all benefits
of using Apache Geode to store and manage session state on servers in a clustered, replicated and highly available
manner.
|
2.3. Session-scoped Proxy Bean
The Spring Boot Apache Geode cache client Web application defines the SessionScopedProxyBean
domain class.
@Component (1)
@SessionScope(proxyMode = ScopedProxyMode.TARGET_CLASS) (2)
public class SessionScopedProxyBean implements Serializable {
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0);
private final int count;
public SessionScopedProxyBean() {
this.count = INSTANCE_COUNTER.incrementAndGet(); (3)
}
public int getCount() {
return count;
}
@Override
public String toString() {
return String.format("{ @type = '%s', count = %d }", getClass().getName(), getCount());
}
}
1 | First, the SessionScopedProxyBean domain class is stereotyped as a Spring @Component to be picked up by
Spring’s classpath component-scan. |
2 | Additionally, instances of this class are scoped to the HttpSession . Therefore, each time a client request
results in a new HttpSession (such as during a login event), a single instance of this class is created and will last
for the duration of the HttpSession . When the HttpSession ends or expires, this instance is destroyed by
the Spring container. If the client re-establishes a new HttpSession , then another, new instance of this class
will be provided to the application’s beans. However only ever 1 instance of this class exists for the duration of
the HttpSession and no more! |
3 | Finally, this class keeps track of how many instances of this type are created by the Spring container throughout the entire application lifecycle. |
More information on Spring’s @SessionScope (i.e. "session" scope proxy beans) can be found in the
Reference Documentation,
along with this.
|
2.4. Request-scoped Proxy Bean
The Spring Boot Apache Geode cache client Web application additionally defines the RequestScopedProxyBean
domain class.
@Component (1)
@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) (2)
public class RequestScopedProxyBean {
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0);
private final int count;
public RequestScopedProxyBean() {
this.count = INSTANCE_COUNTER.incrementAndGet(); (3)
}
public int getCount() {
return count;
}
@Override
public String toString() {
return String.format("{ @type = '%s', count = %d }", getClass().getName(), getCount());
}
}
1 | First, this RequestScopedProxyBean domain class is stereotyped as a Spring @Component to be picked up by
Spring’s classpath component-scan. |
2 | Additionally, instances of this class are scoped to the HttpServletRequest . Therefore, each time a client HTTP
request is sent (e.g. to process a Thread-scoped transaction), a single instance of this class will be created
and will last for the duration of the HttpServletRequest . When the request ends, this instance is destroyed
by the Spring container. Any subsequent client HttpServletRequests results in another, new instance of this
class, which will be provided to the application’s beans. However, only ever 1 instance of this class exists
for the duration of the HttpServletRequest and no more! |
3 | Finally, this class keeps track of how many instances of this type are created by the Spring container throughout the entire application lifecycle. |
More information on Spring’s @RequestScope (i.e. "request" scope proxy beans) can be found in the
Reference Documentation,
along with this.
|
3. Sample Spring Boot Web Application using Apache Geode-managed HttpSessions with Request and Session Scoped Proxy Beans
3.1. Running the Boot Sample Application
You can run the sample by obtaining the source code and invoking the following commands.
First, you must run the server:
$ ./gradlew :spring-session-sample-boot-gemfire-with-scoped-proxies:run
Then, in a separate terminal, run the client:
$ ./gradlew :spring-session-sample-boot-gemfire-with-scoped-proxies:bootRun
You should now be able to access the application at http://localhost:8080/counts.
3.2. Exploring the Sample Application
When you access the Web application @ http://localhost:8080/counts, you will see a screen similar to…

The table shows 1 row with 3 columns of information.
The Session ID
and Session Count
columns show current HttpSession
information including the current
HttpSession’s
ID along with the number of HttpSessions
created during the (client) application’s current run.
Additionally, the current Request Count
is shown to indicate how many requests have been made by the client,
which in this case is your web browser.
You can use your web browser’s refresh button to increase both the session and request count. However, the session count only increases after the current session ends or times out and a new session has been created for the client.
The session will time out after 10 seconds, which was configured on the server using the @EnableGemFireHttpSession
annotation as we saw before (#3)…
@SpringBootApplication
@CacheServerApplication(name = "SpringSessionDataGeodeServerWithScopedProxiesBootSample")
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 10) (3)
...
public class GemFireServer {
...
}
Here, you see that maxInactiveIntervalInSeconds
is set to 10 seconds. After 10 seconds, Apache Geode will expire
the HttpSession
, and upon refreshing your web browser, a new session will be created and the session count
will increase.
However, every request naturally results in incrementing the request count.
3.3. How does it work?
Well, from our defined Web service endpoint in our Spring MVC @Controller
class on the client…
@RequestMapping(method = RequestMethod.GET, path = "/counts")
public String requestAndSessionInstanceCount(HttpServletRequest request, HttpSession session, Model model) { (7)
model.addAttribute("sessionId", session.getId());
model.addAttribute("requestCount", this.requestBean.getCount());
model.addAttribute("sessionCount", this.sessionBean.getCount());
return INDEX_TEMPLATE_VIEW_NAME;
}
We see that we have injected a reference to the HttpSession
as a request mapping method handler parameter.
This will result in a new HttpSession
on the client’s first HTTP request. Subsequent requests from the same client
within the duration of the existing, current HttpSession
will result in the same HttpSession
being injected.
Of course, an HttpSession
is identified by the session’s identifier, which is stored in a Cookie sent between
the client and server during HTTP request processing.
Additionally, we also see that we have injected references to the SessionScopedProxyBean
and RequestScopedProxyBean
in our @Controller
class…
@Autowired
private RequestScopedProxyBean requestBean;
@Autowired
private SessionScopedProxyBean sessionBean;
Based on the class definitions of these two types, as previously shown, these bean instances are scoped according to Spring’s "request" and "session" scopes, respectively. The 2 scopes can only be used in Web applications.
For each and every HTTP request sent by the client (i.e. on each web browser refresh), Spring will create
a new instance of the RequestScopedProxyBean
. This is why the request count increases with every refresh,
which effectively is sending another HTTP request to the server to access and pull the content.
Furthermore, after each new HttpSession
, a new instance of SessionScopedProxyBean
is created. This instance
persists for the duration of the session. If the HttpSession
remains inactive (i.e. no request has been made)
for longer than 10 seconds, the client’s current HttpSession
will expire. Therefore, on any subsequent client
HTTP request, a new HttpSession
will be created by the Web container (e.g. Tomcat), which is replaced by
Spring Session and backed with Apache Geode.
Additionally, this "session" scope bean is stored in the HttpSession
, referenced by a session attribute.
Therefore, you will also notice that the SessionScopedProxyBean
class, unlike the RequestScopedProxyBean
class,
is also java.io.Serializable
…
@Component
@SessionScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopedProxyBean implements Serializable {
...
}
This class is Serializable
since it is stored in the HttpSession
, which will be transferred as part of
the HttpSession
when sent to the Apache Geode cluster to be managed. Therefore, the type must be Serializable
.
Any RequestScopedProxyBeans
are not stored in the HttpSession
and therefore will not be sent to the server,
and as such, do not need to be Serializable
.