Skip to content

Commit 74bb42b

Browse files
kilinkbclozel
authored andcommitted
Optimize Map methods in ServletAttributesMap
ServletAttributesMap inherited default implementations of the size and isEmpty methods from AbstractMap which delegates to the Set returned by entrySet. ServletAttributesMap's entrySet method made this fairly expensive, since it would copy the attributes to a List, then use a Stream to build the Set. To avoid the cost, add implementations of isEmpty / size that don't need to call entrySet at all. Additionally, change entrySet to return a Set view that simply lazily delegates to the underlying servlet request for iteration. Closes gh-32197
1 parent 55717ad commit 74bb42b

File tree

2 files changed

+132
-10
lines changed

2 files changed

+132
-10
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,10 +26,13 @@
2626
import java.security.Principal;
2727
import java.time.Instant;
2828
import java.util.AbstractMap;
29+
import java.util.AbstractSet;
2930
import java.util.ArrayList;
3031
import java.util.Arrays;
3132
import java.util.Collection;
3233
import java.util.Collections;
34+
import java.util.Enumeration;
35+
import java.util.Iterator;
3336
import java.util.List;
3437
import java.util.Locale;
3538
import java.util.Map;
@@ -55,6 +58,7 @@
5558
import org.springframework.http.converter.HttpMessageConverter;
5659
import org.springframework.http.server.RequestPath;
5760
import org.springframework.http.server.ServletServerHttpRequest;
61+
import org.springframework.lang.NonNull;
5862
import org.springframework.lang.Nullable;
5963
import org.springframework.util.CollectionUtils;
6064
import org.springframework.util.LinkedMultiValueMap;
@@ -73,6 +77,7 @@
7377
*
7478
* @author Arjen Poutsma
7579
* @author Sam Brannen
80+
* @author Patrick Strawderman
7681
* @since 5.2
7782
*/
7883
class DefaultServerRequest implements ServerRequest {
@@ -433,18 +438,77 @@ public boolean containsKey(Object key) {
433438

434439
@Override
435440
public void clear() {
436-
List<String> attributeNames = Collections.list(this.servletRequest.getAttributeNames());
437-
attributeNames.forEach(this.servletRequest::removeAttribute);
441+
this.servletRequest.getAttributeNames().asIterator().forEachRemaining(this.servletRequest::removeAttribute);
438442
}
439443

440444
@Override
441445
public Set<Entry<String, Object>> entrySet() {
442-
return Collections.list(this.servletRequest.getAttributeNames()).stream()
443-
.map(name -> {
444-
Object value = this.servletRequest.getAttribute(name);
445-
return new SimpleImmutableEntry<>(name, value);
446-
})
447-
.collect(Collectors.toSet());
446+
return new AbstractSet<>() {
447+
@Override
448+
public Iterator<Entry<String, Object>> iterator() {
449+
return new Iterator<>() {
450+
451+
private final Iterator<String> attributes = ServletAttributesMap.this.servletRequest.getAttributeNames().asIterator();
452+
453+
@Override
454+
public boolean hasNext() {
455+
return this.attributes.hasNext();
456+
}
457+
458+
@Override
459+
public Entry<String, Object> next() {
460+
String attribute = this.attributes.next();
461+
Object value = ServletAttributesMap.this.servletRequest.getAttribute(attribute);
462+
return new SimpleImmutableEntry<>(attribute, value);
463+
}
464+
};
465+
}
466+
467+
@Override
468+
public boolean isEmpty() {
469+
return ServletAttributesMap.this.isEmpty();
470+
}
471+
472+
@Override
473+
public int size() {
474+
return ServletAttributesMap.this.size();
475+
}
476+
477+
@Override
478+
public boolean contains(Object o) {
479+
if (!(o instanceof Map.Entry<?,?> entry)) {
480+
return false;
481+
}
482+
String attribute = (String) entry.getKey();
483+
Object value = ServletAttributesMap.this.servletRequest.getAttribute(attribute);
484+
return value != null && value.equals(entry.getValue());
485+
}
486+
487+
@Override
488+
public boolean addAll(@NonNull Collection<? extends Entry<String, Object>> c) {
489+
throw new UnsupportedOperationException();
490+
}
491+
492+
@Override
493+
public boolean remove(Object o) {
494+
throw new UnsupportedOperationException();
495+
}
496+
497+
@Override
498+
public boolean removeAll(Collection<?> c) {
499+
throw new UnsupportedOperationException();
500+
}
501+
502+
@Override
503+
public boolean retainAll(@NonNull Collection<?> c) {
504+
throw new UnsupportedOperationException();
505+
}
506+
507+
@Override
508+
public void clear() {
509+
throw new UnsupportedOperationException();
510+
}
511+
};
448512
}
449513

450514
@Override
@@ -467,6 +531,22 @@ public Object remove(Object key) {
467531
this.servletRequest.removeAttribute(name);
468532
return value;
469533
}
534+
535+
@Override
536+
public int size() {
537+
Enumeration<String> attributes = this.servletRequest.getAttributeNames();
538+
int size = 0;
539+
while (attributes.hasMoreElements()) {
540+
size++;
541+
attributes.nextElement();
542+
}
543+
return size;
544+
}
545+
546+
@Override
547+
public boolean isEmpty() {
548+
return !this.servletRequest.getAttributeNames().hasMoreElements();
549+
}
470550
}
471551

472552

spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
import java.time.Instant;
2828
import java.time.temporal.ChronoUnit;
2929
import java.util.Collections;
30+
import java.util.Iterator;
3031
import java.util.List;
3132
import java.util.Map;
3233
import java.util.Optional;
3334
import java.util.OptionalLong;
35+
import java.util.Set;
3436

3537
import jakarta.servlet.http.Cookie;
3638
import jakarta.servlet.http.Part;
@@ -61,8 +63,8 @@
6163
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
6264

6365
/**
66+
* Tests for {@link DefaultServerRequest}.
6467
* @author Arjen Poutsma
65-
* @since 5.1
6668
*/
6769
class DefaultServerRequestTests {
6870

@@ -114,6 +116,46 @@ void attribute() {
114116
assertThat(request.attribute("foo")).contains("bar");
115117
}
116118

119+
@Test
120+
void attributes() {
121+
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
122+
servletRequest.setAttribute("foo", "bar");
123+
servletRequest.setAttribute("baz", "qux");
124+
125+
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
126+
127+
Map<String, Object> attributesMap = request.attributes();
128+
assertThat(attributesMap).isNotEmpty();
129+
assertThat(attributesMap).containsEntry("foo", "bar");
130+
assertThat(attributesMap).containsEntry("baz", "qux");
131+
assertThat(attributesMap).doesNotContainEntry("foo", "blah");
132+
133+
Set<Map.Entry<String, Object>> entrySet = attributesMap.entrySet();
134+
assertThat(entrySet).isNotEmpty();
135+
assertThat(entrySet).hasSize(attributesMap.size());
136+
assertThat(entrySet).contains(Map.entry("foo", "bar"));
137+
assertThat(entrySet).contains(Map.entry("baz", "qux"));
138+
assertThat(entrySet).doesNotContain(Map.entry("foo", "blah"));
139+
assertThat(entrySet).isUnmodifiable();
140+
141+
assertThat(entrySet.iterator()).toIterable().contains(Map.entry("foo", "bar"), Map.entry("baz", "qux"));
142+
Iterator<String> attributes = servletRequest.getAttributeNames().asIterator();
143+
Iterator<Map.Entry<String, Object>> entrySetIterator = entrySet.iterator();
144+
while (attributes.hasNext()) {
145+
attributes.next();
146+
assertThat(entrySetIterator).hasNext();
147+
entrySetIterator.next();
148+
}
149+
assertThat(entrySetIterator).isExhausted();
150+
151+
attributesMap.clear();
152+
assertThat(attributesMap).isEmpty();
153+
assertThat(attributesMap).hasSize(0);
154+
assertThat(entrySet).isEmpty();
155+
assertThat(entrySet).hasSize(0);
156+
assertThat(entrySet.iterator()).isExhausted();
157+
}
158+
117159
@Test
118160
void params() {
119161
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);

0 commit comments

Comments
 (0)