Better support for @SessionAttributes in clustered environments
A list of "known" session attributes (listed in @SessionAttributes) was gradually built as attributes get added to the model. In a failover scenario that knowledge is lost causing session attributes to be potentially re-initialized via @ModelAttribute methods. With this change @SessionAttributes listed by name are immediately added to he list of "known" session attributes thus this knowledge is not lost after a failover. Attributes listed by type however still must be discovered as they get added to the model.
This commit is contained in:
parent
81e25b91c2
commit
871336a8c8
|
|
@ -34,6 +34,7 @@ Changes in version 3.1.1 (2012-02-06)
|
||||||
* add normalize() method to UriComponents
|
* add normalize() method to UriComponents
|
||||||
* remove empty path segments from input to UriComponentsBuilder
|
* remove empty path segments from input to UriComponentsBuilder
|
||||||
* add fromRequestUri(request) and fromCurrentRequestUri() methods to ServletUriCommonentsBuilder
|
* add fromRequestUri(request) and fromCurrentRequestUri() methods to ServletUriCommonentsBuilder
|
||||||
|
* improve @SessionAttributes handling to provide better support for clustered sessions
|
||||||
|
|
||||||
Changes in version 3.1 GA (2011-12-12)
|
Changes in version 3.1 GA (2011-12-12)
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2011 the original author or authors.
|
* Copyright 2002-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -33,7 +33,7 @@ import org.springframework.web.context.request.WebRequest;
|
||||||
/**
|
/**
|
||||||
* Manages controller-specific session attributes declared via
|
* Manages controller-specific session attributes declared via
|
||||||
* {@link SessionAttributes @SessionAttributes}. Actual storage is
|
* {@link SessionAttributes @SessionAttributes}. Actual storage is
|
||||||
* performed via {@link SessionAttributeStore}.
|
* delegated to a {@link SessionAttributeStore} instance.
|
||||||
*
|
*
|
||||||
* <p>When a controller annotated with {@code @SessionAttributes} adds
|
* <p>When a controller annotated with {@code @SessionAttributes} adds
|
||||||
* attributes to its model, those attributes are checked against names and
|
* attributes to its model, those attributes are checked against names and
|
||||||
|
|
@ -50,13 +50,14 @@ public class SessionAttributesHandler {
|
||||||
|
|
||||||
private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();
|
private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();
|
||||||
|
|
||||||
private final Set<String> resolvedAttributeNames = Collections.synchronizedSet(new HashSet<String>(4));
|
private final Set<String> knownAttributeNames = Collections.synchronizedSet(new HashSet<String>(4));
|
||||||
|
|
||||||
private final SessionAttributeStore sessionAttributeStore;
|
private final SessionAttributeStore sessionAttributeStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance for a controller type. Session attribute names/types
|
* Create a new instance for a controller type. Session attribute names and
|
||||||
* are extracted from a type-level {@code @SessionAttributes} if found.
|
* types are extracted from the {@code @SessionAttributes} annotation, if
|
||||||
|
* present, on the given type.
|
||||||
* @param handlerType the controller type
|
* @param handlerType the controller type
|
||||||
* @param sessionAttributeStore used for session access
|
* @param sessionAttributeStore used for session access
|
||||||
*/
|
*/
|
||||||
|
|
@ -69,32 +70,33 @@ public class SessionAttributesHandler {
|
||||||
this.attributeNames.addAll(Arrays.asList(annotation.value()));
|
this.attributeNames.addAll(Arrays.asList(annotation.value()));
|
||||||
this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
|
this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.knownAttributeNames.addAll(this.attributeNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the controller represented by this instance has declared session
|
* Whether the controller represented by this instance has declared any
|
||||||
* attribute names or types of interest via {@link SessionAttributes}.
|
* session attributes through an {@link SessionAttributes} annotation.
|
||||||
*/
|
*/
|
||||||
public boolean hasSessionAttributes() {
|
public boolean hasSessionAttributes() {
|
||||||
return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
|
return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the attribute name and/or type match those specified in the
|
* Whether the attribute name or type match the names and types specified
|
||||||
* controller's {@code @SessionAttributes} annotation.
|
* via {@code @SessionAttributes} in underlying controller.
|
||||||
*
|
*
|
||||||
* <p>Attributes successfully resolved through this method are "remembered"
|
* <p>Attributes successfully resolved through this method are "remembered"
|
||||||
* and used in {@link #retrieveAttributes(WebRequest)} and
|
* and subsequently used in {@link #retrieveAttributes(WebRequest)} and
|
||||||
* {@link #cleanupAttributes(WebRequest)}. In other words, retrieval and
|
* {@link #cleanupAttributes(WebRequest)}.
|
||||||
* cleanup only affect attributes previously resolved through here.
|
|
||||||
*
|
*
|
||||||
* @param attributeName the attribute name to check; must not be null
|
* @param attributeName the attribute name to check, never {@code null}
|
||||||
* @param attributeType the type for the attribute; or {@code null}
|
* @param attributeType the type for the attribute, possibly {@code null}
|
||||||
*/
|
*/
|
||||||
public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
|
public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
|
||||||
Assert.notNull(attributeName, "Attribute name must not be null");
|
Assert.notNull(attributeName, "Attribute name must not be null");
|
||||||
if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
|
if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
|
||||||
this.resolvedAttributeNames.add(attributeName);
|
this.knownAttributeNames.add(attributeName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -103,7 +105,7 @@ public class SessionAttributesHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a subset of the given attributes in the session. Attributes not
|
* Store a subset of the given attributes in the session. Attributes not
|
||||||
* declared as session attributes via {@code @SessionAttributes} are ignored.
|
* declared as session attributes via {@code @SessionAttributes} are ignored.
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
* @param attributes candidate attributes for session storage
|
* @param attributes candidate attributes for session storage
|
||||||
|
|
@ -120,15 +122,15 @@ public class SessionAttributesHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve "known" attributes from the session -- i.e. attributes listed
|
* Retrieve "known" attributes from the session, i.e. attributes listed
|
||||||
* in {@code @SessionAttributes} and previously stored in the in the model
|
* by name in {@code @SessionAttributes} or attributes previously stored
|
||||||
* at least once.
|
* in the model that matched by type.
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
* @return a map with handler session attributes; possibly empty.
|
* @return a map with handler session attributes, possibly empty
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> retrieveAttributes(WebRequest request) {
|
public Map<String, Object> retrieveAttributes(WebRequest request) {
|
||||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||||
for (String name : this.resolvedAttributeNames) {
|
for (String name : this.knownAttributeNames) {
|
||||||
Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
|
Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
attributes.put(name, value);
|
attributes.put(name, value);
|
||||||
|
|
@ -138,13 +140,13 @@ public class SessionAttributesHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans "known" attributes from the session - i.e. attributes listed
|
* Remove "known" attributes from the session, i.e. attributes listed
|
||||||
* in {@code @SessionAttributes} and previously stored in the in the model
|
* by name in {@code @SessionAttributes} or attributes previously stored
|
||||||
* at least once.
|
* in the model that matched by type.
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
*/
|
*/
|
||||||
public void cleanupAttributes(WebRequest request) {
|
public void cleanupAttributes(WebRequest request) {
|
||||||
for (String attributeName : this.resolvedAttributeNames) {
|
for (String attributeName : this.knownAttributeNames) {
|
||||||
this.sessionAttributeStore.cleanupAttribute(request, attributeName);
|
this.sessionAttributeStore.cleanupAttribute(request, attributeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2011 the original author or authors.
|
* Copyright 2002-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -24,7 +24,6 @@ import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
@ -72,14 +71,18 @@ public class SessionAttributesHandlerTests {
|
||||||
sessionAttributeStore.storeAttribute(request, "attr1", "value1");
|
sessionAttributeStore.storeAttribute(request, "attr1", "value1");
|
||||||
sessionAttributeStore.storeAttribute(request, "attr2", "value2");
|
sessionAttributeStore.storeAttribute(request, "attr2", "value2");
|
||||||
sessionAttributeStore.storeAttribute(request, "attr3", new TestBean());
|
sessionAttributeStore.storeAttribute(request, "attr3", new TestBean());
|
||||||
|
sessionAttributeStore.storeAttribute(request, "attr4", new TestBean());
|
||||||
|
|
||||||
// Resolve successfully handler session attributes once
|
assertEquals("Named attributes (attr1, attr2) should be 'known' right away",
|
||||||
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null));
|
new HashSet<String>(asList("attr1", "attr2")),
|
||||||
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class));
|
sessionAttributesHandler.retrieveAttributes(request).keySet());
|
||||||
|
|
||||||
Map<String, ?> attributes = sessionAttributesHandler.retrieveAttributes(request);
|
// Resolve 'attr3' by type
|
||||||
|
sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class);
|
||||||
|
|
||||||
assertEquals(new HashSet<String>(asList("attr1", "attr3")), attributes.keySet());
|
assertEquals("Named attributes (attr1, attr2) and resolved attribute (att3) should be 'known'",
|
||||||
|
new HashSet<String>(asList("attr1", "attr2", "attr3")),
|
||||||
|
sessionAttributesHandler.retrieveAttributes(request).keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -88,14 +91,16 @@ public class SessionAttributesHandlerTests {
|
||||||
sessionAttributeStore.storeAttribute(request, "attr2", "value2");
|
sessionAttributeStore.storeAttribute(request, "attr2", "value2");
|
||||||
sessionAttributeStore.storeAttribute(request, "attr3", new TestBean());
|
sessionAttributeStore.storeAttribute(request, "attr3", new TestBean());
|
||||||
|
|
||||||
// Resolve successfully handler session attributes once
|
|
||||||
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null));
|
|
||||||
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class));
|
|
||||||
|
|
||||||
sessionAttributesHandler.cleanupAttributes(request);
|
sessionAttributesHandler.cleanupAttributes(request);
|
||||||
|
|
||||||
assertNull(sessionAttributeStore.retrieveAttribute(request, "attr1"));
|
assertNull(sessionAttributeStore.retrieveAttribute(request, "attr1"));
|
||||||
assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr2"));
|
assertNull(sessionAttributeStore.retrieveAttribute(request, "attr2"));
|
||||||
|
assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr3"));
|
||||||
|
|
||||||
|
// Resolve 'attr3' by type
|
||||||
|
sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class);
|
||||||
|
sessionAttributesHandler.cleanupAttributes(request);
|
||||||
|
|
||||||
assertNull(sessionAttributeStore.retrieveAttribute(request, "attr3"));
|
assertNull(sessionAttributeStore.retrieveAttribute(request, "attr3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,11 +111,6 @@ public class SessionAttributesHandlerTests {
|
||||||
model.put("attr2", "value2");
|
model.put("attr2", "value2");
|
||||||
model.put("attr3", new TestBean());
|
model.put("attr3", new TestBean());
|
||||||
|
|
||||||
// Resolve successfully handler session attributes once
|
|
||||||
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null));
|
|
||||||
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr2", null));
|
|
||||||
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class));
|
|
||||||
|
|
||||||
sessionAttributesHandler.storeAttributes(request, model);
|
sessionAttributesHandler.storeAttributes(request, model);
|
||||||
|
|
||||||
assertEquals("value1", sessionAttributeStore.retrieveAttribute(request, "attr1"));
|
assertEquals("value1", sessionAttributeStore.retrieveAttribute(request, "attr1"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue