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 的会话的预期行为。对我来说这是一个关键问题,我决定忘记分布式缓存(RedisHazelcast)并使用 MapSessionRepository