Add request binding to functional endpoints
This commit introduces form binding to ServerRequest in both WebMVC.fn and WebFlux.fn's, in the form of a bind(Class) method. Closes gh-25943
This commit is contained in:
parent
a3e37597aa
commit
d04d7b2e57
|
@ -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");
|
* 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.
|
||||||
|
@ -29,6 +29,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -48,6 +49,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.reactive.function.BodyExtractor;
|
import org.springframework.web.reactive.function.BodyExtractor;
|
||||||
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
|
@ -219,6 +221,20 @@ public final class MockServerRequest implements ServerRequest {
|
||||||
return (Flux<S>) this.body;
|
return (Flux<S>) this.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> Mono<T> bind(Class<T> bindType) {
|
||||||
|
Assert.state(this.body != null, "No body");
|
||||||
|
return (Mono<T>) this.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> Mono<T> bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) {
|
||||||
|
Assert.state(this.body != null, "No body");
|
||||||
|
return (Mono<T>) this.body;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> attributes() {
|
public Map<String, Object> attributes() {
|
||||||
return this.attributes;
|
return this.attributes;
|
||||||
|
|
|
@ -27,12 +27,14 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.core.codec.DecodingException;
|
import org.springframework.core.codec.DecodingException;
|
||||||
import org.springframework.core.codec.Hints;
|
import org.springframework.core.codec.Hints;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
@ -49,7 +51,12 @@ import org.springframework.http.server.RequestPath;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||||
import org.springframework.web.reactive.function.BodyExtractor;
|
import org.springframework.web.reactive.function.BodyExtractor;
|
||||||
import org.springframework.web.reactive.function.BodyExtractors;
|
import org.springframework.web.reactive.function.BodyExtractors;
|
||||||
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
|
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
|
||||||
|
@ -220,6 +227,33 @@ class DefaultServerRequest implements ServerRequest {
|
||||||
.onErrorMap(DecodingException.class, DECODING_MAPPER);
|
.onErrorMap(DecodingException.class, DECODING_MAPPER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> Mono<T> bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) {
|
||||||
|
Assert.notNull(bindType, "BindType must not be null");
|
||||||
|
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");
|
||||||
|
|
||||||
|
return Mono.defer(() -> {
|
||||||
|
WebExchangeDataBinder dataBinder = new WebExchangeDataBinder(null);
|
||||||
|
dataBinder.setTargetType(ResolvableType.forClass(bindType));
|
||||||
|
dataBinderCustomizer.accept(dataBinder);
|
||||||
|
|
||||||
|
ServerWebExchange exchange = exchange();
|
||||||
|
return dataBinder.construct(exchange)
|
||||||
|
.then(dataBinder.bind(exchange))
|
||||||
|
.then(Mono.defer(() -> {
|
||||||
|
BindingResult bindingResult = dataBinder.getBindingResult();
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
return Mono.error(new BindException(bindingResult));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
T result = (T) bindingResult.getTarget();
|
||||||
|
return Mono.justOrEmpty(result);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> attributes() {
|
public Map<String, Object> attributes() {
|
||||||
return this.exchange.getAttributes();
|
return this.exchange.getAttributes();
|
||||||
|
|
|
@ -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");
|
* 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.
|
||||||
|
@ -29,6 +29,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.cors.reactive.CorsUtils;
|
import org.springframework.web.cors.reactive.CorsUtils;
|
||||||
import org.springframework.web.reactive.function.BodyExtractor;
|
import org.springframework.web.reactive.function.BodyExtractor;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
@ -1044,6 +1046,16 @@ public abstract class RequestPredicates {
|
||||||
return this.request.bodyToFlux(typeReference);
|
return this.request.bodyToFlux(typeReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> bind(Class<T> bindType) {
|
||||||
|
return this.request.bind(bindType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) {
|
||||||
|
return this.request.bind(bindType, dataBinderCustomizer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> attributes() {
|
public Map<String, Object> attributes() {
|
||||||
return this.attributes;
|
return this.attributes;
|
||||||
|
|
|
@ -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");
|
* 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.
|
||||||
|
@ -48,6 +48,8 @@ import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.reactive.function.BodyExtractor;
|
import org.springframework.web.reactive.function.BodyExtractor;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebSession;
|
import org.springframework.web.server.WebSession;
|
||||||
|
@ -197,6 +199,31 @@ public interface ServerRequest {
|
||||||
*/
|
*/
|
||||||
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference);
|
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind to this request and return an instance of the given type.
|
||||||
|
* @param bindType the type of class to bind this request to
|
||||||
|
* @param <T> the type to bind to
|
||||||
|
* @return a mono containing either a constructed and bound instance of
|
||||||
|
* {@code bindType}, or a {@link BindException} in case of binding errors
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
default <T> Mono<T> bind(Class<T> bindType) {
|
||||||
|
return bind(bindType, dataBinder -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind to this request and return an instance of the given type.
|
||||||
|
* @param bindType the type of class to bind this request to
|
||||||
|
* @param dataBinderCustomizer used to customize the data binder, e.g. set
|
||||||
|
* (dis)allowed fields
|
||||||
|
* @param <T> the type to bind to
|
||||||
|
* @return a mono containing either a constructed and bound instance of
|
||||||
|
* {@code bindType}, or a {@link BindException} in case of binding errors
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
<T> Mono<T> bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the request attribute value if present.
|
* Get the request attribute value if present.
|
||||||
* @param name the attribute name
|
* @param name the attribute name
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2023 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.
|
||||||
|
@ -25,6 +25,7 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -42,6 +43,7 @@ import org.springframework.http.server.RequestPath;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.reactive.function.BodyExtractor;
|
import org.springframework.web.reactive.function.BodyExtractor;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
@ -171,6 +173,16 @@ public class ServerRequestWrapper implements ServerRequest {
|
||||||
return this.delegate.bodyToFlux(typeReference);
|
return this.delegate.bodyToFlux(typeReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> bind(Class<T> bindType) {
|
||||||
|
return this.delegate.bind(bindType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) {
|
||||||
|
return this.delegate.bind(bindType, dataBinderCustomizer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Object> attribute(String name) {
|
public Optional<Object> attribute(String name) {
|
||||||
return this.delegate.attribute(name);
|
return this.delegate.attribute(name);
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://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.web.reactive.function.server;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
|
import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer;
|
||||||
|
|
||||||
|
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Arjen Poutsma
|
||||||
|
*/
|
||||||
|
class BindingFunctionIntegrationTests extends AbstractRouterFunctionIntegrationTests {
|
||||||
|
|
||||||
|
private WebClient webClient;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startServer(HttpServer httpServer) throws Exception {
|
||||||
|
super.startServer(httpServer);
|
||||||
|
this.webClient = WebClient.create("http://localhost:" + this.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RouterFunction<?> routerFunction() {
|
||||||
|
return route()
|
||||||
|
.GET("/constructor", request -> ServerResponse.ok().body(
|
||||||
|
request.bind(ConstructorInjection.class).map(Objects::toString), String.class))
|
||||||
|
.GET("/property", request -> ServerResponse.ok().body(
|
||||||
|
request.bind(PropertyInjection.class).map(Objects::toString), String.class))
|
||||||
|
.GET("/mixed", request -> ServerResponse.ok().body(
|
||||||
|
request.bind(MixedInjection.class).map(Objects::toString), String.class))
|
||||||
|
.GET("/customize", request -> ServerResponse.ok().body(
|
||||||
|
request.bind(PropertyInjection.class, dataBinder -> dataBinder.setAllowedFields("foo")).map(Objects::toString), String.class))
|
||||||
|
.GET("/error", request -> ServerResponse.ok().body(
|
||||||
|
request.bind(ErrorInjection.class).map(Objects::toString), String.class))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedHttpServerTest
|
||||||
|
void bindToConstructor(HttpServer httpServer) throws Exception {
|
||||||
|
|
||||||
|
startServer(httpServer);
|
||||||
|
|
||||||
|
Mono<String> result = this.webClient.get()
|
||||||
|
.uri("/constructor?foo=FOO&bar=BAR")
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("FOO:BAR")
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedHttpServerTest
|
||||||
|
void bindToProperties(HttpServer httpServer) throws Exception {
|
||||||
|
|
||||||
|
startServer(httpServer);
|
||||||
|
|
||||||
|
Mono<String> result = this.webClient.get()
|
||||||
|
.uri("/property?foo=FOO&bar=BAR")
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("FOO:BAR")
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedHttpServerTest
|
||||||
|
void bindToMixed(HttpServer httpServer) throws Exception {
|
||||||
|
|
||||||
|
startServer(httpServer);
|
||||||
|
|
||||||
|
Mono<String> result = this.webClient.get()
|
||||||
|
.uri("/mixed?foo=FOO&bar=BAR")
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("FOO:BAR")
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedHttpServerTest
|
||||||
|
void bindCustomizer(HttpServer httpServer) throws Exception {
|
||||||
|
|
||||||
|
startServer(httpServer);
|
||||||
|
|
||||||
|
Mono<String> result = this.webClient.get()
|
||||||
|
.uri("/customize?foo=FOO&bar=BAR")
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("FOO:null")
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedHttpServerTest
|
||||||
|
void bindError(HttpServer httpServer) throws Exception {
|
||||||
|
|
||||||
|
startServer(httpServer);
|
||||||
|
|
||||||
|
Mono<String> result = this.webClient.get()
|
||||||
|
.uri("/error?foo=FOO")
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectError(WebClientResponseException.InternalServerError.class)
|
||||||
|
.verify(Duration.ofSeconds(5L));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final class ConstructorInjection {
|
||||||
|
private final String foo;
|
||||||
|
|
||||||
|
private final String bar;
|
||||||
|
|
||||||
|
public ConstructorInjection(String foo, String bar) {
|
||||||
|
this.foo = foo;
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBar() {
|
||||||
|
return this.bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.foo + ":" + this.bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PropertyInjection {
|
||||||
|
@Nullable
|
||||||
|
private String foo;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String bar;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFoo(String foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getBar() {
|
||||||
|
return this.bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBar(String bar) {
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.valueOf(this.foo) + ":" + String.valueOf(this.bar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MixedInjection {
|
||||||
|
private final String foo;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String bar;
|
||||||
|
|
||||||
|
public MixedInjection(String foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getBar() {
|
||||||
|
return this.bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBar(String bar) {
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return this.foo + ":" + String.valueOf(this.bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ErrorInjection {
|
||||||
|
|
||||||
|
private int foo;
|
||||||
|
|
||||||
|
public int getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFoo(int foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
* 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.
|
||||||
|
@ -36,6 +36,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
@ -47,6 +48,7 @@ import jakarta.servlet.http.HttpSession;
|
||||||
import jakarta.servlet.http.Part;
|
import jakarta.servlet.http.Part;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpRange;
|
import org.springframework.http.HttpRange;
|
||||||
|
@ -56,12 +58,17 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.server.RequestPath;
|
import org.springframework.http.server.RequestPath;
|
||||||
import org.springframework.http.server.ServletServerHttpRequest;
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||||
|
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.context.request.ServletWebRequest;
|
import org.springframework.web.context.request.ServletWebRequest;
|
||||||
import org.springframework.web.context.request.WebRequest;
|
import org.springframework.web.context.request.WebRequest;
|
||||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||||
|
@ -218,6 +225,35 @@ class DefaultServerRequest implements ServerRequest {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
|
||||||
|
Assert.notNull(bindType, "BindType must not be null");
|
||||||
|
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");
|
||||||
|
|
||||||
|
ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(null);
|
||||||
|
dataBinder.setTargetType(ResolvableType.forClass(bindType));
|
||||||
|
dataBinderCustomizer.accept(dataBinder);
|
||||||
|
|
||||||
|
HttpServletRequest servletRequest = servletRequest();
|
||||||
|
dataBinder.construct(servletRequest);
|
||||||
|
dataBinder.bind(servletRequest);
|
||||||
|
|
||||||
|
BindingResult bindingResult = dataBinder.getBindingResult();
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
throw new BindException(bindingResult);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
T result = (T) bindingResult.getTarget();
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalStateException("Binding result has neither target nor errors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Object> attribute(String name) {
|
public Optional<Object> attribute(String name) {
|
||||||
return Optional.ofNullable(servletRequest().getAttribute(name));
|
return Optional.ofNullable(servletRequest().getAttribute(name));
|
||||||
|
|
|
@ -42,6 +42,7 @@ import jakarta.servlet.http.HttpSession;
|
||||||
import jakarta.servlet.http.Part;
|
import jakarta.servlet.http.Part;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpInputMessage;
|
import org.springframework.http.HttpInputMessage;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
@ -52,7 +53,11 @@ import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||||
|
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.util.UriBuilder;
|
import org.springframework.web.util.UriBuilder;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
@ -323,6 +328,35 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder {
|
||||||
throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList(), method());
|
throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList(), method());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
|
||||||
|
Assert.notNull(bindType, "BindType must not be null");
|
||||||
|
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");
|
||||||
|
|
||||||
|
ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(null);
|
||||||
|
dataBinder.setTargetType(ResolvableType.forClass(bindType));
|
||||||
|
dataBinderCustomizer.accept(dataBinder);
|
||||||
|
|
||||||
|
HttpServletRequest servletRequest = servletRequest();
|
||||||
|
dataBinder.construct(servletRequest);
|
||||||
|
dataBinder.bind(servletRequest);
|
||||||
|
|
||||||
|
BindingResult bindingResult = dataBinder.getBindingResult();
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
throw new BindException(bindingResult);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
T result = (T) bindingResult.getTarget();
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalStateException("Binding result has neither target nor errors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> attributes() {
|
public Map<String, Object> attributes() {
|
||||||
return this.attributes;
|
return this.attributes;
|
||||||
|
|
|
@ -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");
|
* 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.
|
||||||
|
@ -31,6 +31,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@ -54,6 +55,8 @@ import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.cors.CorsUtils;
|
import org.springframework.web.cors.CorsUtils;
|
||||||
import org.springframework.web.util.UriBuilder;
|
import org.springframework.web.util.UriBuilder;
|
||||||
import org.springframework.web.util.UriUtils;
|
import org.springframework.web.util.UriUtils;
|
||||||
|
@ -1018,6 +1021,16 @@ public abstract class RequestPredicates {
|
||||||
return this.request.body(bodyType);
|
return this.request.body(bodyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T bind(Class<T> bindType) throws BindException {
|
||||||
|
return this.request.bind(bindType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
|
||||||
|
return this.request.bind(bindType, dataBinderCustomizer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Object> attribute(String name) {
|
public Optional<Object> attribute(String name) {
|
||||||
return this.request.attribute(name);
|
return this.request.attribute(name);
|
||||||
|
|
|
@ -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");
|
* 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.
|
||||||
|
@ -48,6 +48,8 @@ import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.util.ServletRequestPathUtils;
|
import org.springframework.web.util.ServletRequestPathUtils;
|
||||||
import org.springframework.web.util.UriBuilder;
|
import org.springframework.web.util.UriBuilder;
|
||||||
|
|
||||||
|
@ -148,6 +150,30 @@ public interface ServerRequest {
|
||||||
*/
|
*/
|
||||||
<T> T body(ParameterizedTypeReference<T> bodyType) throws ServletException, IOException;
|
<T> T body(ParameterizedTypeReference<T> bodyType) throws ServletException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind to this request and return an instance of the given type.
|
||||||
|
* @param bindType the type of class to bind this request to
|
||||||
|
* @param <T> the type to bind to
|
||||||
|
* @return a constructed and bound instance of {@code bindType}
|
||||||
|
* @throws BindException in case of binding errors
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
default <T> T bind(Class<T> bindType) throws BindException {
|
||||||
|
return bind(bindType, dataBinder -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind to this request and return an instance of the given type.
|
||||||
|
* @param bindType the type of class to bind this request to
|
||||||
|
* @param dataBinderCustomizer used to customize the data binder, e.g. set
|
||||||
|
* (dis)allowed fields
|
||||||
|
* @param <T> the type to bind to
|
||||||
|
* @return a constructed and bound instance of {@code bindType}
|
||||||
|
* @throws BindException in case of binding errors
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
<T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the request attribute value if present.
|
* Get the request attribute value if present.
|
||||||
* @param name the attribute name
|
* @param name the attribute name
|
||||||
|
|
|
@ -49,6 +49,7 @@ import org.springframework.http.converter.StringHttpMessageConverter;
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||||
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
|
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
|
||||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||||
|
@ -304,6 +305,71 @@ class DefaultServerRequestTests {
|
||||||
assertThat(request.principal()).contains(principal);
|
assertThat(request.principal()).contains(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindToConstructor() throws BindException {
|
||||||
|
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
|
||||||
|
servletRequest.addParameter("foo", "FOO");
|
||||||
|
servletRequest.addParameter("bar", "BAR");
|
||||||
|
|
||||||
|
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||||
|
|
||||||
|
ConstructorInjection result = request.bind(ConstructorInjection.class);
|
||||||
|
assertThat(result.getFoo()).isEqualTo("FOO");
|
||||||
|
assertThat(result.getBar()).isEqualTo("BAR");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindToProperties() throws BindException {
|
||||||
|
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
|
||||||
|
servletRequest.addParameter("foo", "FOO");
|
||||||
|
servletRequest.addParameter("bar", "BAR");
|
||||||
|
|
||||||
|
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||||
|
|
||||||
|
PropertyInjection result = request.bind(PropertyInjection.class);
|
||||||
|
assertThat(result.getFoo()).isEqualTo("FOO");
|
||||||
|
assertThat(result.getBar()).isEqualTo("BAR");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindToMixed() throws BindException {
|
||||||
|
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
|
||||||
|
servletRequest.addParameter("foo", "FOO");
|
||||||
|
servletRequest.addParameter("bar", "BAR");
|
||||||
|
|
||||||
|
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||||
|
|
||||||
|
MixedInjection result = request.bind(MixedInjection.class);
|
||||||
|
assertThat(result.getFoo()).isEqualTo("FOO");
|
||||||
|
assertThat(result.getBar()).isEqualTo("BAR");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindCustomizer() throws BindException {
|
||||||
|
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
|
||||||
|
servletRequest.addParameter("foo", "FOO");
|
||||||
|
servletRequest.addParameter("bar", "BAR");
|
||||||
|
|
||||||
|
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||||
|
|
||||||
|
PropertyInjection result = request.bind(PropertyInjection.class, dataBinder -> dataBinder.setAllowedFields("foo"));
|
||||||
|
assertThat(result.getFoo()).isEqualTo("FOO");
|
||||||
|
assertThat(result.getBar()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindError() throws BindException {
|
||||||
|
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
|
||||||
|
servletRequest.addParameter("foo", "FOO");
|
||||||
|
|
||||||
|
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||||
|
|
||||||
|
assertThatExceptionOfType(BindException.class).isThrownBy(() ->
|
||||||
|
request.bind(ErrorInjection.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ParameterizedHttpMethodTest
|
@ParameterizedHttpMethodTest
|
||||||
void checkNotModifiedTimestamp(String method) throws Exception {
|
void checkNotModifiedTimestamp(String method) throws Exception {
|
||||||
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
|
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
|
||||||
|
@ -487,4 +553,83 @@ class DefaultServerRequestTests {
|
||||||
@interface ParameterizedHttpMethodTest {
|
@interface ParameterizedHttpMethodTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ConstructorInjection {
|
||||||
|
private final String foo;
|
||||||
|
|
||||||
|
private final String bar;
|
||||||
|
|
||||||
|
public ConstructorInjection(String foo, String bar) {
|
||||||
|
this.foo = foo;
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBar() {
|
||||||
|
return this.bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PropertyInjection {
|
||||||
|
private String foo;
|
||||||
|
|
||||||
|
private String bar;
|
||||||
|
|
||||||
|
public String getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFoo(String foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBar() {
|
||||||
|
return this.bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBar(String bar) {
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MixedInjection {
|
||||||
|
private final String foo;
|
||||||
|
|
||||||
|
private String bar;
|
||||||
|
|
||||||
|
public MixedInjection(String foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBar() {
|
||||||
|
return this.bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBar(String bar) {
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ErrorInjection {
|
||||||
|
|
||||||
|
private int foo;
|
||||||
|
|
||||||
|
public int getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFoo(int foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue