Skip to content

Commit 437c33b

Browse files
committed
WebFlux @ModelAttribute coverage in reference
Issue: SPR-16040
1 parent 43d3abd commit 437c33b

File tree

2 files changed

+446
-133
lines changed

2 files changed

+446
-133
lines changed

src/docs/asciidoc/web/webflux.adoc

+304
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,263 @@ Type conversion is applied automatically if the target method parameter type is
12251225
`String`. See <<mvc-ann-typeconversion>>.
12261226

12271227

1228+
[[webflux-ann-modelattrib-method-args]]
1229+
==== @ModelAttribute
1230+
[.small]#<<web.adoc#mvc-ann-modelattrib-method-args,Same in Spring MVC>>#
1231+
1232+
Use the `@ModelAttribute` annotation on a method argument to access an attribute from the
1233+
model, or have it instantiated if not present. The model attribute is also overlaid with
1234+
values query parameters for form fields whose names match to field names. This is
1235+
referred to as data binding and it saves you from having to deal with parsing and
1236+
converting individual query parameters and form fields. For example:
1237+
1238+
[source,java,indent=0]
1239+
[subs="verbatim,quotes"]
1240+
----
1241+
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
1242+
public String processSubmit(**@ModelAttribute Pet pet**) { }
1243+
----
1244+
1245+
The `Pet` instance above is resolved as follows:
1246+
1247+
* From the model if already added via <<webflux-ann-modelattrib-methods>>.
1248+
* From the HTTP session via <<webflux-ann-sessionattributes>>.
1249+
* From the invocation of a default constructor.
1250+
* From the invocation of a "primary constructor" with arguments matching to query
1251+
parameters or form fields; argument names are determined via JavaBeans
1252+
`@ConstructorProperties` or via runtime-retained parameter names in the bytecode.
1253+
1254+
After the model attribute instance is obtained, data binding is applied. The
1255+
`WebExchangeDataBinder` class matches names of query parameters and form fields to field
1256+
names on the target Object. Matching fields are populated after type conversion is applied
1257+
where necessary. For more on data binding (and validation) see
1258+
<<core.adoc#validation, Validation>>. For more on customizing data binding see
1259+
<<webflux-ann-initbinder>>.
1260+
1261+
Data binding may result in errors. By default a `WebExchangeBindException` is raised but
1262+
to check for such errors in the controller method, add a `BindingResult` argument
1263+
immediately next to the `@ModelAttribute` as shown below:
1264+
1265+
[source,java,indent=0]
1266+
[subs="verbatim,quotes"]
1267+
----
1268+
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
1269+
public String processSubmit(**@ModelAttribute("pet") Pet pet**, BindingResult result) {
1270+
if (result.hasErrors()) {
1271+
return "petForm";
1272+
}
1273+
// ...
1274+
}
1275+
----
1276+
1277+
Validation can be applied automatically after data binding by adding the
1278+
`javax.validation.Valid` annotation or Spring's `@Validated` annotation (also see
1279+
<<core.adoc#validation-beanvalidation, Bean validation>> and
1280+
<<core.adoc#validation, Spring validation>>). For example:
1281+
1282+
[source,java,indent=0]
1283+
[subs="verbatim,quotes"]
1284+
----
1285+
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
1286+
public String processSubmit(**@Valid @ModelAttribute("pet") Pet pet**, BindingResult result) {
1287+
if (result.hasErrors()) {
1288+
return "petForm";
1289+
}
1290+
// ...
1291+
}
1292+
----
1293+
1294+
Spring WebFlux, unlike Spring MVC, supports reactive types in the model, e.g.
1295+
`Mono<Account>` or `io.reactivex.Single<Account>`. An `@ModelAttribute` argument can be
1296+
declared with or without a reactive type wrapper, and it will be resolved accordingly,
1297+
to the actual value if necessary. Note however that in order to use a `BindingResult`
1298+
argument, you must declare the `@ModelAttribute` argument before it without a reactive
1299+
type wrapper, as shown earlier. Alternatively, you can handle any errors through the
1300+
reactive type:
1301+
1302+
[source,java,indent=0]
1303+
[subs="verbatim,quotes"]
1304+
----
1305+
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
1306+
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
1307+
return petMono
1308+
.flatMap(pet -> {
1309+
// ...
1310+
})
1311+
.onErrorResume(ex -> {
1312+
// ...
1313+
});
1314+
}
1315+
----
1316+
1317+
1318+
[[webflux-ann-sessionattributes]]
1319+
==== @SessionAttributes
1320+
[.small]#<<web.adoc#mvc-ann-sessionattributes,Same in Spring MVC>>#
1321+
1322+
`@SessionAttributes` is used to store model attributes in the `WebSession` between
1323+
requests. It is a type-level annotation that declares session attributes used by a
1324+
specific controller. This will typically list the names of model attributes or types of
1325+
model attributes which should be transparently stored in the session for subsequent
1326+
requests to access.
1327+
1328+
For example:
1329+
1330+
[source,java,indent=0]
1331+
[subs="verbatim,quotes"]
1332+
----
1333+
@Controller
1334+
**@SessionAttributes("pet")**
1335+
public class EditPetForm {
1336+
// ...
1337+
}
1338+
----
1339+
1340+
On the first request when a model attribute with the name "pet" is added to the model,
1341+
it is automatically promoted to and saved in the `WebSession`. It remains there until
1342+
another controller method uses a `SessionStatus` method argument to clear the storage:
1343+
1344+
[source,java,indent=0]
1345+
[subs="verbatim,quotes"]
1346+
----
1347+
@Controller
1348+
**@SessionAttributes("pet")**
1349+
public class EditPetForm {
1350+
1351+
// ...
1352+
1353+
@PostMapping("/pets/{id}")
1354+
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
1355+
if (errors.hasErrors) {
1356+
// ...
1357+
}
1358+
status.setComplete();
1359+
// ...
1360+
}
1361+
}
1362+
}
1363+
----
1364+
1365+
1366+
[[webflux-ann-sessionattribute]]
1367+
==== @SessionAttribute
1368+
[.small]#<<web.adoc#mvc-ann-sessionattribute,Same in Spring MVC>>#
1369+
1370+
If you need access to pre-existing session attributes that are managed globally,
1371+
i.e. outside the controller (e.g. by a filter), and may or may not be present
1372+
use the `@SessionAttribute` annotation on a method parameter:
1373+
1374+
[source,java,indent=0]
1375+
[subs="verbatim,quotes"]
1376+
----
1377+
@RequestMapping("/")
1378+
public String handle(**@SessionAttribute** User user) {
1379+
// ...
1380+
}
1381+
----
1382+
1383+
For use cases that require adding or removing session attributes consider injecting
1384+
`WebSession` into the controller method.
1385+
1386+
For temporary storage of model attributes in the session as part of a controller
1387+
workflow consider using `SessionAttributes` as described in
1388+
<<webflux-ann-sessionattributes>>.
1389+
1390+
1391+
1392+
[[webflux-ann-modelattrib-methods]]
1393+
=== Model Methods
1394+
[.small]#<<web.adoc#mvc-ann-modelattrib-methods,Same in Spring MVC>>#
1395+
1396+
The `@ModelAttribute` annotation can be used on `@RequestMapping`
1397+
<<webflux-ann-modelattrib-method-args,method arguments>> to create or access an Object
1398+
from the model and bind it to the request. `@ModelAttribute` can also be used as a
1399+
method-level annotation on controller methods whose purpose is not to handle requests
1400+
but to add commonly needed model attributes prior to request handling.
1401+
1402+
A controller can have any number of `@ModelAttribute` methods. All such methods are
1403+
invoked before `@RequestMapping` methods in the same controller. A `@ModelAttribute`
1404+
method can also be shared across controllers via `@ControllerAdvice`. See the section on
1405+
<<webflux-ann-controller-advice>> for more details.
1406+
1407+
`@ModelAttribute` methods have flexible method signatures. They support many of the same
1408+
arguments as `@RequestMapping` methods except for `@ModelAttribute` itself nor anything
1409+
related to the request body.
1410+
1411+
An example `@ModelAttribute` method:
1412+
1413+
[source,java,indent=0]
1414+
[subs="verbatim,quotes"]
1415+
----
1416+
@ModelAttribute
1417+
public void populateModel(@RequestParam String number, Model model) {
1418+
model.addAttribute(accountRepository.findAccount(number));
1419+
// add more ...
1420+
}
1421+
----
1422+
1423+
To add one attribute only:
1424+
1425+
[source,java,indent=0]
1426+
[subs="verbatim,quotes"]
1427+
----
1428+
@ModelAttribute
1429+
public Account addAccount(@RequestParam String number) {
1430+
return accountRepository.findAccount(number);
1431+
}
1432+
----
1433+
1434+
[NOTE]
1435+
====
1436+
When a name is not explicitly specified, a default name is chosen based on the Object
1437+
type as explained in the Javadoc for
1438+
{api-spring-framework}/core/Conventions.html[Conventions].
1439+
You can always assign an explicit name by using the overloaded `addAttribute` method or
1440+
through the name attribute on `@ModelAttribute` (for a return value).
1441+
====
1442+
1443+
Spring WebFlux, unlike Spring MVC, explicitly supports reactive types in the model,
1444+
e.g. `Mono<Account>` or `io.reactivex.Single<Account>`. Such asynchronous model
1445+
attributes may be transparently resolved (and the model updated) to their actual values
1446+
at the time of `@RequestMapping` invocation, providing a `@ModelAttribute` argument is
1447+
declared without a wrapper, for example:
1448+
1449+
[source,java,indent=0]
1450+
[subs="verbatim,quotes"]
1451+
----
1452+
@ModelAttribute
1453+
public void addAccount(@RequestParam String number) {
1454+
Mono<Account> accountMono = accountRepository.findAccount(number);
1455+
model.addAttribute("account", accountMono);
1456+
}
1457+
1458+
@PostMapping("/accounts")
1459+
public String handle(@ModelAttribute Account account, BindingResult errors) {
1460+
// ...
1461+
}
1462+
----
1463+
1464+
In addition any model attributes that have a reactive type wrapper are resolved to their
1465+
actual values (and the model updated) just prior to view rendering.
1466+
1467+
`@ModelAttribute` can also be used as a method-level annotation on `@RequestMapping`
1468+
methods in which case the return value of the `@RequestMapping` method is interpreted as a
1469+
model attribute. This is typically not required, as it is the default behavior in HTML
1470+
controllers, unless the return value is a `String` which would otherwise be interpreted
1471+
as a view name. `@ModelAttribute` can also help to customize the model attribute name:
1472+
1473+
[source,java,indent=0]
1474+
[subs="verbatim,quotes"]
1475+
----
1476+
@GetMapping("/accounts/{id}")
1477+
@ModelAttribute("myAccount")
1478+
public Account handle() {
1479+
// ...
1480+
return account;
1481+
}
1482+
----
1483+
1484+
12281485

12291486
[[webflux-ann-initbinder]]
12301487
=== Binder Methods
@@ -1284,6 +1541,53 @@ controller-specific ``Formatter``'s:
12841541

12851542

12861543

1544+
[[webflux-ann-controller-advice]]
1545+
=== Controller Advice
1546+
[.small]#<<web.adoc#mvc-ann-controller-advice,Same in Spring MVC>>#
1547+
1548+
Typically `@ExceptionHandler`, `@InitBinder`, and `@ModelAttribute` methods apply within
1549+
the `@Controller` class (or class hierarchy) they are declared in. If you want such
1550+
methods to apply more globally, across controllers, you can declare them in a class
1551+
marked with `@ControllerAdvice` or `@RestControllerAdvice`.
1552+
1553+
`@ControllerAdvice` is marked with `@Component` which means such classes can be registered
1554+
as Spring beans via <<core.adoc#beans-java-instantiating-container-scan,component scanning>>.
1555+
`@RestControllerAdvice` is also a meta-annotation marked with both `@ControllerAdvice` and
1556+
`@ResponseBody` which essentially means `@ExceptionHandler` methods are rendered to the
1557+
response body via message conversion (vs view resolution/template rendering).
1558+
1559+
On startup, the infrastructure classes for `@RequestMapping` and `@ExceptionHandler` methods
1560+
detect Spring beans of type `@ControllerAdvice`, and then apply their methods at runtime.
1561+
Global `@ExceptionHandler` methods (from an `@ControllerAdvice`) are applied *after* local
1562+
ones (from the `@Controller`). By contrast global `@ModelAttribute` and `@InitBinder`
1563+
methods are applied *before* local ones.
1564+
1565+
By default `@ControllerAdvice` methods apply to every request, i.e. all controllers, but
1566+
you can narrow that down to a subset of controllers via attributes on the annotation:
1567+
1568+
[source,java,indent=0]
1569+
[subs="verbatim,quotes"]
1570+
----
1571+
// Target all Controllers annotated with @RestController
1572+
@ControllerAdvice(annotations = RestController.class)
1573+
public class ExampleAdvice1 {}
1574+
1575+
// Target all Controllers within specific packages
1576+
@ControllerAdvice("org.example.controllers")
1577+
public class ExampleAdvice2 {}
1578+
1579+
// Target all Controllers assignable to specific classes
1580+
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
1581+
public class ExampleAdvice3 {}
1582+
----
1583+
1584+
Keep in mind the above selectors are evaluated at runtime and may negatively impact
1585+
performance if used extensively. See the
1586+
{api-spring-framework}/web/bind/annotation/ControllerAdvice.html[@ControllerAdvice]
1587+
Javadoc for more details.
1588+
1589+
1590+
12871591

12881592
include::webflux-functional.adoc[leveloffset=+1]
12891593

0 commit comments

Comments
 (0)