Store known attribute names in session (for distributed sessions)

Closes gh-30463
This commit is contained in:
Juergen Hoeller
2024-02-06 16:46:11 +01:00
parent 4ed337247c
commit 80949eb30f
2 changed files with 64 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.web.method.annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -26,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.SessionStatus;
@@ -48,6 +50,16 @@ import org.springframework.web.context.request.WebRequest;
*/
public class SessionAttributesHandler {
/**
* Key for known-attribute-names storage (a String array) as a session attribute.
* <p>This is necessary for consistent handling of type-based session attributes
* in distributed session scenarios where handler methods from the same class
* may get invoked on different servers.
* @since 6.1.4
*/
public static final String SESSION_KNOWN_ATTRIBUTE = SessionAttributesHandler.class.getName() + ".KNOWN";
private final Set<String> attributeNames = new HashSet<>();
private final Set<Class<?>> attributeTypes = new HashSet<>();
@@ -96,12 +108,12 @@ public class SessionAttributesHandler {
*/
public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
Assert.notNull(attributeName, "Attribute name must not be null");
if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
if (this.attributeTypes.contains(attributeType)) {
this.knownAttributeNames.add(attributeName);
return true;
}
else {
return false;
return this.attributeNames.contains(attributeName);
}
}
@@ -117,6 +129,13 @@ public class SessionAttributesHandler {
this.sessionAttributeStore.storeAttribute(request, name, value);
}
});
// Store known attribute names in session (for distributed sessions)
// Only necessary for type-based attributes which get added to knownAttributeNames when touched.
if (!this.attributeTypes.isEmpty()) {
this.sessionAttributeStore.storeAttribute(request,
SESSION_KNOWN_ATTRIBUTE, StringUtils.toStringArray(this.knownAttributeNames));
}
}
/**
@@ -127,6 +146,15 @@ public class SessionAttributesHandler {
* @return a map with handler session attributes, possibly empty
*/
public Map<String, Object> retrieveAttributes(WebRequest request) {
// Restore known attribute names from session (for distributed sessions)
// Only necessary for type-based attributes which get added to knownAttributeNames when touched.
if (!this.attributeTypes.isEmpty()) {
Object known = this.sessionAttributeStore.retrieveAttribute(request, SESSION_KNOWN_ATTRIBUTE);
if (known instanceof String[] retrievedAttributeNames) {
this.knownAttributeNames.addAll(Arrays.asList(retrievedAttributeNames));
}
}
Map<String, Object> attributes = new HashMap<>();
for (String name : this.knownAttributeNames) {
Object value = this.sessionAttributeStore.retrieveAttribute(request, name);