Browse Source

Optimize MultiValueMap iteration operations

* use forEach and putIfAbsent to copy headers in DefaultClientRequestBuilder
* use forEach in ReactorClientHttpRequest and ReactorNetty2ClientHttpRequest
* circumvent ReadOnlyHttpHeaders.entrySet()
* ensure the fast path to LinkedCaseInsensitiveMap for forEach and putIfAbsent exists

Closes gh-29972
pull/30420/head
James Yuzawa 2 years ago committed by Brian Clozel
parent
commit
5dacf50b9b
  1. 6
      spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
  2. 20
      spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java
  3. 11
      spring-web/src/main/java/org/springframework/http/HttpHeaders.java
  4. 8
      spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java
  5. 10
      spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
  6. 10
      spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java
  7. 25
      spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java
  8. 7
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java
  9. 13
      spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java

6
spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java

@ -27,6 +27,7 @@ import java.util.Locale; @@ -27,6 +27,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
@ -286,6 +287,11 @@ public class LinkedCaseInsensitiveMap<V> implements Map<String, V>, Serializable @@ -286,6 +287,11 @@ public class LinkedCaseInsensitiveMap<V> implements Map<String, V>, Serializable
return entrySet;
}
@Override
public void forEach(BiConsumer<? super String, ? super V> action) {
this.targetMap.forEach(action);
}
@Override
public LinkedCaseInsensitiveMap<V> clone() {
return new LinkedCaseInsensitiveMap<>(this);

20
spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 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.
@ -22,6 +22,7 @@ import java.util.Collection; @@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import org.springframework.lang.Nullable;
@ -69,15 +70,13 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ @@ -69,15 +70,13 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
@Override
public void addAll(K key, List<? extends V> values) {
List<V> currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(1));
List<V> currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(values.size()));
currentValues.addAll(values);
}
@Override
public void addAll(MultiValueMap<K, V> values) {
for (Entry<K, List<V>> entry : values.entrySet()) {
addAll(entry.getKey(), entry.getValue());
}
values.forEach(this::addAll);
}
@Override
@ -138,6 +137,12 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ @@ -138,6 +137,12 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
return this.targetMap.put(key, value);
}
@Override
@Nullable
public List<V> putIfAbsent(K key, List<V> value) {
return this.targetMap.putIfAbsent(key, value);
}
@Override
@Nullable
public List<V> remove(Object key) {
@ -169,6 +174,11 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ @@ -169,6 +174,11 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
return this.targetMap.entrySet();
}
@Override
public void forEach(BiConsumer<? super K, ? super List<V>> action) {
this.targetMap.forEach(action);
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || this.targetMap.equals(other));

11
spring-web/src/main/java/org/springframework/http/HttpHeaders.java

@ -40,6 +40,7 @@ import java.util.Locale; @@ -40,6 +40,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -1821,6 +1822,16 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable @@ -1821,6 +1822,16 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
return this.headers.entrySet();
}
@Override
public void forEach(BiConsumer<? super String, ? super List<String>> action) {
this.headers.forEach(action);
}
@Override
public List<String> putIfAbsent(String key, List<String> value) {
return this.headers.putIfAbsent(key, value);
}
@Override
public boolean equals(@Nullable Object obj) {

8
spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@ -23,6 +23,7 @@ import java.util.LinkedHashSet; @@ -23,6 +23,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
@ -155,4 +156,9 @@ class ReadOnlyHttpHeaders extends HttpHeaders { @@ -155,4 +156,9 @@ class ReadOnlyHttpHeaders extends HttpHeaders {
Collections::unmodifiableSet));
}
@Override
public void forEach(BiConsumer<? super String, ? super List<String>> action) {
this.headers.forEach((k, vs) -> action.accept(k, Collections.unmodifiableList(vs)));
}
}

10
spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@ -18,7 +18,6 @@ package org.springframework.http.client.reactive; @@ -18,7 +18,6 @@ package org.springframework.http.client.reactive;
import java.net.URI;
import java.nio.file.Path;
import java.util.Collection;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.cookie.DefaultCookie;
@ -129,9 +128,10 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero @@ -129,9 +128,10 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero
@Override
protected void applyCookies() {
getCookies().values().stream().flatMap(Collection::stream)
.map(cookie -> new DefaultCookie(cookie.getName(), cookie.getValue()))
.forEach(this.request::addCookie);
getCookies().values().forEach(values -> values.forEach(value -> {
DefaultCookie cookie = new DefaultCookie(value.getName(), value.getValue());
this.request.addCookie(cookie);
}));
}
@Override

10
spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@ -18,7 +18,6 @@ package org.springframework.http.client.reactive; @@ -18,7 +18,6 @@ package org.springframework.http.client.reactive;
import java.net.URI;
import java.nio.file.Path;
import java.util.Collection;
import io.netty5.buffer.Buffer;
import io.netty5.handler.codec.http.headers.DefaultHttpCookiePair;
@ -130,9 +129,10 @@ class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implement @@ -130,9 +129,10 @@ class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implement
@Override
protected void applyCookies() {
getCookies().values().stream().flatMap(Collection::stream)
.map(cookie -> new DefaultHttpCookiePair(cookie.getName(), cookie.getValue()))
.forEach(this.request::addCookie);
getCookies().values().forEach(values -> values.forEach(value -> {
DefaultHttpCookiePair cookie = new DefaultHttpCookiePair(value.getName(), value.getValue());
this.request.addCookie(cookie);
}));
}
@Override

25
spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java

@ -704,6 +704,31 @@ public class HttpHeadersTests { @@ -704,6 +704,31 @@ public class HttpHeadersTests {
assertThat(readOnlyHttpHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
}
@Test
void readOnlyHttpHeadersCopyOrderTest() {
headers.add("aardvark", "enigma");
headers.add("beaver", "enigma");
headers.add("cat", "enigma");
headers.add("dog", "enigma");
headers.add("elephant", "enigma");
String[] expectedKeys = new String[] { "aardvark", "beaver", "cat", "dog", "elephant" };
HttpHeaders readOnlyHttpHeaders = HttpHeaders.readOnlyHttpHeaders(headers);
HttpHeaders forEachHeaders = new HttpHeaders();
readOnlyHttpHeaders.forEach(forEachHeaders::putIfAbsent);
assertThat(forEachHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
HttpHeaders putAllHeaders = new HttpHeaders();
putAllHeaders.putAll(readOnlyHttpHeaders);
assertThat(putAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
HttpHeaders addAllHeaders = new HttpHeaders();
addAllHeaders.addAll(readOnlyHttpHeaders);
assertThat(addAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
}
@Test // gh-25034
void equalsUnwrapsHttpHeaders() {
HttpHeaders headers1 = new HttpHeaders();

7
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 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.
@ -255,10 +255,7 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder { @@ -255,10 +255,7 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder {
public Mono<Void> writeTo(ClientHttpRequest request, ExchangeStrategies strategies) {
HttpHeaders requestHeaders = request.getHeaders();
if (!this.headers.isEmpty()) {
this.headers.entrySet().stream()
.filter(entry -> !requestHeaders.containsKey(entry.getKey()))
.forEach(entry -> requestHeaders
.put(entry.getKey(), entry.getValue()));
this.headers.forEach(requestHeaders::putIfAbsent);
}
MultiValueMap<String, HttpCookie> requestCookies = request.getCookies();

13
spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 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.
@ -22,6 +22,7 @@ import java.util.Collections; @@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
@ -295,6 +296,16 @@ public class WebSocketHttpHeaders extends HttpHeaders { @@ -295,6 +296,16 @@ public class WebSocketHttpHeaders extends HttpHeaders {
return this.headers.entrySet();
}
@Override
public void forEach(BiConsumer<? super String, ? super List<String>> action) {
this.headers.forEach(action);
}
@Override
public List<String> putIfAbsent(String key, List<String> value) {
return this.headers.putIfAbsent(key, value);
}
@Override
public boolean equals(@Nullable Object other) {

Loading…
Cancel
Save