From 8fc3b3bc37c3748e90b089abb24e4360857ab335 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 23 Jun 2017 14:45:46 -0400 Subject: [PATCH] Add WebTestClientConfigurer Issue: SPR-15674 --- .../server/AbstractMockServerSpec.java | 3 +- .../reactive/server/DefaultWebTestClient.java | 8 +- .../server/DefaultWebTestClientBuilder.java | 43 +++-- .../server/ExchangeMutatorWebFilter.java | 123 --------------- .../reactive/server/MockServerConfigurer.java | 1 + .../web/reactive/server/WebTestClient.java | 12 +- .../server/WebTestClientConfigurer.java | 45 ++++++ .../server/samples/ExchangeMutatorTests.java | 148 ++++++++++++++++++ .../ExchangeMutatorWebFilterTests.java | 106 ------------- .../server/adapter/WebHttpHandlerBuilder.java | 21 +++ .../handler/ExceptionHandlingWebHandler.java | 3 +- 11 files changed, 260 insertions(+), 253 deletions(-) delete mode 100644 spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeMutatorWebFilter.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClientConfigurer.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java delete mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java index 8a1b5a0f89..5dd6cc0eeb 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java @@ -18,7 +18,6 @@ package org.springframework.test.web.reactive.server; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.springframework.web.server.WebFilter; @@ -61,7 +60,7 @@ abstract class AbstractMockServerSpec> WebHttpHandlerBuilder builder = initHttpHandlerBuilder(); builder.filters(theFilters -> theFilters.addAll(0, this.filters)); this.configurers.forEach(configurer -> configurer.beforeServerCreated(builder)); - return new DefaultWebTestClientBuilder(builder.build()); + return new DefaultWebTestClientBuilder(builder); } /** diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 1fe573acb4..479d6db7e9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -71,15 +71,18 @@ class DefaultWebTestClient implements WebTestClient { private final Duration timeout; + private final WebTestClient.Builder builder; + private final AtomicLong requestIndex = new AtomicLong(); DefaultWebTestClient(WebClient.Builder clientBuilder, ClientHttpConnector connector, - @Nullable Duration timeout) { + @Nullable Duration timeout, WebTestClient.Builder webTestClientBuilder) { Assert.notNull(clientBuilder, "WebClient.Builder is required"); this.wiretapConnector = new WiretapConnector(connector); this.webClient = clientBuilder.clientConnector(this.wiretapConnector).build(); this.timeout = (timeout != null ? timeout : Duration.ofSeconds(5)); + this.builder = webTestClientBuilder; } @@ -125,8 +128,7 @@ class DefaultWebTestClient implements WebTestClient { @Override public Builder mutate() { - return new DefaultWebTestClientBuilder(this.wiretapConnector.getDelegate(), - this.webClient.mutate(), this.timeout); + return this.builder; } private > UriSpec toUriSpec( diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index b02194c862..5d07813ddf 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -23,12 +23,13 @@ import java.util.function.Consumer; import org.springframework.http.HttpHeaders; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.server.reactive.HttpHandler; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.util.UriBuilderFactory; /** @@ -41,29 +42,32 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { private final WebClient.Builder webClientBuilder; + private final WebHttpHandlerBuilder httpHandlerBuilder; + private final ClientHttpConnector connector; private Duration responseTimeout; DefaultWebTestClientBuilder() { - this(new ReactorClientHttpConnector()); + this(null, null, new ReactorClientHttpConnector(), null); } - DefaultWebTestClientBuilder(HttpHandler httpHandler) { - this(new HttpHandlerConnector(httpHandler)); + DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder) { + this(null, httpHandlerBuilder, null, null); } - DefaultWebTestClientBuilder(ClientHttpConnector connector) { - this(connector, null, null); - } - - DefaultWebTestClientBuilder(ClientHttpConnector connector, - @Nullable WebClient.Builder webClientBuilder, + DefaultWebTestClientBuilder(@Nullable WebClient.Builder webClientBuilder, + @Nullable WebHttpHandlerBuilder httpHandlerBuilder, + @Nullable ClientHttpConnector connector, @Nullable Duration responseTimeout) { - this.connector = connector; + Assert.isTrue(httpHandlerBuilder != null || connector !=null, + "Either WebHttpHandlerBuilder or ClientHttpConnector must be provided"); + this.webClientBuilder = (webClientBuilder != null ? webClientBuilder : WebClient.builder()); + this.httpHandlerBuilder = (httpHandlerBuilder != null ? httpHandlerBuilder.cloneBuilder() : null); + this.connector = connector; this.responseTimeout = responseTimeout; } @@ -129,9 +133,24 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { return this; } + @Override + public WebTestClient.Builder apply(WebTestClientConfigurer configurer) { + configurer.afterConfigurerAdded(this, this.httpHandlerBuilder, this.connector); + return this; + } + @Override public WebTestClient build() { - return new DefaultWebTestClient(this.webClientBuilder, this.connector, this.responseTimeout); + + ClientHttpConnector connectorToUse = (this.connector != null ? this.connector : + new HttpHandlerConnector(this.httpHandlerBuilder.build())); + + DefaultWebTestClientBuilder webTestClientBuilder = new DefaultWebTestClientBuilder( + this.webClientBuilder.build().mutate(), this.httpHandlerBuilder, + this.connector, this.responseTimeout); + + return new DefaultWebTestClient(this.webClientBuilder, + connectorToUse, this.responseTimeout, webTestClientBuilder); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeMutatorWebFilter.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeMutatorWebFilter.java deleted file mode 100644 index 7cb247394b..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeMutatorWebFilter.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2002-2017 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.test.web.reactive.server; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -import reactor.core.publisher.Mono; - -import org.springframework.http.HttpHeaders; -import org.springframework.util.Assert; -import org.springframework.web.reactive.function.client.ExchangeFilterFunction; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; - - -/** - * Apply {@code ServerWebExchange} transformations during "mock" server tests - * with the {@code WebTestClient}. - * - *

Register the {@code WebFilter} while setting up the mock server through - * one of the following: - *

    - *
  • {@link WebTestClient#bindToController} - *
  • {@link WebTestClient#bindToRouterFunction} - *
  • {@link WebTestClient#bindToApplicationContext} - *
  • {@link WebTestClient#bindToWebHandler} - *
- * - *

Example usage: - * - *

- * Function<ServerWebExchange, ServerWebExchange> fn1 = ...;
- * Function<ServerWebExchange, ServerWebExchange> fn2 = ...;
- *
- * ExchangeMutatorWebFilter mutator = new ExchangeMutatorWebFilter(fn1().andThen(fn2()));
- * WebTestClient client = WebTestClient.bindToController(new MyController()).webFilter(mutator).build();
- * 
- * - * - *

It is also possible to apply "per request" transformations: - * - *

- * ExchangeMutatorWebFilter mutator = new ExchangeMutatorWebFilter();
- * WebTestClient client = WebTestClient.bindToController(new MyController()).webFilter(mutator).build();
- *
- * Function<ServerWebExchange, ServerWebExchange> fn1 = ...;
- * Function<ServerWebExchange, ServerWebExchange> fn2 = ...;
- *
- * client.filter(mutator.perClient(fn1().andThen(fn2()))).get().uri("/").exchange();
- * 
- * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class ExchangeMutatorWebFilter implements WebFilter { - - private final Function processor; - - private final Map> perRequestProcessors = - new ConcurrentHashMap<>(4); - - - public ExchangeMutatorWebFilter() { - this(exchange -> exchange); - } - - public ExchangeMutatorWebFilter(Function processor) { - Assert.notNull(processor, "'processor' is required"); - this.processor = processor; - } - - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - exchange = getProcessor(exchange).apply(exchange); - return chain.filter(exchange); - } - - private Function getProcessor(ServerWebExchange exchange) { - String id = getRequestId(exchange.getRequest().getHeaders()); - Function clientMutator = this.perRequestProcessors.remove(id); - return (clientMutator != null ? this.processor.andThen(clientMutator) : this.processor); - } - - private String getRequestId(HttpHeaders headers) { - String id = headers.getFirst(WebTestClient.WEBTESTCLIENT_REQUEST_ID); - Assert.notNull(id, "No \"" + WebTestClient.WEBTESTCLIENT_REQUEST_ID + "\" header"); - return id; - } - - /** - * Apply the given processor only to requests performed through the client - * instance filtered with the returned filter. See class-level Javadoc for - * sample code. - * @param processor the exchange processor to use - * @return client filter for use with {@link WebTestClient#filter} - */ - public ExchangeFilterFunction perClient(Function processor) { - return (request, next) -> { - String id = getRequestId(request.headers()); - this.perRequestProcessors.compute(id, - (s, value) -> value != null ? value.andThen(processor) : processor); - return next.exchange(request); - }; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerConfigurer.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerConfigurer.java index 31994f8cb4..c60c1e2148 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerConfigurer.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerConfigurer.java @@ -38,6 +38,7 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder; * * @author Rossen Stoyanchev * @since 5.0 + * @see WebTestClientConfigurer */ public interface MockServerConfigurer { diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 22f1d518c1..1e5165057d 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -193,13 +193,7 @@ public interface WebTestClient { /** * Register one or more {@link WebFilter} instances to apply to the * mock server. - * - *

This could be used for example to apply {@code ServerWebExchange} - * transformations such as setting the Principal (for all requests or a - * subset) via {@link ExchangeMutatorWebFilter}. - * * @param filter one or more filters - * @see ExchangeMutatorWebFilter */ T webFilter(WebFilter... filter); @@ -380,6 +374,12 @@ public interface WebTestClient { */ Builder responseTimeout(Duration timeout); + /** + * + * @param configurer + * @return + */ + Builder apply(WebTestClientConfigurer configurer); /** * Build the {@link WebTestClient} instance. diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClientConfigurer.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClientConfigurer.java new file mode 100644 index 0000000000..0b390c0ba4 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClientConfigurer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.reactive.server; + +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.lang.Nullable; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +/** + * Contract that frameworks or applications can use to pre-package a set of + * customizations to a {@link WebTestClient.Builder} and expose that + * as a shortcut. + * + * @author Rossen Stoyanchev + * @since 5.0 + * @see MockServerConfigurer + */ +public interface WebTestClientConfigurer { + + /** + * Invoked once only, immediately (i.e. before this method returns). + * @param builder the WebTestClient builder to make changes to + * @param httpHandlerBuilder the builder for the "mock server" HttpHandler + * this client was configured for "mock server" testing + * @param connector the connector for "live" integration tests if this + * server was configured for live integration testing + */ + void afterConfigurerAdded(WebTestClient.Builder builder, + @Nullable WebHttpHandlerBuilder httpHandlerBuilder, + @Nullable ClientHttpConnector connector); + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java new file mode 100644 index 0000000000..12f310497f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.reactive.server.samples; + +import java.security.Principal; + +import org.junit.Before; +import org.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.lang.Nullable; +import org.springframework.test.web.reactive.server.MockServerConfigurer; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClientConfigurer; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +/** + * Samples tests that demonstrate applying ServerWebExchange initialization. + * @author Rossen Stoyanchev + */ +public class ExchangeMutatorTests { + + private WebTestClient webTestClient; + + + @Before + public void setUp() throws Exception { + + this.webTestClient = WebTestClient.bindToController(new TestController()) + .apply(globalIdentity("Pablo")) + .build(); + } + + @Test + public void useGloballyConfiguredIdentity() throws Exception { + this.webTestClient.get().uri("/userIdentity") + .exchange() + .expectStatus().isOk() + .expectBody(String.class).isEqualTo("Hello Pablo!"); + } + + @Test + public void useLocallyConfiguredIdentity() throws Exception { + + withIdentity(this.webTestClient, "Giovanni") + .get().uri("/userIdentity") + .exchange() + .expectStatus().isOk() + .expectBody(String.class).isEqualTo("Hello Giovanni!"); + } + + + private static MockServerConfigurer globalIdentity(String userName) { + return new IdentityConfigurer(userName); + } + + private static WebTestClient withIdentity(WebTestClient client, String userName) { + return client.mutate().apply(new IdentityConfigurer(userName)).build(); + } + + + @RestController + static class TestController { + + @GetMapping("/userIdentity") + public String handle(Principal principal) { + return "Hello " + principal.getName() + "!"; + } + } + + private static class TestUser implements Principal { + + private final String name; + + TestUser(String name) { + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + } + + private static class IdentityConfigurer implements MockServerConfigurer, WebTestClientConfigurer { + + private final IdentityFilter filter; + + + public IdentityConfigurer(String userName) { + this.filter = new IdentityFilter(userName); + } + + @Override + public void beforeServerCreated(WebHttpHandlerBuilder builder) { + builder.filters(filters -> filters.add(0, this.filter)); + } + + @Override + public void afterConfigurerAdded(WebTestClient.Builder builder, + @Nullable WebHttpHandlerBuilder httpHandlerBuilder, + @Nullable ClientHttpConnector connector) { + + Assert.notNull(httpHandlerBuilder, "Not a mock server"); + httpHandlerBuilder.filters(filters -> { + filters.removeIf(filter -> filter instanceof IdentityFilter); + filters.add(0, this.filter); + }); + } + } + + private static class IdentityFilter implements WebFilter { + + private final Mono userMono; + + + IdentityFilter(String userName) { + this.userMono = Mono.just(new TestUser(userName)); + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + exchange = exchange.mutate().principal(this.userMono).build(); + return chain.filter(exchange); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java deleted file mode 100644 index 1aeb38cbf9..0000000000 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2002-2017 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.test.web.reactive.server.samples; - -import java.security.Principal; -import java.util.function.UnaryOperator; - -import org.junit.Before; -import org.junit.Test; -import reactor.core.publisher.Mono; - -import org.springframework.test.web.reactive.server.ExchangeMutatorWebFilter; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; - -/** - * Samples tests that demonstrate applying ServerWebExchange initialization. - * @author Rossen Stoyanchev - */ -public class ExchangeMutatorWebFilterTests { - - private ExchangeMutatorWebFilter exchangeMutator; - - private WebTestClient webTestClient; - - - @Before - public void setUp() throws Exception { - - this.exchangeMutator = new ExchangeMutatorWebFilter(userIdentity("Pablo")); - - this.webTestClient = WebTestClient.bindToController(new TestController()) - .webFilter(this.exchangeMutator) - .build(); - } - - @Test - public void globalMutator() throws Exception { - this.webTestClient.get().uri("/userIdentity") - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello Pablo!"); - } - - @Test - public void perRequestMutators() throws Exception { - - this.webTestClient = WebTestClient.bindToController(new TestController()) - .webFilter(this.exchangeMutator) - .configureClient() - .filter(this.exchangeMutator.perClient(userIdentity("Giovanni"))) - .build(); - - this.webTestClient - .get().uri("/userIdentity") - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello Giovanni!"); - } - - - private UnaryOperator userIdentity(String userName) { - return exchange -> exchange.mutate().principal(Mono.just(new TestUser(userName))).build(); - } - - - @RestController - static class TestController { - - @GetMapping("/userIdentity") - public String handle(Principal principal) { - return "Hello " + principal.getName() + "!"; - } - } - - - private static class TestUser implements Principal { - - private final String name; - - TestUser(String name) { - this.name = name; - } - - @Override - public String getName() { - return this.name; - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java b/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java index 5b1c680528..43217c8ddf 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java @@ -96,6 +96,19 @@ public class WebHttpHandlerBuilder { this.webHandler = webHandler; } + /** + * Copy constructor. + */ + private WebHttpHandlerBuilder(WebHttpHandlerBuilder other) { + + this.webHandler = other.webHandler; + this.filters.addAll(other.filters); + this.exceptionHandlers.addAll(other.exceptionHandlers); + this.sessionManager = other.sessionManager; + this.codecConfigurer = other.codecConfigurer; + this.localeContextResolver = other.localeContextResolver; + } + /** * Static factory method to create a new builder instance. @@ -263,6 +276,14 @@ public class WebHttpHandlerBuilder { return adapted; } + /** + * Clone this {@link WebHttpHandlerBuilder}. + * @return the cloned builder instance + */ + public WebHttpHandlerBuilder cloneBuilder() { + return new WebHttpHandlerBuilder(this); + } + private static class SortedBeanContainer { diff --git a/spring-web/src/main/java/org/springframework/web/server/handler/ExceptionHandlingWebHandler.java b/spring-web/src/main/java/org/springframework/web/server/handler/ExceptionHandlingWebHandler.java index 4d0e976a36..01dd84d5bb 100644 --- a/spring-web/src/main/java/org/springframework/web/server/handler/ExceptionHandlingWebHandler.java +++ b/spring-web/src/main/java/org/springframework/web/server/handler/ExceptionHandlingWebHandler.java @@ -16,6 +16,7 @@ package org.springframework.web.server.handler; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -40,7 +41,7 @@ public class ExceptionHandlingWebHandler extends WebHandlerDecorator { public ExceptionHandlingWebHandler(WebHandler delegate, List handlers) { super(delegate); - this.exceptionHandlers = Collections.unmodifiableList(handlers); + this.exceptionHandlers = Collections.unmodifiableList(new ArrayList<>(handlers)); }