Support for populating model attributes through data class constructors

Includes a new overloaded ModelAndView constructor with an HttpStatus argument, as well as a HandlerMethodArgumentResolverSupport refactoring (revised checkParameterType signature, actually implementing the HandlerMethodArgumentResolver interface).

Issue: SPR-15199
This commit is contained in:
Juergen Hoeller
2017-03-24 12:15:45 +01:00
parent b3154357f0
commit 65ba865d70
30 changed files with 411 additions and 274 deletions

View File

@@ -44,7 +44,7 @@ public interface HandlerMethodArgumentResolver {
* @param exchange the current exchange
* @return {@code Mono} for the argument value, possibly empty
*/
Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange);
Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange);
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method;
import java.lang.annotation.Annotation;
@@ -25,14 +26,14 @@ import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.util.Assert;
/**
* Base class for {@link HandlerMethodArgumentResolver} implementations with
* access to a {@code ReactiveAdapterRegistry} and methods to check for
* method parameter support.
* Base class for {@link HandlerMethodArgumentResolver} implementations with access to a
* {@code ReactiveAdapterRegistry} and methods to check for method parameter support.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
*/
public abstract class HandlerMethodArgumentResolverSupport {
public abstract class HandlerMethodArgumentResolverSupport implements HandlerMethodArgumentResolver {
private final ReactiveAdapterRegistry adapterRegistry;
@@ -55,12 +56,12 @@ public abstract class HandlerMethodArgumentResolverSupport {
* Evaluate the {@code Predicate} on the the method parameter type or on
* the generic type within a reactive type wrapper.
*/
protected boolean checkParamType(MethodParameter param, Predicate<Class<?>> predicate) {
Class<?> type = param.getParameterType();
protected boolean checkParameterType(MethodParameter parameter, Predicate<Class<?>> predicate) {
Class<?> type = parameter.getParameterType();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type);
if (adapter != null) {
assertHasValues(adapter, param);
type = param.nested().getNestedParameterType();
assertHasValues(adapter, parameter);
type = parameter.nested().getNestedParameterType();
}
return predicate.test(type);
}
@@ -77,25 +78,25 @@ public abstract class HandlerMethodArgumentResolverSupport {
* {@code IllegalStateException} if the same matches the generic type
* within a reactive type wrapper.
*/
protected boolean checkParamTypeNoReactiveWrapper(MethodParameter param, Predicate<Class<?>> predicate) {
Class<?> type = param.getParameterType();
protected boolean checkParameterTypeNoReactiveWrapper(MethodParameter parameter, Predicate<Class<?>> predicate) {
Class<?> type = parameter.getParameterType();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type);
if (adapter != null) {
assertHasValues(adapter, param);
type = param.nested().getNestedParameterType();
assertHasValues(adapter, parameter);
type = parameter.nested().getNestedParameterType();
}
if (predicate.test(type)) {
if (adapter == null) {
return true;
}
throw getReactiveWrapperError(param);
throw buildReactiveWrapperException(parameter);
}
return false;
}
private IllegalStateException getReactiveWrapperError(MethodParameter param) {
private IllegalStateException buildReactiveWrapperException(MethodParameter parameter) {
return new IllegalStateException(getClass().getSimpleName() +
" doesn't support reactive type wrapper: " + param.getGenericParameterType());
" doesn't support reactive type wrapper: " + parameter.getGenericParameterType());
}
/**
@@ -105,29 +106,28 @@ public abstract class HandlerMethodArgumentResolverSupport {
* type within a reactive type wrapper.
*/
protected <A extends Annotation> boolean checkAnnotatedParamNoReactiveWrapper(
MethodParameter param, Class<A> annotationType,
BiPredicate<A, Class<?>> typePredicate) {
MethodParameter parameter, Class<A> annotationType, BiPredicate<A, Class<?>> typePredicate) {
A annotation = param.getParameterAnnotation(annotationType);
A annotation = parameter.getParameterAnnotation(annotationType);
if (annotation == null) {
return false;
}
param = param.nestedIfOptional();
Class<?> type = param.getNestedParameterType();
parameter = parameter.nestedIfOptional();
Class<?> type = parameter.getNestedParameterType();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type);
if (adapter != null) {
assertHasValues(adapter, param);
param = param.nested();
type = param.getNestedParameterType();
assertHasValues(adapter, parameter);
parameter = parameter.nested();
type = parameter.getNestedParameterType();
}
if (typePredicate.test(annotation, type)) {
if (adapter == null) {
return true;
}
throw getReactiveWrapperError(param);
throw buildReactiveWrapperException(parameter);
}
return false;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -33,15 +33,14 @@ import org.springframework.web.server.ServerWebExchange;
*/
public interface SyncHandlerMethodArgumentResolver extends HandlerMethodArgumentResolver {
/**
* {@inheritDoc}
* <p>By default this simply delegates to {@link #resolveArgumentValue} for
* synchronous resolution.
*/
@Override
default Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
default Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
return Mono.justOrEmpty(resolveArgumentValue(parameter, bindingContext, exchange));
}
@@ -53,7 +52,7 @@ public interface SyncHandlerMethodArgumentResolver extends HandlerMethodArgument
* @param exchange the current exchange
* @return an {@code Optional} with the resolved value, possibly empty
*/
Optional<Object> resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange);
Optional<Object> resolveArgumentValue(
MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -114,7 +114,6 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
}
for (ServerHttpMessageReader<?> reader : getMessageReaders()) {
if (reader.canRead(elementType, mediaType)) {
Map<String, Object> readHints = Collections.emptyMap();
if (adapter != null && adapter.isMultiValue()) {
@@ -171,9 +170,9 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
private Object[] extractValidationHints(MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validAnnot != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validAnnot != null ? validAnnot.value() : AnnotationUtils.getValue(ann));
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
}
}

View File

@@ -32,7 +32,6 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport;
import org.springframework.web.server.ServerErrorException;
import org.springframework.web.server.ServerWebExchange;
@@ -58,8 +57,7 @@ import org.springframework.web.server.ServerWebInputException;
* @author Rossen Stoyanchev
* @since 5.0
*/
public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport
implements HandlerMethodArgumentResolver {
public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport {
private final ConfigurableBeanFactory configurableBeanFactory;
@@ -69,23 +67,21 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
/**
* @param beanFactory a bean factory to use for resolving ${...} placeholder
* @param factory a bean factory to use for resolving ${...} placeholder
* and #{...} SpEL expressions in default values, or {@code null} if default
* values are not expected to contain expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
*/
public AbstractNamedValueArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
this.configurableBeanFactory = beanFactory;
this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null);
public AbstractNamedValueArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(registry);
this.configurableBeanFactory = factory;
this.expressionContext = (factory != null ? new BeanExpressionContext(factory, null) : null);
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
public Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
@@ -175,8 +171,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
* @param exchange the current exchange
* @return the resolved argument (may be {@code null})
*/
protected abstract Mono<Object> resolveName(String name, MethodParameter parameter,
ServerWebExchange exchange);
protected abstract Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange);
/**
* Apply type conversion if necessary.
@@ -277,8 +272,8 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
* @param exchange the current exchange
*/
@SuppressWarnings("UnusedParameters")
protected void handleResolvedValue(Object arg, String name, MethodParameter parameter,
Model model, ServerWebExchange exchange) {
protected void handleResolvedValue(
Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -39,23 +39,20 @@ import org.springframework.web.server.ServerWebExchange;
public abstract class AbstractNamedValueSyncArgumentResolver extends AbstractNamedValueArgumentResolver
implements SyncHandlerMethodArgumentResolver {
/**
* @param beanFactory a bean factory to use for resolving ${...}
* @param factory a bean factory to use for resolving ${...}
* placeholder and #{...} SpEL expressions in default values;
* or {@code null} if default values are not expected to have expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
*/
protected AbstractNamedValueSyncArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(beanFactory, adapterRegistry);
protected AbstractNamedValueSyncArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(factory, registry);
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
public Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
// Flip the default implementation from SyncHandlerMethodArgumentResolver:
// instead of delegating to (sync) resolveArgumentValue,
@@ -66,8 +63,8 @@ public abstract class AbstractNamedValueSyncArgumentResolver extends AbstractNam
}
@Override
public Optional<Object> resolveArgumentValue(MethodParameter parameter,
BindingContext context, ServerWebExchange exchange) {
public Optional<Object> resolveArgumentValue(
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
// This won't block since resolveName below doesn't
Object value = resolveArgument(parameter, context, exchange).block();
@@ -76,16 +73,13 @@ public abstract class AbstractNamedValueSyncArgumentResolver extends AbstractNam
}
@Override
protected final Mono<Object> resolveName(String name, MethodParameter param,
ServerWebExchange exchange) {
protected final Mono<Object> resolveName(String name, MethodParameter param, ServerWebExchange exchange) {
return Mono.justOrEmpty(resolveNamedValue(name, param, exchange));
}
/**
* Actually resolve the value synchronously.
*/
protected abstract Optional<Object> resolveNamedValue(String name,
MethodParameter param, ServerWebExchange exchange);
protected abstract Optional<Object> resolveNamedValue(String name, MethodParameter param, ServerWebExchange exchange);
}

View File

@@ -38,17 +38,14 @@ import org.springframework.web.server.ServerWebInputException;
*/
public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
/**
* @param beanFactory a bean factory to use for resolving ${...}
* @param factory a bean factory to use for resolving ${...}
* placeholder and #{...} SpEL expressions in default values;
* or {@code null} if default values are not expected to contain expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
*/
public CookieValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(beanFactory, adapterRegistry);
public CookieValueMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(factory, registry);
}
@@ -64,9 +61,7 @@ public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArg
}
@Override
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) {
HttpCookie cookie = exchange.getRequest().getCookies().getFirst(name);
Class<?> paramType = parameter.getNestedParameterType();
if (HttpCookie.class.isAssignableFrom(paramType)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -41,9 +41,7 @@ import org.springframework.web.server.ServerWebExchange;
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverSupport
implements HandlerMethodArgumentResolver {
public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverSupport {
public ErrorsMethodArgumentResolver(ReactiveAdapterRegistry registry) {
super(registry);
@@ -52,13 +50,13 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS
@Override
public boolean supportsParameter(MethodParameter parameter) {
return checkParamTypeNoReactiveWrapper(parameter, Errors.class::isAssignableFrom);
return checkParameterTypeNoReactiveWrapper(parameter, Errors.class::isAssignableFrom);
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
public Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
String name = getModelAttributeName(parameter);
Object errors = context.getModel().asMap().get(BindingResult.MODEL_KEY_PREFIX + name);
@@ -79,9 +77,8 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS
}
private String getModelAttributeName(MethodParameter parameter) {
Assert.isTrue(parameter.getParameterIndex() > 0,
"Errors argument must be immediately after a model attribute argument.");
"Errors argument must be immediately after a model attribute argument");
int index = parameter.getParameterIndex() - 1;
MethodParameter attributeParam = new MethodParameter(parameter.getMethod(), index);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -36,17 +36,14 @@ import org.springframework.web.server.ServerWebExchange;
*/
public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
/**
* @param beanFactory a bean factory to use for resolving ${...}
* @param factory a bean factory to use for resolving ${...}
* placeholder and #{...} SpEL expressions in default values;
* or {@code null} if default values are not expected to contain expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
*/
public ExpressionValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(beanFactory, adapterRegistry);
public ExpressionValueMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(factory, registry);
}
@@ -62,9 +59,7 @@ public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueSyn
}
@Override
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) {
// No name to resolve
return Optional.empty();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -38,9 +38,7 @@ import org.springframework.web.server.ServerWebExchange;
* @author Rossen Stoyanchev
* @since 5.0
*/
public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentResolver
implements HandlerMethodArgumentResolver {
public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentResolver {
public HttpEntityArgumentResolver(List<ServerHttpMessageReader<?>> readers,
ReactiveAdapterRegistry registry) {
@@ -51,16 +49,15 @@ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentRes
@Override
public boolean supportsParameter(MethodParameter parameter) {
return checkParamTypeNoReactiveWrapper(parameter,
return checkParameterTypeNoReactiveWrapper(parameter,
type -> HttpEntity.class.equals(type) || RequestEntity.class.equals(type));
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
public Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
Class<?> entityType = parameter.getParameterType();
return readBody(parameter.nested(), false, bindingContext, exchange)
.map(body -> createEntity(body, entityType, exchange.getRequest()))
.defaultIfEmpty(createEntity(null, entityType, exchange.getRequest()));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -36,7 +36,6 @@ import org.springframework.web.server.ServerWebExchange;
public class ModelArgumentResolver extends HandlerMethodArgumentResolverSupport
implements SyncHandlerMethodArgumentResolver {
public ModelArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
}
@@ -44,7 +43,7 @@ public class ModelArgumentResolver extends HandlerMethodArgumentResolverSupport
@Override
public boolean supportsParameter(MethodParameter parameter) {
return checkParamTypeNoReactiveWrapper(parameter, Model.class::isAssignableFrom);
return checkParameterTypeNoReactiveWrapper(parameter, Model.class::isAssignableFrom);
}
@Override

View File

@@ -16,30 +16,34 @@
package org.springframework.web.reactive.result.method.annotation;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import org.springframework.beans.BeanUtils;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport;
import org.springframework.web.server.ServerWebExchange;
@@ -58,10 +62,12 @@ import org.springframework.web.server.ServerWebExchange;
* attribute with or without the presence of an {@code @ModelAttribute}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
*/
public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentResolverSupport
implements HandlerMethodArgumentResolver {
public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentResolverSupport {
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private final boolean useDefaultResolution;
@@ -87,25 +93,25 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
return true;
}
else if (this.useDefaultResolution) {
return checkParamType(parameter, type -> !BeanUtils.isSimpleProperty(type));
return checkParameterType(parameter, type -> !BeanUtils.isSimpleProperty(type));
}
return false;
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
public Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
ResolvableType type = ResolvableType.forMethodParameter(parameter);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type.resolve());
ResolvableType valueType = (adapter != null ? type.getGeneric(0) : type);
Assert.state(adapter == null || !adapter.isMultiValue(),
getClass().getSimpleName() + " doesn't support multi-value reactive type wrapper: " +
() -> getClass().getSimpleName() + " doesn't support multi-value reactive type wrapper: " +
parameter.getGenericParameterType());
String name = getAttributeName(valueType, parameter);
Mono<?> valueMono = getAttributeMono(name, valueType, context.getModel());
Mono<?> valueMono = getAttributeMono(name, valueType, context, exchange);
Map<String, Object> model = context.getModel().asMap();
MonoProcessor<BindingResult> bindingResultMono = MonoProcessor.create();
@@ -140,22 +146,25 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
}
private String getAttributeName(ResolvableType valueType, MethodParameter parameter) {
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
if (annot != null && StringUtils.hasText(annot.value())) {
return annot.value();
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null && StringUtils.hasText(ann.value())) {
return ann.value();
}
// TODO: Conventions does not deal with async wrappers
return ClassUtils.getShortNameAsProperty(valueType.getRawClass());
}
private Mono<?> getAttributeMono(String attributeName, ResolvableType attributeType, Model model) {
Object attribute = model.asMap().get(attributeName);
private Mono<?> getAttributeMono(
String attributeName, ResolvableType attributeType, BindingContext context, ServerWebExchange exchange) {
Object attribute = context.getModel().asMap().get(attributeName);
if (attribute == null) {
attribute = BeanUtils.instantiateClass(attributeType.getRawClass());
return createAttribute(attributeName, attributeType.getRawClass(), context, exchange);
}
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute);
if (adapterFrom != null) {
Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding supports single-value async types.");
Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding only supports single-value async types");
return Mono.from(adapterFrom.toPublisher(attribute));
}
else {
@@ -163,10 +172,48 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
}
}
private boolean hasErrorsArgument(MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
return paramTypes.length > i && Errors.class.isAssignableFrom(paramTypes[i + 1]);
private Mono<?> createAttribute(
String attributeName, Class<?> attributeType, BindingContext context, ServerWebExchange exchange) {
Constructor<?>[] ctors = attributeType.getConstructors();
if (ctors.length != 1) {
// No standard data class or standard JavaBeans arrangement ->
// defensively go with default constructor, expecting regular bean property bindings.
return Mono.just(BeanUtils.instantiateClass(attributeType));
}
Constructor<?> ctor = ctors[0];
if (ctor.getParameterCount() == 0) {
// A single default constructor -> clearly a standard JavaBeans arrangement.
return Mono.just(BeanUtils.instantiateClass(ctor));
}
// A single data class constructor -> resolve constructor arguments from request parameters.
return exchange.getRequestParams().then(requestParams -> {
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
Class<?>[] paramTypes = ctor.getParameterTypes();
Assert.state(paramNames.length == paramTypes.length,
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
Object[] args = new Object[paramTypes.length];
WebDataBinder binder = context.createDataBinder(exchange, null, attributeName);
for (int i = 0; i < paramNames.length; i++) {
List<String> paramValues = requestParams.get(paramNames[i]);
Object paramValue = null;
if (paramValues != null) {
paramValue = (paramValues.size() == 1 ? paramValues.get(0) :
paramValues.toArray(new String[paramValues.size()]));
}
args[i] = binder.convertIfNecessary(paramValue, paramTypes[i], new MethodParameter(ctor, i));
}
return Mono.fromSupplier(() -> BeanUtils.instantiateClass(ctor, args));
});
}
private boolean hasErrorsArgument(MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
return (paramTypes.length > i && Errors.class.isAssignableFrom(paramTypes[i + 1]));
}
private void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -43,7 +43,6 @@ import org.springframework.web.server.ServerWebExchange;
public class PathVariableMapMethodArgumentResolver extends HandlerMethodArgumentResolverSupport
implements SyncHandlerMethodArgumentResolver {
public PathVariableMapMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -52,17 +52,14 @@ import org.springframework.web.server.ServerWebExchange;
*/
public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
/**
* @param beanFactory a bean factory to use for resolving ${...}
* @param factory a bean factory to use for resolving ${...}
* placeholder and #{...} SpEL expressions in default values;
* or {@code null} if default values are not expected to contain expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
*/
public PathVariableMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(beanFactory, adapterRegistry);
public PathVariableMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(factory, registry);
}
@@ -83,9 +80,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncAr
@Override
@SuppressWarnings("unchecked")
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) {
String attributeName = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
return exchange.getAttribute(attributeName)
.map(value -> ((Map<String, String>) value).get(name));
@@ -98,8 +93,8 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncAr
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(Object arg, String name, MethodParameter parameter,
Model model, ServerWebExchange exchange) {
protected void handleResolvedValue(
Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) {
// TODO: View.PATH_VARIABLES ?
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -24,7 +24,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.util.Assert;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport;
import org.springframework.web.server.ServerWebExchange;
@@ -35,9 +34,7 @@ import org.springframework.web.server.ServerWebExchange;
* @since 5.0
* @see ServerWebExchangeArgumentResolver
*/
public class PrincipalArgumentResolver extends HandlerMethodArgumentResolverSupport
implements HandlerMethodArgumentResolver {
public class PrincipalArgumentResolver extends HandlerMethodArgumentResolverSupport {
public PrincipalArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
@@ -46,7 +43,7 @@ public class PrincipalArgumentResolver extends HandlerMethodArgumentResolverSupp
@Override
public boolean supportsParameter(MethodParameter parameter) {
return checkParamTypeNoReactiveWrapper(parameter, Principal.class::isAssignableFrom);
return checkParameterTypeNoReactiveWrapper(parameter, Principal.class::isAssignableFrom);
}
@Override

View File

@@ -36,15 +36,13 @@ public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueSy
/**
* @param beanFactory a bean factory to use for resolving ${...}
* @param factory a bean factory to use for resolving ${...}
* placeholder and #{...} SpEL expressions in default values;
* or {@code null} if default values are not expected to have expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
*/
public RequestAttributeMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(beanFactory, adapterRegistry);
public RequestAttributeMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(factory, registry);
}
@@ -61,9 +59,7 @@ public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueSy
}
@Override
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) {
return exchange.getAttribute(name);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -43,9 +43,7 @@ import org.springframework.web.server.ServerWebInputException;
* @author Rossen Stoyanchev
* @since 5.0
*/
public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentResolver
implements HandlerMethodArgumentResolver {
public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentResolver {
public RequestBodyArgumentResolver(List<ServerHttpMessageReader<?>> readers,
ReactiveAdapterRegistry registry) {
@@ -60,8 +58,8 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe
}
@Override
public Mono<Object> resolveArgument(MethodParameter param, BindingContext bindingContext,
ServerWebExchange exchange) {
public Mono<Object> resolveArgument(
MethodParameter param, BindingContext bindingContext, ServerWebExchange exchange) {
RequestBody annotation = param.getParameterAnnotation(RequestBody.class);
return readBody(param, annotation.required(), bindingContext, exchange);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -45,7 +45,6 @@ import org.springframework.web.server.ServerWebExchange;
public class RequestHeaderMapMethodArgumentResolver extends HandlerMethodArgumentResolverSupport
implements SyncHandlerMethodArgumentResolver {
public RequestHeaderMapMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
}

View File

@@ -47,15 +47,13 @@ import org.springframework.web.server.ServerWebInputException;
public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
/**
* @param beanFactory a bean factory to use for resolving ${...}
* @param factory a bean factory to use for resolving ${...}
* placeholder and #{...} SpEL expressions in default values;
* or {@code null} if default values are not expected to have expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
*/
public RequestHeaderMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(beanFactory, adapterRegistry);
public RequestHeaderMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(factory, registry);
}

View File

@@ -48,7 +48,6 @@ import org.springframework.web.server.ServerWebExchange;
public class RequestParamMapMethodArgumentResolver extends HandlerMethodArgumentResolverSupport
implements SyncHandlerMethodArgumentResolver {
public RequestParamMapMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -59,19 +59,19 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr
/**
* Class constructor with a default resolution mode flag.
* @param beanFactory a bean factory used for resolving ${...} placeholder
* @param factory a bean factory used for resolving ${...} placeholder
* and #{...} SpEL expressions in default values, or {@code null} if default
* values are not expected to contain expressions
* @param adapterRegistry for checking reactive type wrappers
* @param registry for checking reactive type wrappers
* @param useDefaultResolution in default resolution mode a method argument
* that is a simple type, as defined in {@link BeanUtils#isSimpleProperty},
* is treated as a request parameter even if it isn't annotated, the
* request parameter name is derived from the method parameter name.
*/
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry, boolean useDefaultResolution) {
public RequestParamMethodArgumentResolver(
ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry, boolean useDefaultResolution) {
super(beanFactory, adapterRegistry);
super(factory, registry);
this.useDefaultResolution = useDefaultResolution;
}
@@ -82,7 +82,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr
return true;
}
else if (this.useDefaultResolution) {
return checkParamTypeNoReactiveWrapper(param, BeanUtils::isSimpleProperty);
return checkParameterTypeNoReactiveWrapper(param, BeanUtils::isSimpleProperty);
}
return false;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -48,7 +48,6 @@ import org.springframework.web.server.ServerWebExchange;
public class ServerWebExchangeArgumentResolver extends HandlerMethodArgumentResolverSupport
implements SyncHandlerMethodArgumentResolver {
public ServerWebExchangeArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
}
@@ -56,7 +55,7 @@ public class ServerWebExchangeArgumentResolver extends HandlerMethodArgumentReso
@Override
public boolean supportsParameter(MethodParameter parameter) {
return checkParamTypeNoReactiveWrapper(parameter,
return checkParameterTypeNoReactiveWrapper(parameter,
type -> ServerWebExchange.class.isAssignableFrom(type) ||
ServerHttpRequest.class.isAssignableFrom(type) ||
ServerHttpResponse.class.isAssignableFrom(type) ||

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method.annotation;
import java.util.Optional;
@@ -36,11 +37,8 @@ import org.springframework.web.server.ServerWebInputException;
*/
public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueArgumentResolver {
public SessionAttributeMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
ReactiveAdapterRegistry adapterRegistry) {
super(beanFactory, adapterRegistry);
public SessionAttributeMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
super(factory, registry);
}
@@ -56,9 +54,7 @@ public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueAr
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter,
ServerWebExchange exchange) {
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
return exchange.getSession()
.map(session -> session.getAttribute(name))
.filter(Optional::isPresent)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -34,9 +34,7 @@ import org.springframework.web.server.WebSession;
* @since 5.0
* @see ServerWebExchangeArgumentResolver
*/
public class WebSessionArgumentResolver extends HandlerMethodArgumentResolverSupport
implements HandlerMethodArgumentResolver {
public class WebSessionArgumentResolver extends HandlerMethodArgumentResolverSupport {
public WebSessionArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
@@ -45,12 +43,12 @@ public class WebSessionArgumentResolver extends HandlerMethodArgumentResolverSup
@Override
public boolean supportsParameter(MethodParameter parameter) {
return checkParamTypeNoReactiveWrapper(parameter, WebSession.class::isAssignableFrom);
return checkParameterTypeNoReactiveWrapper(parameter, WebSession.class::isAssignableFrom);
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
public Mono<Object> resolveArgument(
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
Assert.isAssignable(WebSession.class, parameter.getParameterType());
return exchange.getSession().cast(Object.class);

View File

@@ -56,12 +56,9 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.post;
import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.*;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.*;
/**
* Unit tests for {@link AbstractMessageReaderArgumentResolver}.
@@ -307,7 +304,16 @@ public class MessageReaderArgumentResolverTests {
private AbstractMessageReaderArgumentResolver resolver(Decoder<?>... decoders) {
List<ServerHttpMessageReader<?>> readers = new ArrayList<>();
Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder)));
return new AbstractMessageReaderArgumentResolver(readers) {};
return new AbstractMessageReaderArgumentResolver(readers) {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return false;
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
return null;
}
};
}

View File

@@ -42,16 +42,13 @@ import org.springframework.web.method.ResolvableMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
/**
* Unit tests for {@link ModelAttributeMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
public class ModelAttributeMethodArgumentResolverTests {
@@ -116,7 +113,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
public void createAndBindToMono() throws Exception {
MethodParameter parameter = this.testMethod
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
@@ -130,7 +126,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
public void createAndBindToSingle() throws Exception {
MethodParameter parameter = this.testMethod
.annotPresent(ModelAttribute.class).arg(Single.class, Foo.class);
@@ -211,6 +206,7 @@ public class ModelAttributeMethodArgumentResolverTests {
Foo foo = valueExtractor.apply(value);
assertEquals("Robert", foo.getName());
assertEquals(25, foo.getAge());
String key = "foo";
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key;
@@ -231,7 +227,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
@SuppressWarnings("unchecked")
public void validationErrorToMono() throws Exception {
MethodParameter parameter = this.testMethod
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
@@ -246,7 +241,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
public void validationErrorToSingle() throws Exception {
MethodParameter parameter = this.testMethod
.annotPresent(ModelAttribute.class).arg(Single.class, Foo.class);
@@ -264,7 +258,6 @@ public class ModelAttributeMethodArgumentResolverTests {
ServerWebExchange exchange = postForm("age=invalid");
Mono<?> mono = createResolver().resolveArgument(param, this.bindContext, exchange);
mono = valueMonoExtractor.apply(mono);
StepVerifier.create(mono)
@@ -277,6 +270,31 @@ public class ModelAttributeMethodArgumentResolverTests {
.verify();
}
@Test
public void bindDataClass() throws Exception {
testBindBar(this.testMethod.annotNotPresent(ModelAttribute.class).arg(Bar.class));
}
private void testBindBar(MethodParameter param) throws Exception {
Object value = createResolver()
.resolveArgument(param, this.bindContext, postForm("name=Robert&age=25&count=1"))
.block(Duration.ZERO);
Bar bar = (Bar) value;
assertEquals("Robert", bar.getName());
assertEquals(25, bar.getAge());
assertEquals(1, bar.getCount());
String key = "bar";
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key;
Map<String, Object> map = bindContext.getModel().asMap();
assertEquals(map.toString(), 2, map.size());
assertSame(bar, map.get(key));
assertNotNull(map.get(bindingResultKey));
assertTrue(map.get(bindingResultKey) instanceof BindingResult);
}
private ModelAttributeMethodArgumentResolver createResolver() {
return new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry(), false);
@@ -298,7 +316,8 @@ public class ModelAttributeMethodArgumentResolverTests {
Foo fooNotAnnotated,
String stringNotAnnotated,
Mono<Foo> monoNotAnnotated,
Mono<String> monoStringNotAnnotated) {
Mono<String> monoStringNotAnnotated,
Bar barNotAnnotated) {
}
@@ -333,4 +352,35 @@ public class ModelAttributeMethodArgumentResolverTests {
}
}
private static class Bar {
private final String name;
private final int age;
private int count;
public Bar(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return this.age;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
}