Merge branch '6.1.x'
This commit is contained in:
commit
e1567b93c2
|
@ -77,7 +77,7 @@ and others) and is equivalent to `required=false`.
|
||||||
| For access to a part in a `multipart/form-data` request. Supports reactive types.
|
| For access to a part in a `multipart/form-data` request. Supports reactive types.
|
||||||
See xref:web/webflux/controller/ann-methods/multipart-forms.adoc[Multipart Content] and xref:web/webflux/reactive-spring.adoc#webflux-multipart[Multipart Data].
|
See xref:web/webflux/controller/ann-methods/multipart-forms.adoc[Multipart Content] and xref:web/webflux/reactive-spring.adoc#webflux-multipart[Multipart Data].
|
||||||
|
|
||||||
| `java.util.Map`, `org.springframework.ui.Model`, and `org.springframework.ui.ModelMap`.
|
| `java.util.Map` or `org.springframework.ui.Model`
|
||||||
| For access to the model that is used in HTML controllers and is exposed to templates as
|
| For access to the model that is used in HTML controllers and is exposed to templates as
|
||||||
part of view rendering.
|
part of view rendering.
|
||||||
|
|
||||||
|
@ -89,9 +89,9 @@ and others) and is equivalent to `required=false`.
|
||||||
Note that use of `@ModelAttribute` is optional -- for example, to set its attributes.
|
Note that use of `@ModelAttribute` is optional -- for example, to set its attributes.
|
||||||
See "`Any other argument`" later in this table.
|
See "`Any other argument`" later in this table.
|
||||||
|
|
||||||
| `Errors`, `BindingResult`
|
| `Errors` or `BindingResult`
|
||||||
| For access to errors from validation and data binding for a command object, i.e. a
|
| For access to errors from validation and data binding for a command object, i.e. a
|
||||||
`@ModelAttribute` argument. An `Errors`, or `BindingResult` argument must be declared
|
`@ModelAttribute` argument. An `Errors` or `BindingResult` argument must be declared
|
||||||
immediately after the validated method argument.
|
immediately after the validated method argument.
|
||||||
|
|
||||||
| `SessionStatus` + class-level `@SessionAttributes`
|
| `SessionStatus` + class-level `@SessionAttributes`
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2023 the original author or authors.
|
* Copyright 2002-2024 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.
|
||||||
|
@ -46,7 +46,7 @@ public class ConcurrentModel extends ConcurrentHashMap<String, Object> implement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new {@code ModelMap} containing the supplied attribute
|
* Construct a new {@code ConcurrentModel} containing the supplied attribute
|
||||||
* under the supplied name.
|
* under the supplied name.
|
||||||
* @see #addAttribute(String, Object)
|
* @see #addAttribute(String, Object)
|
||||||
*/
|
*/
|
||||||
|
@ -55,8 +55,8 @@ public class ConcurrentModel extends ConcurrentHashMap<String, Object> implement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new {@code ModelMap} containing the supplied attribute.
|
* Construct a new {@code ConcurrentModel} containing the supplied attribute.
|
||||||
* Uses attribute name generation to generate the key for the supplied model
|
* <p>Uses attribute name generation to generate the key for the supplied model
|
||||||
* object.
|
* object.
|
||||||
* @see #addAttribute(Object)
|
* @see #addAttribute(Object)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* Generic support for UI layer concepts.
|
* Generic support for UI layer concepts.
|
||||||
* Provides a generic ModelMap for model holding.
|
* <p>Provides generic {@code Model} and {@code ModelMap} holders for model attributes.
|
||||||
*/
|
*/
|
||||||
@NonNullApi
|
@NonNullApi
|
||||||
@NonNullFields
|
@NonNullFields
|
||||||
|
|
|
@ -18,11 +18,11 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||||
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.beans.testfixture.beans.TestBean;
|
import org.springframework.beans.testfixture.beans.TestBean;
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||||
import org.springframework.web.server.WebSession;
|
import org.springframework.web.server.WebSession;
|
||||||
import org.springframework.web.testfixture.server.MockWebSession;
|
import org.springframework.web.testfixture.server.MockWebSession;
|
||||||
|
@ -31,7 +31,8 @@ import static java.util.Arrays.asList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test fixture with {@link SessionAttributesHandler}.
|
* Tests for {@link SessionAttributesHandler}.
|
||||||
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
*/
|
*/
|
||||||
class SessionAttributesHandlerTests {
|
class SessionAttributesHandlerTests {
|
||||||
|
@ -86,11 +87,11 @@ class SessionAttributesHandlerTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void storeAttributes() {
|
void storeAttributes() {
|
||||||
|
Map<String, Object> model = Map.of(
|
||||||
ModelMap model = new ModelMap();
|
"attr1", "value1",
|
||||||
model.put("attr1", "value1");
|
"attr2", "value2",
|
||||||
model.put("attr2", "value2");
|
"attr3", new TestBean()
|
||||||
model.put("attr3", new TestBean());
|
);
|
||||||
|
|
||||||
WebSession session = new MockWebSession();
|
WebSession session = new MockWebSession();
|
||||||
sessionAttributesHandler.storeAttributes(session, model);
|
sessionAttributesHandler.storeAttributes(session, model);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2024 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.
|
||||||
|
@ -20,7 +20,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.context.support.GenericApplicationContext;
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.util.UriComponents;
|
import org.springframework.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
@ -38,7 +37,7 @@ public class DummyMacroRequestContext {
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
private final ServerWebExchange exchange;
|
||||||
|
|
||||||
private final ModelMap model;
|
private final Map<String, Object> model;
|
||||||
|
|
||||||
private final GenericApplicationContext context;
|
private final GenericApplicationContext context;
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ public class DummyMacroRequestContext {
|
||||||
|
|
||||||
private String contextPath;
|
private String contextPath;
|
||||||
|
|
||||||
public DummyMacroRequestContext(ServerWebExchange exchange, ModelMap model, GenericApplicationContext context) {
|
public DummyMacroRequestContext(ServerWebExchange exchange, Map<String, Object> model, GenericApplicationContext context) {
|
||||||
this.exchange = exchange;
|
this.exchange = exchange;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
|
@ -17,11 +17,10 @@
|
||||||
package org.springframework.web.reactive.result.view;
|
package org.springframework.web.reactive.result.view;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Arrays;
|
import java.util.HashMap;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
@ -30,8 +29,6 @@ import org.springframework.core.codec.CharSequenceEncoder;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||||
import org.springframework.ui.ExtendedModelMap;
|
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
|
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
|
||||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||||
|
|
||||||
|
@ -48,7 +45,7 @@ class HttpMessageWriterViewTests {
|
||||||
|
|
||||||
private HttpMessageWriterView view = new HttpMessageWriterView(new Jackson2JsonEncoder());
|
private HttpMessageWriterView view = new HttpMessageWriterView(new Jackson2JsonEncoder());
|
||||||
|
|
||||||
private final ModelMap model = new ExtendedModelMap();
|
private final Map<String, Object> model = new HashMap<>();
|
||||||
|
|
||||||
private final MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
|
private final MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
|
||||||
|
|
||||||
|
@ -63,18 +60,18 @@ class HttpMessageWriterViewTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void singleMatch() throws Exception {
|
void singleMatch() throws Exception {
|
||||||
this.view.setModelKeys(Collections.singleton("foo2"));
|
this.view.setModelKeys(Set.of("foo2"));
|
||||||
this.model.addAttribute("foo1", Collections.singleton("bar1"));
|
this.model.put("foo1", Set.of("bar1"));
|
||||||
this.model.addAttribute("foo2", Collections.singleton("bar2"));
|
this.model.put("foo2", Set.of("bar2"));
|
||||||
this.model.addAttribute("foo3", Collections.singleton("bar3"));
|
this.model.put("foo3", Set.of("bar3"));
|
||||||
|
|
||||||
assertThat(doRender()).isEqualTo("[\"bar2\"]");
|
assertThat(doRender()).isEqualTo("[\"bar2\"]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void noMatch() throws Exception {
|
void noMatch() throws Exception {
|
||||||
this.view.setModelKeys(Collections.singleton("foo2"));
|
this.view.setModelKeys(Set.of("foo2"));
|
||||||
this.model.addAttribute("foo1", "bar1");
|
this.model.put("foo1", "bar1");
|
||||||
|
|
||||||
assertThat(doRender()).isEmpty();
|
assertThat(doRender()).isEmpty();
|
||||||
}
|
}
|
||||||
|
@ -82,18 +79,18 @@ class HttpMessageWriterViewTests {
|
||||||
@Test
|
@Test
|
||||||
void noMatchBecauseNotSupported() throws Exception {
|
void noMatchBecauseNotSupported() throws Exception {
|
||||||
this.view = new HttpMessageWriterView(new Jaxb2XmlEncoder());
|
this.view = new HttpMessageWriterView(new Jaxb2XmlEncoder());
|
||||||
this.view.setModelKeys(new HashSet<>(Collections.singletonList("foo1")));
|
this.view.setModelKeys(Set.of("foo1"));
|
||||||
this.model.addAttribute("foo1", "bar1");
|
this.model.put("foo1", "bar1");
|
||||||
|
|
||||||
assertThat(doRender()).isEmpty();
|
assertThat(doRender()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void multipleMatches() throws Exception {
|
void multipleMatches() throws Exception {
|
||||||
this.view.setModelKeys(new HashSet<>(Arrays.asList("foo1", "foo2")));
|
this.view.setModelKeys(Set.of("foo1", "foo2"));
|
||||||
this.model.addAttribute("foo1", Collections.singleton("bar1"));
|
this.model.put("foo1", Set.of("bar1"));
|
||||||
this.model.addAttribute("foo2", Collections.singleton("bar2"));
|
this.model.put("foo2", Set.of("bar2"));
|
||||||
this.model.addAttribute("foo3", Collections.singleton("bar3"));
|
this.model.put("foo3", Set.of("bar3"));
|
||||||
|
|
||||||
assertThat(doRender()).isEqualTo("{\"foo1\":[\"bar1\"],\"foo2\":[\"bar2\"]}");
|
assertThat(doRender()).isEqualTo("{\"foo1\":[\"bar1\"],\"foo2\":[\"bar2\"]}");
|
||||||
}
|
}
|
||||||
|
@ -101,12 +98,12 @@ class HttpMessageWriterViewTests {
|
||||||
@Test
|
@Test
|
||||||
void multipleMatchesNotSupported() throws Exception {
|
void multipleMatchesNotSupported() throws Exception {
|
||||||
this.view = new HttpMessageWriterView(CharSequenceEncoder.allMimeTypes());
|
this.view = new HttpMessageWriterView(CharSequenceEncoder.allMimeTypes());
|
||||||
this.view.setModelKeys(new HashSet<>(Arrays.asList("foo1", "foo2")));
|
this.view.setModelKeys(Set.of("foo1", "foo2"));
|
||||||
this.model.addAttribute("foo1", "bar1");
|
this.model.put("foo1", "bar1");
|
||||||
this.model.addAttribute("foo2", "bar2");
|
this.model.put("foo2", "bar2");
|
||||||
|
|
||||||
assertThatIllegalStateException().isThrownBy(
|
assertThatIllegalStateException()
|
||||||
this::doRender)
|
.isThrownBy(this::doRender)
|
||||||
.withMessageContaining("Map rendering is not supported");
|
.withMessageContaining("Map rendering is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,8 +112,8 @@ class HttpMessageWriterViewTests {
|
||||||
Map<String, String> pojoData = new LinkedHashMap<>();
|
Map<String, String> pojoData = new LinkedHashMap<>();
|
||||||
pojoData.put("foo", "f");
|
pojoData.put("foo", "f");
|
||||||
pojoData.put("bar", "b");
|
pojoData.put("bar", "b");
|
||||||
this.model.addAttribute("pojoData", pojoData);
|
this.model.put("pojoData", pojoData);
|
||||||
this.view.setModelKeys(Collections.singleton("pojoData"));
|
this.view.setModelKeys(Set.of("pojoData"));
|
||||||
|
|
||||||
this.view.render(this.model, MediaType.APPLICATION_JSON, exchange).block(Duration.ZERO);
|
this.view.render(this.model, MediaType.APPLICATION_JSON, exchange).block(Duration.ZERO);
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,6 @@ import org.springframework.beans.testfixture.beans.TestBean;
|
||||||
import org.springframework.context.support.GenericApplicationContext;
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.ui.ExtendedModelMap;
|
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.reactive.result.view.BindStatus;
|
import org.springframework.web.reactive.result.view.BindStatus;
|
||||||
|
@ -322,7 +320,7 @@ class FreeMarkerMacroTests {
|
||||||
names.put("Fred", "Fred Bloggs");
|
names.put("Fred", "Fred Bloggs");
|
||||||
names.put("Rob&Harrop", "Rob Harrop");
|
names.put("Rob&Harrop", "Rob Harrop");
|
||||||
|
|
||||||
ModelMap model = new ExtendedModelMap();
|
Map<String, Object> model = new HashMap<>();
|
||||||
DummyMacroRequestContext rc = new DummyMacroRequestContext(this.exchange, model,
|
DummyMacroRequestContext rc = new DummyMacroRequestContext(this.exchange, model,
|
||||||
this.applicationContext);
|
this.applicationContext);
|
||||||
rc.setMessageMap(msgMap);
|
rc.setMessageMap(msgMap);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2023 the original author or authors.
|
* Copyright 2002-2024 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.
|
||||||
|
@ -19,6 +19,7 @@ package org.springframework.web.reactive.result.view.freemarker;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import freemarker.template.Configuration;
|
import freemarker.template.Configuration;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -28,8 +29,6 @@ import reactor.test.StepVerifier;
|
||||||
import org.springframework.context.ApplicationContextException;
|
import org.springframework.context.ApplicationContextException;
|
||||||
import org.springframework.context.support.GenericApplicationContext;
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||||
import org.springframework.ui.ExtendedModelMap;
|
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.web.reactive.result.view.ZeroDemandResponse;
|
import org.springframework.web.reactive.result.view.ZeroDemandResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
@ -125,8 +124,7 @@ class FreeMarkerViewTests {
|
||||||
freeMarkerView.setConfiguration(this.freeMarkerConfig);
|
freeMarkerView.setConfiguration(this.freeMarkerConfig);
|
||||||
freeMarkerView.setUrl("test.ftl");
|
freeMarkerView.setUrl("test.ftl");
|
||||||
|
|
||||||
ModelMap model = new ExtendedModelMap();
|
Map<String, Object> model = Map.of("hello", "hi FreeMarker");
|
||||||
model.addAttribute("hello", "hi FreeMarker");
|
|
||||||
freeMarkerView.render(model, null, this.exchange).block(Duration.ofMillis(5000));
|
freeMarkerView.render(model, null, this.exchange).block(Duration.ofMillis(5000));
|
||||||
|
|
||||||
StepVerifier.create(this.exchange.getResponse().getBody())
|
StepVerifier.create(this.exchange.getResponse().getBody())
|
||||||
|
@ -148,8 +146,7 @@ class FreeMarkerViewTests {
|
||||||
freeMarkerView.setConfiguration(this.freeMarkerConfig);
|
freeMarkerView.setConfiguration(this.freeMarkerConfig);
|
||||||
freeMarkerView.setUrl("test.ftl");
|
freeMarkerView.setUrl("test.ftl");
|
||||||
|
|
||||||
ModelMap model = new ExtendedModelMap();
|
Map<String, Object> model = Map.of("hello", "hi FreeMarker");
|
||||||
model.addAttribute("hello", "hi FreeMarker");
|
|
||||||
freeMarkerView.render(model, null, exchange).subscribe();
|
freeMarkerView.render(model, null, exchange).subscribe();
|
||||||
|
|
||||||
response.cancelWrite();
|
response.cancelWrite();
|
||||||
|
|
Loading…
Reference in New Issue