diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java index 14ee0f808a..26a2547030 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java @@ -243,13 +243,12 @@ public final class ModelFactory { /** - * Derive the model attribute name for a method parameter based on: - *
    - *
  1. the parameter {@code @ModelAttribute} annotation value - *
  2. the parameter type - *
+ * Derive the model attribute name for the given method parameter based on + * a {@code @ModelAttribute} parameter annotation (if present) or falling + * back on parameter type based conventions. * @param parameter a descriptor for the method parameter - * @return the derived name (never {@code null} or empty String) + * @return the derived name + * @see Conventions#getVariableNameForParameter(MethodParameter) */ public static String getNameForParameter(MethodParameter parameter) { ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java index 17f8c6da45..500ba2c93e 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java @@ -74,10 +74,7 @@ public class SessionAttributesHandler { this.attributeNames.addAll(Arrays.asList(annotation.names())); this.attributeTypes.addAll(Arrays.asList(annotation.types())); } - - for (String attributeName : this.attributeNames) { - this.knownAttributeNames.add(attributeName); - } + this.knownAttributeNames.addAll(this.attributeNames); } /** @@ -90,7 +87,7 @@ public class SessionAttributesHandler { /** * Whether the attribute name or type match the names and types specified - * via {@code @SessionAttributes} in underlying controller. + * via {@code @SessionAttributes} on the underlying controller. *

Attributes successfully resolved through this method are "remembered" * and subsequently used in {@link #retrieveAttributes(WebRequest)} and * {@link #cleanupAttributes(WebRequest)}. diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java index 06c15bf8d5..9390cc01e0 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java @@ -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. @@ -30,12 +30,15 @@ import org.springframework.web.bind.support.SessionAttributeStore; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; -import static java.util.Arrays.*; -import static org.junit.Assert.*; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * Test fixture with {@link SessionAttributesHandler}. - * * @author Rossen Stoyanchev */ public class SessionAttributesHandlerTests { @@ -50,10 +53,10 @@ public class SessionAttributesHandlerTests { @Test public void isSessionAttribute() throws Exception { - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr2", null)); + assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", String.class)); + assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr2", String.class)); assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("simple", TestBean.class)); - assertFalse(sessionAttributesHandler.isHandlerSessionAttribute("simple", null)); + assertFalse(sessionAttributesHandler.isHandlerSessionAttribute("simple", String.class)); } @Test diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java b/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java index 35ebd661d0..4e0b7cf98c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java @@ -24,7 +24,8 @@ import org.springframework.web.bind.support.WebExchangeDataBinder; import org.springframework.web.server.ServerWebExchange; /** - * Context to assist with processing a request and binding it onto Objects. + * Context to assist with binding request data onto Objects and provide access + * to a shared {@link Model} with controller-specific attributes. * *

Provides methods to create a {@link WebExchangeDataBinder} for a specific * target, command Object to apply data binding and validation to, or without a diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java index 9ec4e889a6..9d7e3ef653 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java @@ -30,6 +30,7 @@ import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.reactive.BindingContext; @@ -73,12 +74,13 @@ class ModelInitializer { List> resultList = new ArrayList<>(); attributeMethods.forEach(invocable -> resultList.add(invocable.invoke(exchange, bindingContext))); - return Mono.zip(resultList, objectArray -> { - return Arrays.stream(objectArray) - .map(object -> (HandlerResult) object) - .map(handlerResult -> handleResult(handlerResult, bindingContext)) - .collect(Collectors.toList()); - }).flatMap(completionList -> Mono.when(completionList)); + return Mono + .zip(resultList, objectArray -> { + return Arrays.stream(objectArray) + .map(object -> handleResult(((HandlerResult) object), bindingContext)) + .collect(Collectors.toList()); + }) + .flatMap(completionList -> Mono.when(completionList)); } private Mono handleResult(HandlerResult handlerResult, BindingContext bindingContext) { @@ -86,11 +88,8 @@ class ModelInitializer { if (value != null) { ResolvableType type = handlerResult.getReturnType(); ReactiveAdapter adapter = this.adapterRegistry.getAdapter(type.getRawClass(), value); - if (adapter != null) { - Class attributeType = (adapter.isNoValue() ? Void.class : type.resolveGeneric()); - if (attributeType == Void.class) { - return Mono.from(adapter.toPublisher(value)); - } + if (isAsyncVoidType(type, adapter)) { + return Mono.from(adapter.toPublisher(value)); } String name = getAttributeName(handlerResult.getReturnTypeSource()); bindingContext.getModel().asMap().putIfAbsent(name, value); @@ -98,6 +97,10 @@ class ModelInitializer { return Mono.empty(); } + private boolean isAsyncVoidType(ResolvableType type, @Nullable ReactiveAdapter adapter) { + return adapter != null && (adapter.isNoValue() || type.resolveGeneric() == Void.class); + } + private String getAttributeName(MethodParameter param) { return Optional .ofNullable(AnnotatedElementUtils.findMergedAnnotation(param.getAnnotatedElement(), ModelAttribute.class)) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java index 9d5f0c3dad..cefa7c44af 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java @@ -147,7 +147,6 @@ public abstract class AbstractView implements View, ApplicationContextAware { * Obtain the ApplicationContext for actual use. * @return the ApplicationContext (never {@code null}) * @throws IllegalStateException in case of no ApplicationContext set - * @since 5.0 */ protected final ApplicationContext obtainApplicationContext() { ApplicationContext applicationContext = getApplicationContext(); @@ -191,7 +190,9 @@ public abstract class AbstractView implements View, ApplicationContextAware { *

The default implementation creates a combined output Map that includes * model as well as static attributes with the former taking precedence. */ - protected Mono> getModelAttributes(@Nullable Map model, ServerWebExchange exchange) { + protected Mono> getModelAttributes(@Nullable Map model, + ServerWebExchange exchange) { + int size = (model != null ? model.size() : 0); Map attributes = new LinkedHashMap<>(size); @@ -203,9 +204,11 @@ public abstract class AbstractView implements View, ApplicationContextAware { } /** - * By default, resolve async attributes supported by the {@link ReactiveAdapterRegistry} to their blocking counterparts. - *

View implementations capable of taking advantage of reactive types can override this method if needed. - * @return {@code Mono} to represent when the async attributes have been resolved + * By default, resolve async attributes supported by the + * {@link ReactiveAdapterRegistry} to their blocking counterparts. + *

View implementations capable of taking advantage of reactive types + * can override this method if needed. + * @return {@code Mono} for the completion of async attributes resolution */ protected Mono resolveAsyncAttributes(Map model) { @@ -252,8 +255,9 @@ public abstract class AbstractView implements View, ApplicationContextAware { /** * Create a RequestContext to expose under the specified attribute name. - *

The default implementation creates a standard RequestContext instance for the - * given request and model. Can be overridden in subclasses for custom instances. + *

The default implementation creates a standard RequestContext instance + * for the given request and model. Can be overridden in subclasses for + * custom instances. * @param exchange current exchange * @param model combined output Map (never {@code null}), * with dynamic values taking precedence over static attributes @@ -269,7 +273,8 @@ public abstract class AbstractView implements View, ApplicationContextAware { *

The default implementation looks in the {@link #getApplicationContext() * Spring configuration} for a {@code RequestDataValueProcessor} bean with * the name {@link #REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME}. - * @return the RequestDataValueProcessor, or null if there is none at the application context. + * @return the RequestDataValueProcessor, or null if there is none at the + * application context. */ @Nullable protected RequestDataValueProcessor getRequestDataValueProcessor() { @@ -286,7 +291,8 @@ public abstract class AbstractView implements View, ApplicationContextAware { * with dynamic values taking precedence over static attributes * @param contentType the content type selected to render with which should * match one of the {@link #getSupportedMediaTypes() supported media types}. - *@param exchange current exchange @return {@code Mono} to represent when and if rendering succeeds + *@param exchange current exchange @return {@code Mono} to represent when + * and if rendering succeeds */ protected abstract Mono renderInternal(Map renderAttributes, @Nullable MediaType contentType, ServerWebExchange exchange);