@@ -1225,6 +1225,263 @@ Type conversion is applied automatically if the target method parameter type is
1225
1225
`String`. See <<mvc-ann-typeconversion>>.
1226
1226
1227
1227
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
+
1228
1485
1229
1486
[[webflux-ann-initbinder]]
1230
1487
=== Binder Methods
@@ -1284,6 +1541,53 @@ controller-specific ``Formatter``'s:
1284
1541
1285
1542
1286
1543
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
+
1287
1591
1288
1592
include::webflux-functional.adoc[leveloffset=+1]
1289
1593
0 commit comments