Spring REST 网络服务和会话持久性
Spring REST webservice and session persistence
我有这个 Spring 网络服务测试代码:
@RestController
@RequestMapping("/counter")
public class CounterController
{
@Autowired
private Counter counter;
@RequestMapping(value = "/inc", method = GET)
public int inc() throws Exception {
counter.incCounter();
return counter.getCounter();
}
@RequestMapping(value = "/get", method = GET)
public int get() throws Exception {
Thread.sleep(5000);
return counter.getCounter();
}
}
其中 Counter 是一个会话范围的对象
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Counter implements Serializable {
private static final long serialVersionUID = 9162936878293396831L;
private int value;
public int getCounter() {
return value;
}
public void incCounter() {
value += 1;
}
}
会话配置
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800)
public class HttpSessionConfig {
@Bean
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
@Bean
public HttpSessionStrategy httpSessionStrategy(){
return new HeaderHttpSessionStrategy();
}
}
如您所见,get()
方法休眠 5 秒,returns 计数器的值。
问题是,如果我在 get()
执行期间多次调用 inc()
,所有计数器更改都会丢失,因为当 get()
完成时,returns 计数器的值会丢失有什么时候开始执行的。奇怪的问题是 get()
完成后会保留计数器(它是一个会话对象)并且当此操作完成时所有更改都将丢失。
是否存在一种方法来防止不修改会话对象的函数不保留它?
更新:我认为 Spring 代码证实了这种错误行为。 class ServletRequestAttributes
的这段代码显示,访问的每个会话对象(无论访问是否为读取)都标记为在 web 服务操作完成时保存:
@Override
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
根据 Spring 会话文档:
Optimized Writes
The Session instances managed by RedisOperationsSessionRepository
keeps track of the properties that have changed and only updates
those. This means if an attribute is written once and read many times
we only need to write that attribute once.
或者文档有误,或者我做错了什么。
我认为您在测试代码时犯了一些错误。我刚刚对其进行了测试,它按预期工作。
我使用了 SoapUI,在 Cookie(同一会话)中创建了 2 个具有相同 JSESSIONID
值的请求。
然后我请求 /get,同时在第二个请求 window 中,我发送了垃圾邮件 /inc。
/get返回的是/inc的个数。 (开始值是 0 ,比我在 /get 睡觉时将它增加到 11 。最后, /get 返回 11)。
我建议您仔细检查您的会话是否有问题。
编辑:带有附加日志的代码:(我已将休眠时间增加到 10000):
2016-04-06 11:56:10.977 INFO 7884 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 14 ms
2016-04-06 11:56:11.014 INFO 7884 --- [nio-8080-exec-1] c.p.controller.TestServiceController : Before 10sec counter value: 0
2016-04-06 11:56:21.015 INFO 7884 --- [nio-8080-exec-1] c.p.controller.TestServiceController : After 10sec counter value: 0
2016-04-06 11:56:36.955 INFO 7884 --- [nio-8080-exec-2] c.p.controller.TestServiceController : Before 10sec counter value: 0
2016-04-06 11:56:46.956 INFO 7884 --- [nio-8080-exec-2] c.p.controller.TestServiceController : After 10sec counter value: 0
2016-04-06 11:56:50.558 INFO 7884 --- [nio-8080-exec-3] c.p.controller.TestServiceController : Incrementing counter value: 1
2016-04-06 11:56:53.494 INFO 7884 --- [nio-8080-exec-4] c.p.controller.TestServiceController : Before 10sec counter value: 1
2016-04-06 11:57:03.496 INFO 7884 --- [nio-8080-exec-4] c.p.controller.TestServiceController : After 10sec counter value: 1
2016-04-06 11:57:05.600 INFO 7884 --- [nio-8080-exec-5] c.p.controller.TestServiceController : Before 10sec counter value: 1
2016-04-06 11:57:06.715 INFO 7884 --- [nio-8080-exec-6] c.p.controller.TestServiceController : Incrementing counter value: 2
2016-04-06 11:57:06.869 INFO 7884 --- [nio-8080-exec-7] c.p.controller.TestServiceController : Incrementing counter value: 3
2016-04-06 11:57:07.038 INFO 7884 --- [nio-8080-exec-8] c.p.controller.TestServiceController : Incrementing counter value: 4
2016-04-06 11:57:07.186 INFO 7884 --- [nio-8080-exec-9] c.p.controller.TestServiceController : Incrementing counter value: 5
2016-04-06 11:57:07.321 INFO 7884 --- [io-8080-exec-10] c.p.controller.TestServiceController : Incrementing counter value: 6
2016-04-06 11:57:07.478 INFO 7884 --- [nio-8080-exec-1] c.p.controller.TestServiceController : Incrementing counter value: 7
2016-04-06 11:57:07.641 INFO 7884 --- [nio-8080-exec-2] c.p.controller.TestServiceController : Incrementing counter value: 8
2016-04-06 11:57:07.794 INFO 7884 --- [nio-8080-exec-3] c.p.controller.TestServiceController : Incrementing counter value: 9
2016-04-06 11:57:07.967 INFO 7884 --- [nio-8080-exec-4] c.p.controller.TestServiceController : Incrementing counter value: 10
2016-04-06 11:57:08.121 INFO 7884 --- [nio-8080-exec-6] c.p.controller.TestServiceController : Incrementing counter value: 11
2016-04-06 11:57:15.602 INFO 7884 --- [nio-8080-exec-5] c.p.controller.TestServiceController : After 10sec counter value: 11
似乎与此问题无关,这是 Spring 具有会话作用域 bean 的会话的预期行为。对我来说这是一个关键问题,我决定忘记分布式缓存(Redis
和 Hazelcast
)并使用 MapSessionRepository
我有这个 Spring 网络服务测试代码:
@RestController
@RequestMapping("/counter")
public class CounterController
{
@Autowired
private Counter counter;
@RequestMapping(value = "/inc", method = GET)
public int inc() throws Exception {
counter.incCounter();
return counter.getCounter();
}
@RequestMapping(value = "/get", method = GET)
public int get() throws Exception {
Thread.sleep(5000);
return counter.getCounter();
}
}
其中 Counter 是一个会话范围的对象
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Counter implements Serializable {
private static final long serialVersionUID = 9162936878293396831L;
private int value;
public int getCounter() {
return value;
}
public void incCounter() {
value += 1;
}
}
会话配置
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800)
public class HttpSessionConfig {
@Bean
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
@Bean
public HttpSessionStrategy httpSessionStrategy(){
return new HeaderHttpSessionStrategy();
}
}
如您所见,get()
方法休眠 5 秒,returns 计数器的值。
问题是,如果我在 get()
执行期间多次调用 inc()
,所有计数器更改都会丢失,因为当 get()
完成时,returns 计数器的值会丢失有什么时候开始执行的。奇怪的问题是 get()
完成后会保留计数器(它是一个会话对象)并且当此操作完成时所有更改都将丢失。
是否存在一种方法来防止不修改会话对象的函数不保留它?
更新:我认为 Spring 代码证实了这种错误行为。 class ServletRequestAttributes
的这段代码显示,访问的每个会话对象(无论访问是否为读取)都标记为在 web 服务操作完成时保存:
@Override
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
根据 Spring 会话文档:
Optimized Writes
The Session instances managed by RedisOperationsSessionRepository keeps track of the properties that have changed and only updates those. This means if an attribute is written once and read many times we only need to write that attribute once.
或者文档有误,或者我做错了什么。
我认为您在测试代码时犯了一些错误。我刚刚对其进行了测试,它按预期工作。
我使用了 SoapUI,在 Cookie(同一会话)中创建了 2 个具有相同 JSESSIONID
值的请求。
然后我请求 /get,同时在第二个请求 window 中,我发送了垃圾邮件 /inc。
/get返回的是/inc的个数。 (开始值是 0 ,比我在 /get 睡觉时将它增加到 11 。最后, /get 返回 11)。
我建议您仔细检查您的会话是否有问题。
编辑:带有附加日志的代码:(我已将休眠时间增加到 10000):
2016-04-06 11:56:10.977 INFO 7884 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 14 ms
2016-04-06 11:56:11.014 INFO 7884 --- [nio-8080-exec-1] c.p.controller.TestServiceController : Before 10sec counter value: 0
2016-04-06 11:56:21.015 INFO 7884 --- [nio-8080-exec-1] c.p.controller.TestServiceController : After 10sec counter value: 0
2016-04-06 11:56:36.955 INFO 7884 --- [nio-8080-exec-2] c.p.controller.TestServiceController : Before 10sec counter value: 0
2016-04-06 11:56:46.956 INFO 7884 --- [nio-8080-exec-2] c.p.controller.TestServiceController : After 10sec counter value: 0
2016-04-06 11:56:50.558 INFO 7884 --- [nio-8080-exec-3] c.p.controller.TestServiceController : Incrementing counter value: 1
2016-04-06 11:56:53.494 INFO 7884 --- [nio-8080-exec-4] c.p.controller.TestServiceController : Before 10sec counter value: 1
2016-04-06 11:57:03.496 INFO 7884 --- [nio-8080-exec-4] c.p.controller.TestServiceController : After 10sec counter value: 1
2016-04-06 11:57:05.600 INFO 7884 --- [nio-8080-exec-5] c.p.controller.TestServiceController : Before 10sec counter value: 1
2016-04-06 11:57:06.715 INFO 7884 --- [nio-8080-exec-6] c.p.controller.TestServiceController : Incrementing counter value: 2
2016-04-06 11:57:06.869 INFO 7884 --- [nio-8080-exec-7] c.p.controller.TestServiceController : Incrementing counter value: 3
2016-04-06 11:57:07.038 INFO 7884 --- [nio-8080-exec-8] c.p.controller.TestServiceController : Incrementing counter value: 4
2016-04-06 11:57:07.186 INFO 7884 --- [nio-8080-exec-9] c.p.controller.TestServiceController : Incrementing counter value: 5
2016-04-06 11:57:07.321 INFO 7884 --- [io-8080-exec-10] c.p.controller.TestServiceController : Incrementing counter value: 6
2016-04-06 11:57:07.478 INFO 7884 --- [nio-8080-exec-1] c.p.controller.TestServiceController : Incrementing counter value: 7
2016-04-06 11:57:07.641 INFO 7884 --- [nio-8080-exec-2] c.p.controller.TestServiceController : Incrementing counter value: 8
2016-04-06 11:57:07.794 INFO 7884 --- [nio-8080-exec-3] c.p.controller.TestServiceController : Incrementing counter value: 9
2016-04-06 11:57:07.967 INFO 7884 --- [nio-8080-exec-4] c.p.controller.TestServiceController : Incrementing counter value: 10
2016-04-06 11:57:08.121 INFO 7884 --- [nio-8080-exec-6] c.p.controller.TestServiceController : Incrementing counter value: 11
2016-04-06 11:57:15.602 INFO 7884 --- [nio-8080-exec-5] c.p.controller.TestServiceController : After 10sec counter value: 11
似乎与此问题无关,这是 Spring 具有会话作用域 bean 的会话的预期行为。对我来说这是一个关键问题,我决定忘记分布式缓存(Redis
和 Hazelcast
)并使用 MapSessionRepository