WebFlux support for @SessionAttributes
Issue: SPR-15887
This commit is contained in:
@@ -46,7 +46,8 @@ import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ControllerMethodResolver}.
|
||||
@@ -108,6 +109,7 @@ public class ControllerMethodResolverTests {
|
||||
assertEquals(ErrorsMethodArgumentResolver.class, next(resolvers, index).getClass());
|
||||
assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass());
|
||||
assertEquals(PrincipalArgumentResolver.class, next(resolvers, index).getClass());
|
||||
assertEquals(SessionStatusMethodArgumentResolver.class, next(resolvers, index).getClass());
|
||||
assertEquals(WebSessionArgumentResolver.class, next(resolvers, index).getClass());
|
||||
|
||||
assertEquals(CustomArgumentResolver.class, next(resolvers, index).getClass());
|
||||
|
||||
@@ -23,30 +23,40 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import rx.Single;
|
||||
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.ResolvableMethod;
|
||||
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@@ -55,31 +65,55 @@ import static org.mockito.Mockito.mock;
|
||||
*/
|
||||
public class ModelInitializerTests {
|
||||
|
||||
private final ModelInitializer modelInitializer = new ModelInitializer(new ReactiveAdapterRegistry());
|
||||
private ModelInitializer modelInitializer;
|
||||
|
||||
private final ServerWebExchange exchange = MockServerHttpRequest.get("/path").toExchange();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
ReactiveAdapterRegistry adapterRegistry = new ReactiveAdapterRegistry();
|
||||
|
||||
ArgumentResolverConfigurer resolverConfigurer = new ArgumentResolverConfigurer();
|
||||
resolverConfigurer.addCustomResolver(new ModelArgumentResolver(adapterRegistry));
|
||||
|
||||
ControllerMethodResolver methodResolver = new ControllerMethodResolver(
|
||||
resolverConfigurer, Collections.emptyList(), adapterRegistry, new StaticApplicationContext());
|
||||
|
||||
this.modelInitializer = new ModelInitializer(methodResolver, adapterRegistry);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void basic() throws Exception {
|
||||
TestController controller = new TestController();
|
||||
public void initBinderMethod() throws Exception {
|
||||
|
||||
Validator validator = mock(Validator.class);
|
||||
|
||||
TestController controller = new TestController();
|
||||
controller.setValidator(validator);
|
||||
InitBinderBindingContext context = getBindingContext(controller);
|
||||
|
||||
List<SyncInvocableHandlerMethod> binderMethods = getBinderMethods(controller);
|
||||
List<InvocableHandlerMethod> attributeMethods = getAttributeMethods(controller);
|
||||
Method method = ResolvableMethod.on(TestController.class).annotPresent(GetMapping.class).resolveMethod();
|
||||
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
|
||||
this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(Duration.ofMillis(5000));
|
||||
|
||||
WebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
|
||||
BindingContext bindingContext = new InitBinderBindingContext(bindingInitializer, binderMethods);
|
||||
|
||||
this.modelInitializer.initModel(bindingContext, attributeMethods, this.exchange).block(Duration.ofMillis(5000));
|
||||
|
||||
WebExchangeDataBinder binder = bindingContext.createDataBinder(this.exchange, "name");
|
||||
WebExchangeDataBinder binder = context.createDataBinder(this.exchange, "name");
|
||||
assertEquals(Collections.singletonList(validator), binder.getValidators());
|
||||
}
|
||||
|
||||
Map<String, Object> model = bindingContext.getModel().asMap();
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void modelAttributeMethods() throws Exception {
|
||||
TestController controller = new TestController();
|
||||
InitBinderBindingContext context = getBindingContext(controller);
|
||||
|
||||
Method method = ResolvableMethod.on(TestController.class).annotPresent(GetMapping.class).resolveMethod();
|
||||
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
|
||||
this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(Duration.ofMillis(5000));
|
||||
|
||||
Map<String, Object> model = context.getModel().asMap();
|
||||
assertEquals(5, model.size());
|
||||
|
||||
Object value = model.get("bean");
|
||||
@@ -98,31 +132,101 @@ public class ModelInitializerTests {
|
||||
assertEquals("Void Mono Method Bean", ((TestBean) value).getName());
|
||||
}
|
||||
|
||||
private List<SyncInvocableHandlerMethod> getBinderMethods(Object controller) {
|
||||
return MethodIntrospector
|
||||
.selectMethods(controller.getClass(), BINDER_METHODS).stream()
|
||||
.map(method -> new SyncInvocableHandlerMethod(controller, method))
|
||||
.collect(Collectors.toList());
|
||||
@Test
|
||||
public void saveModelAttributeToSession() throws Exception {
|
||||
TestController controller = new TestController();
|
||||
InitBinderBindingContext context = getBindingContext(controller);
|
||||
|
||||
Method method = ResolvableMethod.on(TestController.class).annotPresent(GetMapping.class).resolveMethod();
|
||||
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
|
||||
this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(Duration.ofMillis(5000));
|
||||
|
||||
WebSession session = this.exchange.getSession().block(Duration.ZERO);
|
||||
assertNotNull(session);
|
||||
assertEquals(0, session.getAttributes().size());
|
||||
|
||||
context.saveModel();
|
||||
assertEquals(1, session.getAttributes().size());
|
||||
assertEquals("Bean", ((TestBean) session.getRequiredAttribute("bean")).getName());
|
||||
}
|
||||
|
||||
private List<InvocableHandlerMethod> getAttributeMethods(Object controller) {
|
||||
return MethodIntrospector
|
||||
.selectMethods(controller.getClass(), ATTRIBUTE_METHODS).stream()
|
||||
.map(method -> toInvocable(controller, method))
|
||||
.collect(Collectors.toList());
|
||||
@Test
|
||||
public void retrieveModelAttributeFromSession() throws Exception {
|
||||
WebSession session = this.exchange.getSession().block(Duration.ZERO);
|
||||
assertNotNull(session);
|
||||
|
||||
TestBean testBean = new TestBean("Session Bean");
|
||||
session.getAttributes().put("bean", testBean);
|
||||
|
||||
TestController controller = new TestController();
|
||||
InitBinderBindingContext context = getBindingContext(controller);
|
||||
|
||||
Method method = ResolvableMethod.on(TestController.class).annotPresent(GetMapping.class).resolveMethod();
|
||||
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
|
||||
this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(Duration.ofMillis(5000));
|
||||
|
||||
context.saveModel();
|
||||
assertEquals(1, session.getAttributes().size());
|
||||
assertEquals("Session Bean", ((TestBean) session.getRequiredAttribute("bean")).getName());
|
||||
}
|
||||
|
||||
private InvocableHandlerMethod toInvocable(Object controller, Method method) {
|
||||
ModelArgumentResolver resolver = new ModelArgumentResolver(new ReactiveAdapterRegistry());
|
||||
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(controller, method);
|
||||
handlerMethod.setArgumentResolvers(Collections.singletonList(resolver));
|
||||
return handlerMethod;
|
||||
@Test
|
||||
public void requiredSessionAttributeMissing() throws Exception {
|
||||
TestController controller = new TestController();
|
||||
InitBinderBindingContext context = getBindingContext(controller);
|
||||
|
||||
Method method = ResolvableMethod.on(TestController.class).annotPresent(PostMapping.class).resolveMethod();
|
||||
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
|
||||
try {
|
||||
this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(Duration.ofMillis(5000));
|
||||
fail();
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
assertEquals("Required attribute 'missing-bean' is missing.", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearModelAttributeFromSession() throws Exception {
|
||||
WebSession session = this.exchange.getSession().block(Duration.ZERO);
|
||||
assertNotNull(session);
|
||||
|
||||
TestBean testBean = new TestBean("Session Bean");
|
||||
session.getAttributes().put("bean", testBean);
|
||||
|
||||
TestController controller = new TestController();
|
||||
InitBinderBindingContext context = getBindingContext(controller);
|
||||
|
||||
Method method = ResolvableMethod.on(TestController.class).annotPresent(GetMapping.class).resolveMethod();
|
||||
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
|
||||
this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(Duration.ofMillis(5000));
|
||||
|
||||
context.getSessionStatus().setComplete();
|
||||
context.saveModel();
|
||||
|
||||
assertEquals(0, session.getAttributes().size());
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private InitBinderBindingContext getBindingContext(Object controller) {
|
||||
|
||||
List<SyncInvocableHandlerMethod> binderMethods =
|
||||
MethodIntrospector.selectMethods(controller.getClass(), BINDER_METHODS)
|
||||
.stream()
|
||||
.map(method -> new SyncInvocableHandlerMethod(controller, method))
|
||||
.collect(Collectors.toList());;
|
||||
|
||||
WebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
|
||||
return new InitBinderBindingContext(bindingInitializer, binderMethods);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@SessionAttributes({"bean", "missing-bean"})
|
||||
private static class TestController {
|
||||
|
||||
@Nullable
|
||||
private Validator validator;
|
||||
|
||||
|
||||
@@ -165,8 +269,12 @@ public class ModelInitializerTests {
|
||||
.then();
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
public void handle() {}
|
||||
@GetMapping
|
||||
public void handleGet() {}
|
||||
|
||||
@PostMapping
|
||||
public void handlePost(@ModelAttribute("missing-bean") TestBean testBean) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.session.InMemoryWebSessionStore;
|
||||
|
||||
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 {
|
||||
|
||||
private final SessionAttributesHandler sessionAttributesHandler =
|
||||
new SessionAttributesHandler(TestController.class);
|
||||
|
||||
|
||||
@Test
|
||||
public void isSessionAttribute() throws Exception {
|
||||
assertTrue(this.sessionAttributesHandler.isHandlerSessionAttribute("attr1", String.class));
|
||||
assertTrue(this.sessionAttributesHandler.isHandlerSessionAttribute("attr2", String.class));
|
||||
assertTrue(this.sessionAttributesHandler.isHandlerSessionAttribute("simple", TestBean.class));
|
||||
assertFalse(this.sessionAttributesHandler.isHandlerSessionAttribute("simple", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveAttributes() throws Exception {
|
||||
WebSession session = new InMemoryWebSessionStore().createWebSession().block(Duration.ZERO);
|
||||
assertNotNull(session);
|
||||
|
||||
session.getAttributes().put("attr1", "value1");
|
||||
session.getAttributes().put("attr2", "value2");
|
||||
session.getAttributes().put("attr3", new TestBean());
|
||||
session.getAttributes().put("attr4", new TestBean());
|
||||
|
||||
assertEquals("Named attributes (attr1, attr2) should be 'known' right away",
|
||||
new HashSet<>(asList("attr1", "attr2")),
|
||||
sessionAttributesHandler.retrieveAttributes(session).keySet());
|
||||
|
||||
// Resolve 'attr3' by type
|
||||
sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class);
|
||||
|
||||
assertEquals("Named attributes (attr1, attr2) and resolved attribute (att3) should be 'known'",
|
||||
new HashSet<>(asList("attr1", "attr2", "attr3")),
|
||||
sessionAttributesHandler.retrieveAttributes(session).keySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cleanupAttributes() throws Exception {
|
||||
WebSession session = new InMemoryWebSessionStore().createWebSession().block(Duration.ZERO);
|
||||
assertNotNull(session);
|
||||
|
||||
session.getAttributes().put("attr1", "value1");
|
||||
session.getAttributes().put("attr2", "value2");
|
||||
session.getAttributes().put("attr3", new TestBean());
|
||||
|
||||
this.sessionAttributesHandler.cleanupAttributes(session);
|
||||
|
||||
assertNull(session.getAttributes().get("attr1"));
|
||||
assertNull(session.getAttributes().get("attr2"));
|
||||
assertNotNull(session.getAttributes().get("attr3"));
|
||||
|
||||
// Resolve 'attr3' by type
|
||||
this.sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class);
|
||||
this.sessionAttributesHandler.cleanupAttributes(session);
|
||||
|
||||
assertNull(session.getAttributes().get("attr3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void storeAttributes() throws Exception {
|
||||
WebSession session = new InMemoryWebSessionStore().createWebSession().block(Duration.ZERO);
|
||||
assertNotNull(session);
|
||||
|
||||
ModelMap model = new ModelMap();
|
||||
model.put("attr1", "value1");
|
||||
model.put("attr2", "value2");
|
||||
model.put("attr3", new TestBean());
|
||||
|
||||
sessionAttributesHandler.storeAttributes(session, model);
|
||||
|
||||
assertEquals("value1", session.getAttributes().get("attr1"));
|
||||
assertEquals("value2", session.getAttributes().get("attr2"));
|
||||
assertTrue(session.getAttributes().get("attr3") instanceof TestBean);
|
||||
}
|
||||
|
||||
|
||||
@SessionAttributes(names = { "attr1", "attr2" }, types = { TestBean.class })
|
||||
private static class TestController {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user