From 0d3fb0ee0f4c4ab9166b17168129f9769601d0bb Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Thu, 22 Aug 2019 16:49:12 +0200 Subject: [PATCH] Add Kotlin code snippets to WebMvc refdoc See gh-21778 --- src/docs/asciidoc/web/web-uris.adoc | 169 ++- src/docs/asciidoc/web/webmvc-cors.adoc | 218 ++- src/docs/asciidoc/web/webmvc-view.adoc | 486 ++++--- src/docs/asciidoc/web/webmvc.adoc | 1783 +++++++++++++++++++----- 4 files changed, 2098 insertions(+), 558 deletions(-) diff --git a/src/docs/asciidoc/web/web-uris.adoc b/src/docs/asciidoc/web/web-uris.adoc index 676233d511e..155c9ed1f45 100644 --- a/src/docs/asciidoc/web/web-uris.adoc +++ b/src/docs/asciidoc/web/web-uris.adoc @@ -4,8 +4,8 @@ `UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- UriComponents uriComponents = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") // <1> @@ -21,12 +21,28 @@ <4> Build a `UriComponents`. <5> Expand variables and obtain the `URI`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uriComponents = UriComponentsBuilder + .fromUriString("https://example.com/hotels/{hotel}") // <1> + .queryParam("q", "{q}") // <2> + .encode() // <3> + .build() // <4> + + val uri = uriComponents.expand("Westin", "123").toUri() // <5> +---- +<1> Static factory method with a URI template. +<2> Add or replace URI components. +<3> Request to have the URI template and URI variables encoded. +<4> Build a `UriComponents`. +<5> Expand variables and obtain the `URI`. The preceding example can be consolidated into one chain and shortened with `buildAndExpand`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") @@ -35,28 +51,54 @@ as the following example shows: .buildAndExpand("Westin", "123") .toUri(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uri = UriComponentsBuilder + .fromUriString("https://example.com/hotels/{hotel}") + .queryParam("q", "{q}") + .encode() + .buildAndExpand("Westin", "123") + .toUri() +---- You can shorten it further by going directly to a URI (which implies encoding), as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uri = UriComponentsBuilder + .fromUriString("https://example.com/hotels/{hotel}") + .queryParam("q", "{q}") + .build("Westin", "123") +---- You shorter it further still with a full URI template, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}?q={q}") .build("Westin", "123"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +val uri = UriComponentsBuilder + .fromUriString("https://example.com/hotels/{hotel}?q={q}") + .build("Westin", "123") +---- + @@ -76,39 +118,62 @@ exposes shared configuration options. The following example shows how to configure a `RestTemplate`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; String baseUrl = "https://example.org"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); - factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VARIABLES); + factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES); RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(factory); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode + + val baseUrl = "https://example.org" + val factory = DefaultUriBuilderFactory(baseUrl) + factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES + + val restTemplate = RestTemplate() + restTemplate.uriTemplateHandler = factory +---- The following example configures a `WebClient`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; String baseUrl = "https://example.org"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); - factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VARIABLES); + factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES); WebClient client = WebClient.builder().uriBuilderFactory(factory).build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode + + val baseUrl = "https://example.org" + val factory = DefaultUriBuilderFactory(baseUrl) + factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES + + val client = WebClient.builder().uriBuilderFactory(factory).build() +---- In addition, you can also use `DefaultUriBuilderFactory` directly. It is similar to using `UriComponentsBuilder` but, instead of static factory methods, it is an actual instance that holds configuration and preferences, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- String baseUrl = "https://example.com"; DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl); @@ -117,6 +182,16 @@ that holds configuration and preferences, as the following example shows: .queryParam("q", "{q}") .build("Westin", "123"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val baseUrl = "https://example.com" + val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl) + + val uri = uriBuilderFactory.uriString("/hotels/{hotel}") + .queryParam("q", "{q}") + .build("Westin", "123") +---- @@ -144,10 +219,10 @@ URI variables intentionally contain reserved characters. The following example uses the first option: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") + URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .encode() .buildAndExpand("New York", "foo+bar") @@ -155,24 +230,48 @@ URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") // Result is "/hotel%20list/New%20York?q=foo%2Bbar" ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uri = UriComponentsBuilder.fromPath("/hotel list/{city}") + .queryParam("q", "{q}") + .encode() + .buildAndExpand("New York", "foo+bar") + .toUri() + + // Result is "/hotel%20list/New%20York?q=foo%2Bbar" +---- You can shorten the preceding example by going directly to the URI (which implies encoding), as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") + URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") + .queryParam("q", "{q}") + .build("New York", "foo+bar") +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .build("New York", "foo+bar") ---- You can shorten it further still with a full URI template, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + RI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}") + .build("New York", "foo+bar") ---- -URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}") +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}") .build("New York", "foo+bar") ---- @@ -180,8 +279,8 @@ The `WebClient` and the `RestTemplate` expand and encode URI templates internall the `UriBuilderFactory` strategy. Both can be configured with a custom strategy. as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- String baseUrl = "https://example.com"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl) @@ -194,6 +293,22 @@ as the following example shows: // Customize the WebClient.. WebClient client = WebClient.builder().uriBuilderFactory(factory).build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val baseUrl = "https://example.com" + val factory = DefaultUriBuilderFactory(baseUrl).apply { + encodingMode = EncodingMode.TEMPLATE_AND_VALUES + } + + // Customize the RestTemplate.. + val restTemplate = RestTemplate().apply { + uriTemplateHandler = factory + } + + // Customize the WebClient.. + val client = WebClient.builder().uriBuilderFactory(factory).build() +---- The `DefaultUriBuilderFactory` implementation uses `UriComponentsBuilder` internally to expand and encode URI templates. As a factory, it provides a single place to configure diff --git a/src/docs/asciidoc/web/webmvc-cors.adoc b/src/docs/asciidoc/web/webmvc-cors.adoc index 3415005fb30..ada37bb93d7 100644 --- a/src/docs/asciidoc/web/webmvc-cors.adoc +++ b/src/docs/asciidoc/web/webmvc-cors.adoc @@ -83,24 +83,43 @@ The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`] annotation enables cross-origin requests on annotated controller methods, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@RestController -@RequestMapping("/account") -public class AccountController { - - @CrossOrigin - @GetMapping("/{id}") - public Account retrieve(@PathVariable Long id) { - // ... + @RestController + @RequestMapping("/account") + public class AccountController { + + @CrossOrigin + @GetMapping("/{id}") + public Account retrieve(@PathVariable Long id) { + // ... + } + + @DeleteMapping("/{id}") + public void remove(@PathVariable Long id) { + // ... + } } - - @DeleteMapping("/{id}") - public void remove(@PathVariable Long id) { - // ... +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RestController + @RequestMapping("/account") + class AccountController { + + @CrossOrigin + @GetMapping("/{id}") + fun retrieve(@PathVariable id: Long): Account { + // ... + } + + @DeleteMapping("/{id}") + fun remove(@PathVariable id: Long) { + // ... + } } -} ---- By default, `@CrossOrigin` allows: @@ -118,8 +137,8 @@ should only be used where appropriate. `@CrossOrigin` is supported at the class level, too, and is inherited by all methods, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @CrossOrigin(origins = "https://domain2.com", maxAge = 3600) @RestController @@ -137,29 +156,67 @@ public class AccountController { } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600) + @RestController + @RequestMapping("/account") + class AccountController { + + @GetMapping("/{id}") + fun retrieve(@PathVariable id: Long): Account { + // ... + } + + @DeleteMapping("/{id}") + fun remove(@PathVariable id: Long) { + // ... + } +---- You can use `@CrossOrigin` at both the class level and the method level, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@CrossOrigin(maxAge = 3600) -@RestController -@RequestMapping("/account") -public class AccountController { - - @CrossOrigin("https://domain2.com") - @GetMapping("/{id}") - public Account retrieve(@PathVariable Long id) { - // ... + @CrossOrigin(maxAge = 3600) + @RestController + @RequestMapping("/account") + public class AccountController { + + @CrossOrigin("https://domain2.com") + @GetMapping("/{id}") + public Account retrieve(@PathVariable Long id) { + // ... + } + + @DeleteMapping("/{id}") + public void remove(@PathVariable Long id) { + // ... + } } - - @DeleteMapping("/{id}") - public void remove(@PathVariable Long id) { - // ... +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @CrossOrigin(maxAge = 3600) + @RestController + @RequestMapping("/account") + class AccountController { + + @CrossOrigin("https://domain2.com") + @GetMapping("/{id}") + fun retrieve(@PathVariable id: Long): Account { + // ... + } + + @DeleteMapping("/{id}") + fun remove(@PathVariable id: Long) { + // ... + } } -} ---- @@ -196,26 +253,46 @@ should only be used where appropriate. To enable CORS in the MVC Java config, you can use the `CorsRegistry` callback, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@Configuration -@EnableWebMvc -public class WebConfig implements WebMvcConfigurer { + @Configuration + @EnableWebMvc + public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + + registry.addMapping("/api/**") + .allowedOrigins("https://domain2.com") + .allowedMethods("PUT", "DELETE") + .allowedHeaders("header1", "header2", "header3") + .exposedHeaders("header1", "header2") + .allowCredentials(true).maxAge(3600); + + // Add more mappings... + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { + override fun addCorsMappings(registry: CorsRegistry) { - registry.addMapping("/api/**") - .allowedOrigins("https://domain2.com") - .allowedMethods("PUT", "DELETE") - .allowedHeaders("header1", "header2", "header3") - .exposedHeaders("header1", "header2") - .allowCredentials(true).maxAge(3600); + registry.addMapping("/api/**") + .allowedOrigins("https://domain2.com") + .allowedMethods("PUT", "DELETE") + .allowedHeaders("header1", "header2", "header3") + .exposedHeaders("header1", "header2") + .allowCredentials(true).maxAge(3600) - // Add more mappings... + // Add more mappings... + } } -} ---- @@ -226,8 +303,7 @@ public class WebConfig implements WebMvcConfigurer { To enable CORS in the XML namespace, you can use the `` element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -262,21 +338,39 @@ for CORS. To configure the filter, pass a `CorsConfigurationSource` to its constructor, as the following example shows: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim",role="primary"] +.Java +---- + CorsConfiguration config = new CorsConfiguration(); + + // Possibly... + // config.applyPermitDefaultValues() + + config.setAllowCredentials(true); + config.addAllowedOrigin("https://domain1.com"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + + CorsFilter filter = new CorsFilter(source); +---- +[source,kotlin,indent=0,subs="verbatim",role="secondary"] +.Kotlin ---- -CorsConfiguration config = new CorsConfiguration(); + val config = CorsConfiguration() -// Possibly... -// config.applyPermitDefaultValues() + // Possibly... + // config.applyPermitDefaultValues() -config.setAllowCredentials(true); -config.addAllowedOrigin("https://domain1.com"); -config.addAllowedHeader("*"); -config.addAllowedMethod("*"); + config.allowCredentials = true + config.addAllowedOrigin("https://domain1.com") + config.addAllowedHeader("*") + config.addAllowedMethod("*") -UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); -source.registerCorsConfiguration("/**", config); + val source = UrlBasedCorsConfigurationSource() + source.registerCorsConfiguration("/**", config) -CorsFilter filter = new CorsFilter(source); + val filter = CorsFilter(source) ---- diff --git a/src/docs/asciidoc/web/webmvc-view.adoc b/src/docs/asciidoc/web/webmvc-view.adoc index 71bff8db1c3..db4608f9d70 100644 --- a/src/docs/asciidoc/web/webmvc-view.adoc +++ b/src/docs/asciidoc/web/webmvc-view.adoc @@ -46,33 +46,51 @@ integration for using Spring MVC with FreeMarker templates. The following example shows how to configure FreeMarker as a view technology: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configuration + @EnableWebMvc + public class WebConfig implements WebMvcConfigurer { + + @Override + public void configureViewResolvers(ViewResolverRegistry registry) { + registry.freeMarker(); + } + + // Configure FreeMarker... + + @Bean + public FreeMarkerConfigurer freeMarkerConfigurer() { + FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); + configurer.setTemplateLoaderPath("/WEB-INF/freemarker"); + return configurer; + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- @Configuration @EnableWebMvc - public class WebConfig implements WebMvcConfigurer { + class WebConfig : WebMvcConfigurer { - @Override - public void configureViewResolvers(ViewResolverRegistry registry) { - registry.freemarker(); + override fun configureViewResolvers(registry: ViewResolverRegistry) { + registry.freeMarker() } // Configure FreeMarker... @Bean - public FreeMarkerConfigurer freeMarkerConfigurer() { - FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); - configurer.setTemplateLoaderPath("/WEB-INF/freemarker"); - return configurer; + fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply { + setTemplateLoaderPath("/WEB-INF/freemarker") } } ---- The following example shows how to configure the same in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -89,8 +107,7 @@ The following example shows how to configure the same in XML: Alternatively, you can also declare the `FreeMarkerConfigurer` bean for full control over all properties, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -114,8 +131,7 @@ properties on the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property a `java.util.Properties` object, and the `freemarkerVariables` property requires a `java.util.Map`. The following example shows how to use a `FreeMarkerConfigurer`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -167,8 +183,7 @@ controller, you can use code similar to the next example to bind to field values display error messages for each input field in similar fashion to the JSP equivalent. The following example shows a `personForm` view: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -309,8 +324,7 @@ time, a class name or style attribute. Note that FreeMarker can specify default values for the attributes parameter. The following example shows how to use the `formInput` and `showWErrors` macros: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- <@spring.formInput "command.name"/> <@spring.showErrors "
"/> @@ -322,8 +336,7 @@ occurs through Spring's Validation framework. The generated HTML resembles the following example: -[source,jsp,indent=0] -[subs="verbatim,quotes"] +[source,jsp,indent=0,subs="verbatim,quotes"] ---- Name: @@ -357,8 +370,7 @@ value of 'London' for this field, so no validation is necessary. When the form i rendered, the entire list of cities to choose from is supplied as reference data in the model under the name 'cityMap'. The following listing shows the example: -[source,jsp,indent=0] -[subs="verbatim,quotes"] +[source,jsp,indent=0,subs="verbatim,quotes"] ---- ... Town: @@ -372,8 +384,7 @@ keys are what the form actually submits as `POST` request parameters. The map va labels that the user sees. In the preceding example, given a list of three well known cities and a default value in the form backing object, the HTML resembles the following: -[source,jsp,indent=0] -[subs="verbatim,quotes"] +[source,jsp,indent=0,subs="verbatim,quotes"] ---- Town: London @@ -384,26 +395,37 @@ and a default value in the form backing object, the HTML resembles the following If your application expects to handle cities by internal codes (for example), you can create the map of codes with suitable keys, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - protected Map referenceData(HttpServletRequest request) throws Exception { + protected Map referenceData(HttpServletRequest request) throws Exception { Map cityMap = new LinkedHashMap<>(); cityMap.put("LDN", "London"); cityMap.put("PRS", "Paris"); cityMap.put("NYC", "New York"); - Map model = new HashMap<>(); + Map model = new HashMap<>(); model.put("cityMap", cityMap); return model; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + protected fun referenceData(request: HttpServletRequest): Map { + val cityMap = linkedMapOf( + "LDN" to "London", + "PRS" to "Paris", + "NYC" to "New York" + ) + return hashMapOf("cityMap" to cityMap) + } +---- The code now produces output where the radio values are the relevant codes, but the user still sees the more user-friendly city names, as follows: -[source,jsp,indent=0] -[subs="verbatim,quotes"] +[source,jsp,indent=0,subs="verbatim,quotes"] ---- Town: London @@ -426,8 +448,7 @@ template processing to provide different behavior for different fields in your f To switch to XHTML compliance for your tags, specify a value of `true` for a model or context variable named `xhtmlCompliant`, as the following example shows: -[source,jsp,indent=0] -[subs="verbatim,quotes"] +[source,jsp,indent=0,subs="verbatim,quotes"] ---- <#-- for FreeMarker --> <#assign xhtmlCompliant = true> @@ -438,8 +459,7 @@ compliant. In similar fashion, you can specify HTML escaping per field, as the following example shows: -[source,jsp,indent=0] -[subs="verbatim,quotes"] +[source,jsp,indent=0,subs="verbatim,quotes"] ---- <#-- until this point, default HTML escaping is used --> @@ -471,8 +491,8 @@ NOTE: The Groovy Markup Template engine requires Groovy 2.3.1+. The following example shows how to configure the Groovy Markup Template Engine: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -493,11 +513,29 @@ The following example shows how to configure the Groovy Markup Template Engine: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configureViewResolvers(registry: ViewResolverRegistry) { + registry.groovy() + } + + // Configure the Groovy Markup Template Engine... + + @Bean + fun groovyMarkupConfigurer() = GroovyMarkupConfigurer().apply { + resourceLoaderPath = "/WEB-INF/" + } + } +---- The following example shows how to configure the same in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -517,8 +555,7 @@ The following example shows how to configure the same in XML: Unlike traditional template engines, Groovy Markup relies on a DSL that uses a builder syntax. The following example shows a sample template for an HTML page: -[source,groovy,indent=0] -[subs="verbatim,quotes"] +[source,groovy,indent=0,subs="verbatim,quotes"] ---- yieldUnescaped '' html(lang:'en') { @@ -589,8 +626,8 @@ You can declare a `ScriptTemplateConfigurer` bean to specify the script engine t the script files to load, what function to call to render templates, and so on. The following example uses Mustache templates and the Nashorn JavaScript engine: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -612,11 +649,30 @@ The following example uses Mustache templates and the Nashorn JavaScript engine: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configureViewResolvers(registry: ViewResolverRegistry) { + registry.scriptTemplate() + } + + @Bean + fun configurer() = ScriptTemplateConfigurer().apply { + engineName = "nashorn" + setScripts("mustache.js") + renderObject = "Mustache" + renderFunction = "render" + } + } +---- The following example shows the same arrangement in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -631,25 +687,38 @@ The following example shows the same arrangement in XML: The controller would look no different for the Java and XML configurations, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller public class SampleController { @GetMapping("/sample") public String test(Model model) { - model.addObject("title", "Sample title"); - model.addObject("body", "Sample body"); + model.addAttribute("title", "Sample title"); + model.addAttribute("body", "Sample body"); return "template"; } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Controller + class SampleController { + + @GetMapping("/sample") + fun test(model: Model): String { + model["title"] = "Sample title" + model["body"] = "Sample body" + return "template" + } + } +---- The following example shows the Mustache template: -[source,html,indent=0] -[subs="verbatim,quotes"] +[source,html,indent=0,subs="verbatim,quotes"] ---- @@ -680,8 +749,8 @@ browser facilities that are not available in the server-side script engine. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -703,6 +772,26 @@ The following example shows how to do so: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configureViewResolvers(registry: ViewResolverRegistry) { + registry.scriptTemplate() + } + + @Bean + fun configurer() = ScriptTemplateConfigurer().apply { + engineName = "nashorn" + setScripts("polyfill.js", "handlebars.js", "render.js") + renderFunction = "render" + isSharedEngine = false + } + } +---- NOTE: Setting the `sharedEngine` property to `false` is required when you use non-thread-safe script engines with templating libraries not designed for concurrency, such as Handlebars or @@ -711,8 +800,7 @@ to https://bugs.openjdk.java.net/browse/JDK-8076099[this bug]. `polyfill.js` defines only the `window` object needed by Handlebars to run properly, as follows: -[source,javascript,indent=0] -[subs="verbatim,quotes"] +[source,javascript,indent=0,subs="verbatim,quotes"] ---- var window = {}; ---- @@ -722,8 +810,7 @@ implementation should also store any reused cached templates or pre-compiled tem You can do so on the script side (and handle any customization you need -- managing template engine configuration, for example). The following example shows how to do so: -[source,javascript,indent=0] -[subs="verbatim,quotes"] +[source,javascript,indent=0,subs="verbatim,quotes"] ---- function render(template, model) { var compiledTemplate = Handlebars.compile(template); @@ -756,8 +843,7 @@ When developing with JSPs, you can declare a `InternalResourceViewResolver` or a mapped to a class and a URL. With a `ResourceBundleViewResolver`, you can mix different types of views by using only one resolver, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -776,8 +862,7 @@ different types of views by using only one resolver, as the following example sh encourage placing your JSP files in a directory under the `'WEB-INF'` directory so there can be no direct access by clients. -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -858,8 +943,7 @@ such as `firstName` and `lastName`. We can use it as the form-backing object of form controller, which returns `form.jsp`. The following example shows what `form.jsp` could look like: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -886,8 +970,7 @@ how inner tags are used with the `form` tag. The following listing shows the generated HTML, which looks like a standard form: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ----
@@ -913,8 +996,7 @@ The preceding JSP assumes that the variable name of the form-backing object is (definitely a best practice), you can bind the form to the named variable, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ----
@@ -952,8 +1034,8 @@ This tag renders an HTML `input` tag with the `type` set to `checkbox`. Assume that our `User` has preferences such as newsletter subscription and a list of hobbies. The following example shows the `Preferences` class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Preferences { @@ -986,11 +1068,19 @@ hobbies. The following example shows the `Preferences` class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class Preferences( + var receiveNewsletter: Boolean, + var interests: StringArray, + var favouriteWord: String + ) +---- The corresponding `form.jsp` could then resemble the following: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ----
@@ -1035,8 +1125,7 @@ There are three approaches to the `checkbox` tag, which should meet all your che Note that, regardless of the approach, the same HTML structure is generated. The following HTML snippet defines some checkboxes: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1074,8 +1163,7 @@ the available options in the `items` property. Typically, the bound property is collection so that it can hold multiple values selected by the user. The following example shows a JSP that uses this tag: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ----
Interests:
@@ -1106,8 +1194,7 @@ This tag renders an HTML `input` element with the `type` set to `radio`. A typical usage pattern involves multiple tag instances bound to the same property but with different values, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1132,8 +1219,7 @@ used as the value and the map entry's value are used as the label to be displaye You can also use a custom object where you can provide the property names for the value by using `itemValue` and the label by using `itemLabel`, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1147,8 +1233,7 @@ by using `itemValue` and the label by using `itemLabel`, as the following exampl This tag renders an HTML `input` tag with the type set to `password` with the bound value. -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1162,8 +1247,7 @@ Note that, by default, the password value is not shown. If you do want the password value to be shown, you can set the value of the `showPassword` attribute to `true`, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1182,8 +1266,7 @@ option as well as the use of nested `option` and `options` tags. Assume that a `User` has a list of skills. The corresponding HTML could be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1194,8 +1277,7 @@ Assume that a `User` has a list of skills. The corresponding HTML could be as fo If the `User's` skill are in Herbology, the HTML source of the 'Skills' row could be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1216,8 +1298,7 @@ as follows: This tag renders an HTML `option` element. It sets `selected`, based on the bound value. The following HTML shows typical output for it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1235,8 +1316,7 @@ value. The following HTML shows typical output for it: If the `User's` house was in Gryffindor, the HTML source of the 'House' row would be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1259,8 +1339,7 @@ as follows: This tag renders a list of HTML `option` elements. It sets the `selected` attribute, based on the bound value. The following HTML shows typical output for it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1275,8 +1354,7 @@ based on the bound value. The following HTML shows typical output for it: If the `User` lived in the UK, the HTML source of the 'Country' row would be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1311,8 +1389,7 @@ the item label property applies to the map value. This tag renders an HTML `textarea` element. The following HTML shows typical output for it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1329,16 +1406,14 @@ This tag renders an HTML `input` tag with the `type` set to `hidden` with the bo an unbound hidden value, use the HTML `input` tag with the `type` set to `hidden`. The following HTML shows typical output for it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- If we choose to submit the `house` value as a hidden one, the HTML would be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1356,8 +1431,8 @@ Assume that we want to display all error messages for the `firstName` and `lastN fields once we submit the form. We have a validator for instances of the `User` class called `UserValidator`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class UserValidator implements Validator { @@ -1371,11 +1446,25 @@ called `UserValidator`, as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class UserValidator : Validator { + + override fun supports(candidate: Class<*>): Boolean { + return User::class.java.isAssignableFrom(candidate) + } + + override fun validate(obj: Any, errors: Errors) { + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.") + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.") + } + } +---- The `form.jsp` could be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ----
Sex:
Sex:
Password:
Password:
Skills:
Skills:
House:
House:
Country:
Country:
Notes:
@@ -1404,8 +1493,7 @@ The `form.jsp` could be as follows: If we submit a form with empty values in the `firstName` and `lastName` fields, the HTML would be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ----
@@ -1441,8 +1529,7 @@ shows that the `errors` tag also supports some basic wildcarding functionality. The following example displays a list of errors at the top of the page, followed by field-specific errors next to the fields: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1468,8 +1555,7 @@ field-specific errors next to the fields: The HTML would be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- Field is required.
Field is required.
@@ -1522,8 +1608,7 @@ request. To support HTTP method conversion, the Spring MVC form tag was updated to support setting the HTTP method. For example, the following snippet comes from the Pet Clinic sample: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ----

@@ -1534,8 +1619,7 @@ The preceding example performs an HTTP POST, with the "`real`" DELETE method hid a request parameter. It is picked up by the `HiddenHttpMethodFilter`, which is defined in web.xml, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- httpMethodFilter @@ -1550,8 +1634,8 @@ web.xml, as the following example shows: The following example shows the corresponding `@Controller` method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RequestMapping(method = RequestMethod.DELETE) public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { @@ -1559,7 +1643,15 @@ The following example shows the corresponding `@Controller` method: return "redirect:/owners/" + ownerId; } ---- - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RequestMapping(method = [RequestMethod.DELETE]) + fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String { + clinic.deletePet(petId) + return "redirect:/owners/$ownerId" + } +---- [[mvc-view-jsp-formtaglib-html5]] ==== HTML5 Tags @@ -1603,8 +1695,7 @@ To be able to use Tiles, you have to configure it by using files that contain de https://tiles.apache.org[]). In Spring, this is done by using the `TilesConfigurer`. The following example `ApplicationContext` configuration shows how to do so: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1630,8 +1721,7 @@ implementations, the `UrlBasedViewResolver` and the `ResourceBundleViewResolver` You can specify locale-specific Tiles definitions by adding an underscore and then the locale, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1657,8 +1747,7 @@ them otherwise in the file names for Tiles definitions. The `UrlBasedViewResolver` instantiates the given `viewClass` for each view it has to resolve. The following bean defines a `UrlBasedViewResolver`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1674,16 +1763,14 @@ view names and view classes that the resolver can use. The following example sho definition for a `ResourceBundleViewResolver` and the corresponding view names and view classes (taken from the Pet Clinic sample): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- ... welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView @@ -1725,8 +1812,7 @@ configuration, scoped beans, and so on. Note that you need to define one Spring for each preparer name (as used in your Tiles definitions). The following example shows how to define a `SpringBeanPreparerFactory` property on a `TilesConfigurer` bean: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1761,8 +1847,8 @@ package `org.springframework.web.servlet.view.feed`. optionally override the `buildFeedMetadata()` method (the default implementation is empty). The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SampleContentAtomView extends AbstractAtomFeedView { @@ -1777,14 +1863,29 @@ empty). The following example shows how to do so: HttpServletRequest request, HttpServletResponse response) throws Exception { // implementation omitted } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SampleContentAtomView : AbstractAtomFeedView() { + + override fun buildFeedMetadata(model: Map, + feed: Feed, request: HttpServletRequest) { + // implementation omitted + } + override fun buildFeedEntries(model: Map, + request: HttpServletRequest, response: HttpServletResponse): List { + // implementation omitted + } } ---- Similar requirements apply for implementing `AbstractRssFeedView`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SampleContentRssView extends AbstractRssFeedView { @@ -1801,6 +1902,24 @@ Similar requirements apply for implementing `AbstractRssFeedView`, as the follow } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SampleContentRssView : AbstractRssFeedView() { + + override fun buildFeedMetadata(model: Map, + feed: Channel, request: HttpServletRequest) { + // implementation omitted + } + + override fun buildFeedItems(model: Map, + request: HttpServletRequest, response: HttpServletResponse): List { + // implementation omitted + } + } +---- + + The `buildFeedItems()` and `buildFeedEntries()` methods pass in the HTTP request, in case you need to access the Locale. The HTTP response is passed in only for the setting of @@ -1847,8 +1966,8 @@ A simple PDF view for a word list could extend `org.springframework.web.servlet.view.document.AbstractPdfView` and implement the `buildPdfDocument()` method, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class PdfWordList extends AbstractPdfView { @@ -1862,6 +1981,21 @@ A simple PDF view for a word list could extend } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class PdfWordList : AbstractPdfView() { + + override fun buildPdfDocument(model: Map, doc: Document, writer: PdfWriter, + request: HttpServletRequest, response: HttpServletResponse) { + + val words = model["wordList"] as List + for (word in words) { + doc.add(Paragraph(word)) + } + } + } +---- A controller can return such a view either from an external view definition (referencing it by name) or as a `View` instance from the handler method. @@ -1965,24 +2099,38 @@ Configuration is standard for a simple Spring web application: The MVC configura has to define an `XsltViewResolver` bean and regular MVC annotation configuration. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@EnableWebMvc -@ComponentScan -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Bean - public XsltViewResolver xsltViewResolver() { - XsltViewResolver viewResolver = new XsltViewResolver(); - viewResolver.setPrefix("/WEB-INF/xsl/"); - viewResolver.setSuffix(".xslt"); - return viewResolver; + @EnableWebMvc + @ComponentScan + @Configuration + public class WebConfig implements WebMvcConfigurer { + + @Bean + public XsltViewResolver xsltViewResolver() { + XsltViewResolver viewResolver = new XsltViewResolver(); + viewResolver.setPrefix("/WEB-INF/xsl/"); + viewResolver.setSuffix(".xslt"); + return viewResolver; + } } -} ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @EnableWebMvc + @ComponentScan + @Configuration + class WebConfig : WebMvcConfigurer { + @Bean + fun xsltViewResolver() = XsltViewResolver().apply { + setPrefix("/WEB-INF/xsl/") + setSuffix(".xslt") + } + } +---- [[mvc-view-xslt-controllercode]] @@ -1993,8 +2141,8 @@ We also need a Controller that encapsulates our word-generation logic. The controller logic is encapsulated in a `@Controller` class, with the handler method being defined as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller public class XsltController { @@ -2017,6 +2165,32 @@ handler method being defined as follows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.ui.set + + @Controller + class XsltController { + + @RequestMapping("/") + fun home(model: Model): String { + val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() + val root = document.createElement("wordList") + + val words = listOf("Hello", "Spring", "Framework") + for (word in words) { + val wordNode = document.createElement("word") + val textNode = document.createTextNode(word) + wordNode.appendChild(textNode) + root.appendChild(wordNode) + } + + model["wordList"] = root + return "home" + } + } +---- So far, we have only created a DOM document and added it to the Model map. Note that you can also load an XML file as a `Resource` and use it instead of a custom DOM document. @@ -2039,8 +2213,7 @@ and end with an `xslt` file extension. The following example shows an XSLT transform: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2068,8 +2241,7 @@ The following example shows an XSLT transform: The preceding transform is rendered as the following HTML: -[source,html,indent=0] -[subs="verbatim,quotes"] +[source,html,indent=0,subs="verbatim,quotes"] ---- diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 4571dba89af..5516346ab5b 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -40,8 +40,8 @@ The following example of the Java configuration registers and initializes the `DispatcherServlet`, which is auto-detected by the Servlet container (see <>): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyWebApplicationInitializer implements WebApplicationInitializer { @@ -61,6 +61,26 @@ the `DispatcherServlet`, which is auto-detected by the Servlet container } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyWebApplicationInitializer : WebApplicationInitializer { + + override fun onStartup(servletCxt: ServletContext) { + + // Load Spring web application configuration + val ac = AnnotationConfigWebApplicationContext() + ac.register(AppConfig::class.java) + ac.refresh() + + // Create and register the DispatcherServlet + val servlet = DispatcherServlet(ac) + val registration = servletCxt.addServlet("app", servlet) + registration.setLoadOnStartup(1) + registration.addMapping("/app/*") + } + } +---- NOTE: In addition to using the ServletContext API directly, you can also extend `AbstractAnnotationConfigDispatcherServletInitializer` and override specific methods @@ -68,8 +88,7 @@ NOTE: In addition to using the ServletContext API directly, you can also extend The following example of `web.xml` configuration registers and initializes the `DispatcherServlet`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -135,8 +154,8 @@ image::images/mvc-context-hierarchy.png[] The following example configures a `WebApplicationContext` hierarchy: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -156,14 +175,31 @@ The following example configures a `WebApplicationContext` hierarchy: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { + + override fun getRootConfigClasses(): Array> { + return arrayOf(RootConfig::class.java) + } + + override fun getServletConfigClasses(): Array> { + return arrayOf(App1Config::class.java) + } + + override fun getServletMappings(): Array { + return arrayOf("/app1/*") + } + } +---- TIP: If an application context hierarchy is not required, applications can return all configuration through `getRootConfigClasses()` and `null` from `getServletConfigClasses()`. The following example shows the `web.xml` equivalent: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -285,8 +321,8 @@ In a Servlet 3.0+ environment, you have the option of configuring the Servlet co programmatically as an alternative or in combination with a `web.xml` file. The following example registers a `DispatcherServlet`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import org.springframework.web.WebApplicationInitializer; @@ -303,6 +339,24 @@ example registers a `DispatcherServlet`: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.web.WebApplicationInitializer + + class MyWebApplicationInitializer : WebApplicationInitializer { + + override fun onStartup(container: ServletContext) { + val appContext = XmlWebApplicationContext() + appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml") + + val registration = container.addServlet("dispatcher", DispatcherServlet(appContext)) + registration.setLoadOnStartup(1) + registration.addMapping("/") + } + } +---- + `WebApplicationInitializer` is an interface provided by Spring MVC that ensures your implementation is detected and automatically used to initialize any Servlet 3 container. @@ -314,8 +368,8 @@ location of the `DispatcherServlet` configuration. This is recommended for applications that use Java-based Spring configuration, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -335,12 +389,30 @@ following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { + + override fun getRootConfigClasses(): Array>? { + return null + } + + override fun getServletConfigClasses(): Array>? { + return arrayOf(MyWebConfig::class.java) + } + + override fun getServletMappings(): Array { + return arrayOf("/") + } + } +---- If you use XML-based Spring configuration, you should extend directly from `AbstractDispatcherServletInitializer`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { @@ -362,13 +434,33 @@ If you use XML-based Spring configuration, you should extend directly from } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyWebAppInitializer : AbstractDispatcherServletInitializer() { + + override fun createRootApplicationContext(): WebApplicationContext? { + return null + } + + override fun createServletApplicationContext(): WebApplicationContext { + return XmlWebApplicationContext().apply { + setConfigLocation("/WEB-INF/spring/dispatcher-config.xml") + } + } + + override fun getServletMappings(): Array { + return arrayOf("/") + } + } +---- `AbstractDispatcherServletInitializer` also provides a convenient way to add `Filter` instances and have them be automatically mapped to the `DispatcherServlet`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { @@ -381,6 +473,18 @@ following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyWebAppInitializer : AbstractDispatcherServletInitializer() { + + // ... + + override fun getServletFilters(): Array { + return arrayOf(HiddenHttpMethodFilter(), CharacterEncodingFilter()) + } + } +---- Each filter is added with a default name based on its concrete type and automatically mapped to the `DispatcherServlet`. @@ -566,8 +670,7 @@ Servlet containers can render a default error page in HTML. To customize the def error page of the container, you can declare an error page mapping in `web.xml`. The following example shows how to do so: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- /error @@ -580,8 +683,8 @@ Servlet container makes an ERROR dispatch within the container to the configured to a `@Controller`, which could be implemented to return an error view name with a model or to render a JSON response, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RestController public class ErrorController { @@ -595,6 +698,21 @@ or to render a JSON response, as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RestController + class ErrorController { + + @RequestMapping(path = ["/error"]) + fun handle(request: HttpServletRequest): Map { + val map = HashMap() + map["status"] = request.getAttribute("javax.servlet.error.status_code") + map["reason"] = request.getAttribute("javax.servlet.error.message") + return map + } + } +---- TIP: The Servlet API does not provide a way to create error page mappings in Java. You can, however, use both a `WebApplicationInitializer` and a minimal `web.xml`. @@ -793,8 +911,7 @@ This locale resolver inspects a `Cookie` that might exist on the client to see i properties of this locale resolver, you can specify the name of the cookie as well as the maximum age. The following example defines a `CookieLocaleResolver`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -856,8 +973,7 @@ that contain a parameter named `siteLanguage` now changes the locale. So, for ex a request for the URL, `https://www.sf.net/home.view?siteLanguage=nl`, changes the site language to Dutch. The following example shows how to intercept the locale: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -907,8 +1023,7 @@ The web application context automatically detects a bean with that name and uses When you use the `ResourceBundleThemeSource`, a theme is defined in a simple properties file. The properties file lists the resources that make up the theme, as the following example shows: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- styleSheet=/themes/cool/style.css background=/themes/cool/img/coolBg.jpg @@ -919,8 +1034,7 @@ code. For a JSP, you typically do this using the `spring:theme` custom tag, whic very similar to the `spring:message` tag. The following JSP fragment uses the theme defined in the previous example to customize the look and feel: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> @@ -1009,8 +1123,8 @@ To do so: The following example shows how to set a `MultipartConfigElement` on the Servlet registration: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -1025,6 +1139,21 @@ The following example shows how to set a `MultipartConfigElement` on the Servlet } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class AppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { + + // ... + + override fun customizeRegistration(registration: ServletRegistration.Dynamic) { + + // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold + registration.setMultipartConfig(MultipartConfigElement("/tmp")) + } + + } +---- Once the Servlet 3.0 configuration is in place, you can add a bean of type `StandardServletMultipartResolver` with a name of `multipartResolver`. @@ -1057,8 +1186,8 @@ through the `enableLoggingRequestDetails` property on `DispatcherServlet`. The following example shows how to do so by using Java configuration: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -1079,12 +1208,34 @@ public class MyInitializer } @Override - protected void customizeRegistration(Dynamic registration) { + protected void customizeRegistration(ServletRegistration.Dynamic registration) { registration.setInitParameter("enableLoggingRequestDetails", "true"); } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { + + override fun getRootConfigClasses(): Array>? { + return ... + } + + override fun getServletConfigClasses(): Array>? { + return ... + } + + override fun getServletMappings(): Array { + return ... + } + + override fun customizeRegistration(registration: ServletRegistration.Dynamic) { + registration.setInitParameter("enableLoggingRequestDetails", "true") + } + } +---- @@ -1181,8 +1332,8 @@ exception handling, and more. Annotated controllers have flexible method signatu do not have to extend base classes nor implement specific interfaces. The following example shows a controller defined by annotations: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller public class HelloController { @@ -1194,6 +1345,21 @@ The following example shows a controller defined by annotations: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.ui.set + + @Controller + class HelloController { + + @GetMapping("/hello") + fun handle(model: Model): String { + model["message"] = "Hello World!" + return "index" + } + } +---- In the preceding example, the method accepts a `Model` and returns a view name as a `String`, but many other options exist and are explained later in this chapter. @@ -1216,8 +1382,8 @@ annotated class, indicating its role as a web component. To enable auto-detection of such `@Controller` beans, you can add component scanning to your Java configuration, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @ComponentScan("org.example.web") @@ -1226,11 +1392,20 @@ your Java configuration, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @ComponentScan("org.example.web") + class WebConfig { + + // ... + } +---- The following example shows the XML configuration equivalent of the preceding example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- + @PostMapping(path = "/pets", consumes = "application/json") // <1> public void addPet(@RequestBody Pet pet) { // ... } ---- <1> Using a `consumes` attribute to narrow the mapping by the content type. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/pets", consumes = ["application/json"]) // <1> + fun addPet(@RequestBody pet: Pet) { + // ... + } +---- +<1> Using a `consumes` attribute to narrow the mapping by the content type. + The `consumes` attribute also supports negation expressions -- for example, `!text/plain` means any content type other than `text/plain`. @@ -1501,10 +1736,10 @@ TIP: `MediaType` provides constants for commonly used media types, such as You can narrow the request mapping based on the `Accept` request header and the list of content types that a controller method produces, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @GetMapping(path = "/pets/{petId}", produces = "application/json") <1> + @GetMapping(path = "/pets/{petId}", produces = "application/json") // <1> @ResponseBody public Pet getPet(@PathVariable String petId) { // ... @@ -1512,6 +1747,17 @@ content types that a controller method produces, as the following example shows: ---- <1> Using a `produces` attribute to narrow the mapping by the content type. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/pets/{petId}", produces = ["application/json"]) // <1> + @ResponseBody + fun getPet(@PathVariable petId: String): Pet { + // ... + } +---- +<1> Using a `produces` attribute to narrow the mapping by the content type. + The media type can specify a character set. Negated expressions are supported -- for example, `!text/plain` means any content type other than "text/plain". @@ -1531,28 +1777,47 @@ You can narrow request mappings based on request parameter conditions. You can t presence of a request parameter (`myParam`), for the absence of one (`!myParam`), or for a specific value (`myParam=myValue`). The following example shows how to test for a specific value: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") <1> + @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") // <1> public void findPet(@PathVariable String petId) { // ... } ---- <1> Testing whether `myParam` equals `myValue`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/pets/{petId}", params = ["myParam=myValue"]) // <1> + fun findPet(@PathVariable petId: String) { + // ... + } +---- +<1> Testing whether `myParam` equals `myValue`. + You can also use the same with request header conditions, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @GetMapping(path = "/pets", headers = "myHeader=myValue") <1> + @GetMapping(path = "/pets", headers = "myHeader=myValue") // <1> public void findPet(@PathVariable String petId) { // ... } ---- <1> Testing whether `myHeader` equals `myValue`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/pets", headers = ["myHeader=myValue"]) // <1> + fun findPet(@PathVariable petId: String) { + // ... + } +---- + TIP: You can match `Content-Type` and `Accept` with the headers condition, but it is better to use <> and <> instead. @@ -1613,27 +1878,44 @@ You can programmatically register handler methods, which you can use for dynamic registrations or for advanced cases, such as different instances of the same handler under different URLs. The following example registers a handler method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@Configuration -public class MyConfig { + @Configuration + public class MyConfig { - @Autowired - public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) <1> - throws NoSuchMethodException { + @Autowired + public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) // <1> + throws NoSuchMethodException { - RequestMappingInfo info = RequestMappingInfo - .paths("/user/{id}").methods(RequestMethod.GET).build(); <2> + RequestMappingInfo info = RequestMappingInfo + .paths("/user/{id}").methods(RequestMethod.GET).build(); // <2> - Method method = UserHandler.class.getMethod("getUser", Long.class); <3> + Method method = UserHandler.class.getMethod("getUser", Long.class); // <3> - mapping.registerMapping(info, handler, method); <4> + mapping.registerMapping(info, handler, method); // <4> + } } +---- +<1> Inject the target handler and the handler mapping for controllers. +<2> Prepare the request mapping meta data. +<3> Get the handler method. +<4> Add the registration. -} +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- + @Configuration + class MyConfig { + @Autowired + fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { // <1> + val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build() // <2> + val method = UserHandler::class.java.getMethod("getUser", Long::class.java) // <3> + mapping.registerMapping(info, handler, method) // <4> + } + } +---- <1> Inject the target handler and the handler mapping for controllers. <2> Prepare the request mapping meta data. <3> Get the handler method. @@ -1919,8 +2201,8 @@ method must use a URI variable to mask that variable content and ensure the requ be matched successfully independent of matrix variable order and presence. The following example uses a matrix variable: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // GET /pets/42;q=11;r=22 @@ -1931,13 +2213,25 @@ The following example uses a matrix variable: // q == 11 } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // GET /pets/42;q=11;r=22 + + @GetMapping("/pets/{petId}") + fun findPet(@PathVariable petId: String, @MatrixVariable q: Int) { + + // petId == 42 + // q == 11 + } +---- Given that all path segments may contain matrix variables, you may sometimes need to disambiguate which path variable the matrix variable is expected to be in. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // GET /owners/42;q=11/pets/21;q=22 @@ -1950,12 +2244,26 @@ The following example shows how to do so: // q2 == 22 } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // GET /owners/42;q=11/pets/21;q=22 + + @GetMapping("/owners/{ownerId}/pets/{petId}") + fun findPet( + @MatrixVariable(name = "q", pathVar = "ownerId") q1: Int, + @MatrixVariable(name = "q", pathVar = "petId") q2: Int) { + + // q1 == 11 + // q2 == 22 + } +---- A matrix variable may be defined as optional and a default value specified, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // GET /pets/42 @@ -1965,11 +2273,22 @@ following example shows: // q == 1 } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // GET /pets/42 + + @GetMapping("/pets/{petId}") + fun findPet(@MatrixVariable(required = false, defaultValue = "1") q: Int) { + + // q == 1 + } +---- To get all matrix variables, you can use a `MultiValueMap`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @@ -1982,6 +2301,20 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa // petMatrixVars: ["q" : 22, "s" : 23] } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // GET /owners/42;q=11;r=12/pets/21;q=22;s=23 + + @GetMapping("/owners/{ownerId}/pets/{petId}") + fun findPet( + @MatrixVariable matrixVars: MultiValueMap, + @MatrixVariable(pathVar="petId") petMatrixVars: MultiValueMap) { + + // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] + // petMatrixVars: ["q" : 22, "s" : 23] + } +---- Note that you need to enable the use of matrix variables. In the MVC Java configuration, you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through @@ -1998,8 +2331,8 @@ query parameters or form data) to a method argument in a controller. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller @RequestMapping("/pets") @@ -2020,6 +2353,30 @@ The following example shows how to do so: ---- <1> Using `@RequestParam` to bind `petId`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.ui.set + + @Controller + @RequestMapping("/pets") + class EditPetForm { + + // ... + + @GetMapping + fun setupForm(@RequestParam("petId") petId: Int, model: Model): String { // <1> + val pet = this.clinic.loadPet(petId); + model["pet"] = pet + return "petForm" + } + + // ... + + } +---- +<1> Using `@RequestParam` to bind `petId`. + By default, method parameters that use this annotation are required, but you can specify that a method parameter is optional by setting the `@RequestParam` annotation's `required` flag to `false` or by declaring the argument with an `java.util.Optional` wrapper. @@ -2063,13 +2420,26 @@ Keep-Alive 300 The following example gets the value of the `Accept-Encoding` and `Keep-Alive` headers: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/demo") public void handle( - @RequestHeader("Accept-Encoding") String encoding, <1> - @RequestHeader("Keep-Alive") long keepAlive) { <2> + @RequestHeader("Accept-Encoding") String encoding, // <1> + @RequestHeader("Keep-Alive") long keepAlive) { // <2> + //... + } +---- +<1> Get the value of the `Accept-Encoding` header. +<2> Get the value of the `Keep-Alive` header. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/demo") + fun handle( + @RequestHeader("Accept-Encoding") encoding: String, // <1> + @RequestHeader("Keep-Alive") keepAlive: Long) { // <2> //... } ---- @@ -2098,16 +2468,15 @@ in a controller. Consider a request with the following cookie: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 ---- The following example shows how to get the cookie value: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/demo") public void handle(@CookieValue("JSESSIONID") String cookie) { <1> @@ -2116,6 +2485,16 @@ The following example shows how to get the cookie value: ---- <1> Get the value of the `JSESSIONID` cookie. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/demo") + fun handle(@CookieValue("JSESSIONID") cookie: String) { // <1> + //... + } +---- +<1> Get the value of the `JSESSIONID` cookie. + If the target method parameter type is not `String`, type conversion is applied automatically. See <>. @@ -2130,14 +2509,22 @@ values from HTTP Servlet request parameters whose names match to field names. Th to as data binding, and it saves you from having to deal with parsing and converting individual query parameters and form fields. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute Pet pet) { } <1> ---- <1> Bind an instance of `Pet`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +@PostMapping("/owners/{ownerId}/pets/{petId}/edit") +fun processSubmit(@ModelAttribute pet: Pet): String { } // <1> +---- +<1> Bind an instance of `Pet`. + The `Pet` instance above is resolved as follows: * From the model if already added by using <>. @@ -2154,14 +2541,22 @@ with a URI path variable convention. In the following example, the model attribu `account`, matches the URI path variable, `account`, and the `Account` is loaded by passing the `String` account number through a registered `Converter`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PutMapping("/accounts/{account}") public String save(@ModelAttribute("account") Account account) { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PutMapping("/accounts/{account}") + fun save(@ModelAttribute("account") account: Account): String { + // ... + } +---- After the model attribute instance is obtained, data binding is applied. The `WebDataBinder` class matches Servlet request parameter names (query parameters and form @@ -2174,11 +2569,11 @@ Data binding can result in errors. By default, a `BindException` is raised. Howe for such errors in the controller method, you can add a `BindingResult` argument immediately next to the `@ModelAttribute`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") - public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { <1> + public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { // <1> if (result.hasErrors()) { return "petForm"; } @@ -2187,12 +2582,25 @@ to the `@ModelAttribute`, as the following example shows: ---- <1> Adding a `BindingResult` next to the `@ModelAttribute`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/owners/{ownerId}/pets/{petId}/edit") + fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1> + if (result.hasErrors()) { + return "petForm" + } + // ... + } +---- +<1> Adding a `BindingResult` next to the `@ModelAttribute`. + In some cases, you may want access to a model attribute without data binding. For such cases, you can inject the `Model` into the controller and access it directly or, alternatively, set `@ModelAttribute(binding=false)`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ModelAttribute public AccountForm setUpForm() { @@ -2206,7 +2614,28 @@ alternatively, set `@ModelAttribute(binding=false)`, as the following example sh @PostMapping("update") public String update(@Valid AccountForm form, BindingResult result, - @ModelAttribute(binding=false) Account account) { <1> + @ModelAttribute(binding=false) Account account) { // <1> + // ... + } +---- +<1> Setting `@ModelAttribute(binding=false)`. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ModelAttribute + fun setUpForm(): AccountForm { + return AccountForm() + } + + @ModelAttribute + fun findAccount(@PathVariable accountId: String): Account { + return accountRepository.findOne(accountId) + } + + @PostMapping("update") + fun update(@Valid form: AccountForm, result: BindingResult, + @ModelAttribute(binding = false) account: Account): String { // <1> // ... } ---- @@ -2217,18 +2646,30 @@ You can automatically apply validation after data binding by adding the <> and <>). The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @PostMapping("/owners/{ownerId}/pets/{petId}/edit") + public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { // <1> + if (result.hasErrors()) { + return "petForm"; + } + // ... + } +---- +<1> Validate the `Pet` instance. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") - public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { <1> + fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1> if (result.hasErrors()) { - return "petForm"; + return "petForm" } // ... } ---- -<1> Validate the `Pet` instance. Note that using `@ModelAttribute` is optional (for example, to set its attributes). By default, any argument that is not a simple value type (as determined by @@ -2249,11 +2690,22 @@ requests to access. The following example uses the `@SessionAttributes` annotation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Controller + @SessionAttributes("pet") // <1> + public class EditPetForm { + // ... + } +---- +<1> Using the `@SessionAttributes` annotation. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- @Controller - @SessionAttributes("pet") <1> + @SessionAttributes("pet") // <1> public class EditPetForm { // ... } @@ -2265,11 +2717,11 @@ it is automatically promoted to and saved in the HTTP Servlet session. It remain until another controller method uses a `SessionStatus` method argument to clear the storage, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller - @SessionAttributes("pet") <1> + @SessionAttributes("pet") // <1> public class EditPetForm { // ... @@ -2279,7 +2731,7 @@ storage, as the following example shows: if (errors.hasErrors) { // ... } - status.setComplete(); <2> + status.setComplete(); // <2> // ... } } @@ -2288,6 +2740,28 @@ storage, as the following example shows: <1> Storing the `Pet` value in the Servlet session. <2> Clearing the `Pet` value from the Servlet session. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +@Controller +@SessionAttributes("pet") // <1> +class EditPetForm { + + // ... + + @PostMapping("/pets/{id}") + fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String { + if (errors.hasErrors()) { + // ... + } + status.setComplete() // <2> + // ... + } +} +---- +<1> Storing the `Pet` value in the Servlet session. +<2> Clearing the `Pet` value from the Servlet session. + [[mvc-ann-sessionattribute]] ==== `@SessionAttribute` @@ -2298,8 +2772,8 @@ If you need access to pre-existing session attributes that are managed globally you can use the `@SessionAttribute` annotation on a method parameter, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RequestMapping("/") public String handle(@SessionAttribute User user) { <1> @@ -2308,6 +2782,15 @@ as the following example shows: ---- <1> Using a `@SessionAttribute` annotation. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RequestMapping("/") + fun handle(@SessionAttribute user: User): String { // <1> + // ... + } +---- + For use cases that require adding or removing session attributes, consider injecting `org.springframework.web.context.request.WebRequest` or `javax.servlet.http.HttpSession` into the controller method. @@ -2325,11 +2808,21 @@ Similar to `@SessionAttribute`, you can use the `@RequestAttribute` annotations access pre-existing request attributes created earlier (for example, by a Servlet `Filter` or `HandlerInterceptor`): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @GetMapping("/") + public String handle(@RequestAttribute Client client) { // <1> + // ... + } +---- +<1> Using the `@RequestAttribute` annotation. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- @GetMapping("/") - public String handle(@RequestAttribute Client client) { <1> + fun handle(@RequestAttribute client: Client): String { // <1> // ... } ---- @@ -2364,8 +2857,8 @@ Note that URI template variables from the present request are automatically made available when expanding a redirect URL, and you don't need to explicitly add them through `Model` or `RedirectAttributes`. The following example shows how to define a redirect: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PostMapping("/files/{path}") public String upload(...) { @@ -2373,6 +2866,15 @@ through `Model` or `RedirectAttributes`. The following example shows how to defi return "redirect:files/{path}"; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/files/{path}") + fun upload(...): String { + // ... + return "redirect:files/{path}" + } +---- Another way of passing data to the redirect target is by using flash attributes. Unlike other redirect attributes, flash attributes are saved in the HTTP session (and, hence, do @@ -2434,24 +2936,43 @@ requests with `multipart/form-data` is parsed and accessible as regular request parameters. The following example accesses one regular form field and one uploaded file: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@Controller -public class FileUploadController { + @Controller + public class FileUploadController { - @PostMapping("/form") - public String handleFormUpload(@RequestParam("name") String name, - @RequestParam("file") MultipartFile file) { + @PostMapping("/form") + public String handleFormUpload(@RequestParam("name") String name, + @RequestParam("file") MultipartFile file) { - if (!file.isEmpty()) { - byte[] bytes = file.getBytes(); - // store the bytes somewhere - return "redirect:uploadSuccess"; + if (!file.isEmpty()) { + byte[] bytes = file.getBytes(); + // store the bytes somewhere + return "redirect:uploadSuccess"; + } + return "redirect:uploadFailure"; + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Controller + class FileUploadController { + + @PostMapping("/form") + fun handleFormUpload(@RequestParam("name") name: String, + @RequestParam("file") file: MultipartFile): String { + + if (!file.isEmpty) { + val bytes = file.bytes + // store the bytes somewhere + return "redirect:uploadSuccess" + } + return "redirect:uploadFailure" } - return "redirect:uploadFailure"; } -} ---- Declaring the argument type as a `List` allows for resolving multiple @@ -2469,38 +2990,57 @@ You can also use multipart content as part of data binding to a and file from the preceding example could be fields on a form object, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -class MyForm { + class MyForm { - private String name; + private String name; - private MultipartFile file; + private MultipartFile file; - // ... -} + // ... + } -@Controller -public class FileUploadController { - - @PostMapping("/form") - public String handleFormUpload(MyForm form, BindingResult errors) { - if (!form.getFile().isEmpty()) { - byte[] bytes = form.getFile().getBytes(); - // store the bytes somewhere - return "redirect:uploadSuccess"; + @Controller + public class FileUploadController { + + @PostMapping("/form") + public String handleFormUpload(MyForm form, BindingResult errors) { + if (!form.getFile().isEmpty()) { + byte[] bytes = form.getFile().getBytes(); + // store the bytes somewhere + return "redirect:uploadSuccess"; + } + return "redirect:uploadFailure"; + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyForm(val name: String, val file: MultipartFile, ...) + + @Controller + class FileUploadController { + + @PostMapping("/form") + fun handleFormUpload(form: MyForm, errors: BindingResult): String { + if (!form.file.isEmpty) { + val bytes = form.file.bytes + // store the bytes somewhere + return "redirect:uploadSuccess" + } + return "redirect:uploadFailure" } - return "redirect:uploadFailure"; } -} ---- + Multipart requests can also be submitted from non-browser clients in a RESTful service scenario. The following example shows a file with JSON: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- POST /someUrl Content-Type: multipart/mixed @@ -2525,14 +3065,23 @@ probably want it deserialized from JSON (similar to `@RequestBody`). Use the `@RequestPart` annotation to access a multipart after converting it with an <>: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@PostMapping("/") -public String handle(@RequestPart("meta-data") MetaData metadata, - @RequestPart("file-data") MultipartFile file) { - // ... -} + @PostMapping("/") + public String handle(@RequestPart("meta-data") MetaData metadata, + @RequestPart("file-data") MultipartFile file) { + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/") + fun handle(@RequestPart("meta-data") metadata: MetaData, + @RequestPart("file-data") file: MultipartFile): String { + // ... + } ---- You can use `@RequestPart` in combination with `javax.validation.Valid` or use Spring's @@ -2542,17 +3091,27 @@ into a 400 (BAD_REQUEST) response. Alternatively, you can handle validation erro within the controller through an `Errors` or `BindingResult` argument, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@PostMapping("/") -public String handle(@Valid @RequestPart("meta-data") MetaData metadata, - BindingResult result) { - // ... -} + @PostMapping("/") + public String handle(@Valid @RequestPart("meta-data") MetaData metadata, + BindingResult result) { + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/") + fun handle(@Valid @RequestPart("meta-data") metadata: MetaData, + result: BindingResult): String { + // ... + } ---- + [[mvc-ann-requestbody]] ==== `@RequestBody` [.small]#<># @@ -2561,14 +3120,23 @@ You can use the `@RequestBody` annotation to have the request body read and dese `Object` through an <>. The following example uses a `@RequestBody` argument: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PostMapping("/accounts") public void handle(@RequestBody Account account) { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/accounts") + fun handle(@RequestBody account: Account) { + // ... + } +---- + You can use the <> option of the <> to configure or customize message conversion. @@ -2580,14 +3148,22 @@ into a 400 (BAD_REQUEST) response. Alternatively, you can handle validation erro within the controller through an `Errors` or `BindingResult` argument, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PostMapping("/accounts") public void handle(@Valid @RequestBody Account account, BindingResult result) { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/accounts") + fun handle(@Valid @RequestBody account: Account, result: BindingResult) { + // ... + } +---- [[mvc-ann-httpentity]] @@ -2597,14 +3173,23 @@ as the following example shows: `HttpEntity` is more or less identical to using <> but is based on a container object that exposes request headers and body. The following listing shows an example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PostMapping("/accounts") public void handle(HttpEntity entity) { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping("/accounts") + fun handle(entity: HttpEntity) { + // ... + } +---- + [[mvc-ann-responsebody]] @@ -2616,8 +3201,8 @@ to the response body through an <>. The following listing shows an example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/accounts/{id}") @ResponseBody @@ -2625,6 +3210,15 @@ The following listing shows an example: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/accounts/{id}") + @ResponseBody + fun handle(): Account { + // ... + } +---- `@ResponseBody` is also supported at the class level, in which case it is inherited by all controller methods. This is the effect of `@RestController`, which is nothing more @@ -2646,8 +3240,8 @@ See <> for details. `ResponseEntity` is like <> but with status and headers. For example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/something") public ResponseEntity handle() { @@ -2656,6 +3250,16 @@ See <> for details. return ResponseEntity.ok().eTag(etag).build(body); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/something") + fun handle(): ResponseEntity { + val body = ... + val etag = ... + return ResponseEntity.ok().eTag(etag).build(body) + } +---- Spring MVC supports using a single value <> to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive @@ -2677,8 +3281,8 @@ which allow rendering only a subset of all fields in an `Object`. To use it with `@ResponseBody` or `ResponseEntity` controller methods, you can use Jackson's `@JsonView` annotation to activate a serialization view class, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RestController public class UserController { @@ -2717,6 +3321,25 @@ which allow rendering only a subset of all fields in an `Object`. To use it with } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RestController + class UserController { + + @GetMapping("/user") + @JsonView(User.WithoutPasswordView::class) + fun getUser() = User("eric", "7!jd#h23") + } + + class User( + @JsonView(WithoutPasswordView::class) val username: String, + @JsonView(WithPasswordView::class) val password: String) { + + interface WithoutPasswordView + interface WithPasswordView : WithoutPasswordView + } +---- NOTE: `@JsonView` allows an array of view classes, but you can specify only one per controller method. If you need to activate multiple views, you can use a composite interface. @@ -2724,8 +3347,8 @@ controller method. If you need to activate multiple views, you can use a composi For controllers that rely on view resolution, you can add the serialization view class to the model, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller public class UserController extends AbstractController { @@ -2738,6 +3361,22 @@ to the model, as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.ui.set + + @Controller + class UserController : AbstractController() { + + @GetMapping("/user") + fun getUser(model: Model): String { + model["user"] = User("eric", "7!jd#h23") + model[JsonView::class.qualifiedName] = User.WithoutPasswordView::class.java + return "userView" + } + } +---- @@ -2766,8 +3405,8 @@ related to the request body. The following example shows a `@ModelAttribute` method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ModelAttribute public void populateModel(@RequestParam String number, Model model) { @@ -2775,17 +3414,35 @@ The following example shows a `@ModelAttribute` method: // add more ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ModelAttribute + fun populateModel(@RequestParam number: String, model: Model) { + model.addAttribute(accountRepository.findAccount(number)) + // add more ... + } +---- The following example adds only one attribute: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ModelAttribute public Account addAccount(@RequestParam String number) { return accountRepository.findAccount(number); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ModelAttribute + fun addAccount(@RequestParam number: String): Account { + return accountRepository.findAccount(number) + } +---- + NOTE: When a name is not explicitly specified, a default name is chosen based on the `Object` type, as explained in the javadoc for {api-spring-framework}/core/Conventions.html[`Conventions`]. @@ -2798,8 +3455,8 @@ attribute. This is typically not required, as it is the default behavior in HTML unless the return value is a `String` that would otherwise be interpreted as a view name. `@ModelAttribute` can also customize the model attribute name, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/accounts/{id}") @ModelAttribute("myAccount") @@ -2808,6 +3465,16 @@ unless the return value is a `String` that would otherwise be interpreted as a v return account; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/accounts/{id}") + @ModelAttribute("myAccount") + fun handle(): Account { + // ... + return account + } +---- @@ -2833,13 +3500,13 @@ do, except for `@ModelAttribute` (command object) arguments. Typically, they are with a `WebDataBinder` argument (for registrations) and a `void` return value. The following listing shows an example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller public class FormController { - @InitBinder <1> + @InitBinder // <1> public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); @@ -2851,17 +3518,35 @@ The following listing shows an example: ---- <1> Defining an `@InitBinder` method. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Controller + class FormController { + + @InitBinder // <1> + fun initBinder(binder: WebDataBinder) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd") + dateFormat.isLenient = false + binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false)) + } + + // ... + } +---- +<1> Defining an `@InitBinder` method. + Alternatively, when you use a `Formatter`-based setup through a shared `FormattingConversionService`, you can re-use the same approach and register controller-specific `Formatter` implementations, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller public class FormController { - @InitBinder <1> + @InitBinder // <1> protected void initBinder(WebDataBinder binder) { binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); } @@ -2871,7 +3556,21 @@ controller-specific `Formatter` implementations, as the following example shows: ---- <1> Defining an `@InitBinder` method on a custom formatter. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Controller + class FormController { + + @InitBinder // <1> + protected fun initBinder(binder: WebDataBinder) { + binder.addCustomFormatter(DateFormatter("yyyy-MM-dd")) + } + // ... + } +---- +<1> Defining an `@InitBinder` method on a custom formatter. [[mvc-ann-exceptionhandler]] === Exceptions @@ -2880,8 +3579,8 @@ controller-specific `Formatter` implementations, as the following example shows: `@Controller` and <> classes can have `@ExceptionHandler` methods to handle exceptions from controller methods, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller public class SimpleController { @@ -2894,6 +3593,20 @@ controller-specific `Formatter` implementations, as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Controller + class SimpleController { + + // ... + + @ExceptionHandler + fun handle(ex: IOException): ResponseEntity { + // ... + } + } +---- The exception may match against a top-level exception being propagated (that is, a direct `IOException` being thrown) or against the immediate cause within a top-level wrapper exception @@ -2907,26 +3620,42 @@ used to sort exceptions based on their depth from the thrown exception type. Alternatively, the annotation declaration may narrow the exception types to match, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExceptionHandler({FileSystemException.class, RemoteException.class}) public ResponseEntity handle(IOException ex) { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExceptionHandler(FileSystemException::class, RemoteException::class) + fun handle(ex: IOException): ResponseEntity { + // ... + } +---- You can even use a list of specific exception types with a very generic argument signature, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExceptionHandler({FileSystemException.class, RemoteException.class}) public ResponseEntity handle(Exception ex) { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExceptionHandler(FileSystemException::class, RemoteException::class) + fun handle(ex: Exception): ResponseEntity { + // ... + } +---- [NOTE] ==== @@ -3139,8 +3868,8 @@ By default, `@ControllerAdvice` methods apply to every request (that is, all con but you can narrow that down to a subset of controllers by using attributes on the annotation, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Target all Controllers annotated with @RestController @ControllerAdvice(annotations = RestController.class) @@ -3154,6 +3883,21 @@ annotation, as the following example shows: @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class ExampleAdvice3 {} ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Target all Controllers annotated with @RestController + @ControllerAdvice(annotations = [RestController::class]) + class ExampleAdvice1 + + // Target all Controllers within specific packages + @ControllerAdvice("org.example.controllers") + class ExampleAdvice2 + + // Target all Controllers assignable to specific classes + @ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class]) + class ExampleAdvice3 +---- The selectors in the preceding example are evaluated at runtime and may negatively impact performance if used extensively. See the @@ -3179,8 +3923,8 @@ include::web-uris.adoc[leveloffset=+2] You can use `ServletUriComponentsBuilder` to create URIs relative to the current request, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- HttpServletRequest request = ... @@ -3191,29 +3935,57 @@ as the following example shows: .expand("123") .encode(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val request: HttpServletRequest = ... + + // Re-uses host, scheme, port, path and query string... + + val ucb = ServletUriComponentsBuilder.fromRequest(request) + .replaceQueryParam("accountId", "{id}").build() + .expand("123") + .encode() +---- You can create URIs relative to the context path, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Re-uses host, port and context path... ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts").build() ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Re-uses host, port and context path... + + val ucb = ServletUriComponentsBuilder.fromContextPath(request) + .path("/accounts").build() +---- You can create URIs relative to a Servlet (for example, `/main/{asterisk}`), as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Re-uses host, port, context path, and Servlet prefix... ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts").build() ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Re-uses host, port, context path, and Servlet prefix... + + val ucb = ServletUriComponentsBuilder.fromServletMapping(request) + .path("/accounts").build() +---- NOTE: As of 5.1, `ServletUriComponentsBuilder` ignores information from the `Forwarded` and `X-Forwarded-*` headers, which specify the client-originated address. Consider using the @@ -3228,8 +4000,8 @@ such headers. Spring MVC provides a mechanism to prepare links to controller methods. For example, the following MVC controller allows for link creation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Controller @RequestMapping("/hotels/{hotel}") @@ -3241,17 +4013,38 @@ the following MVC controller allows for link creation: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Controller + @RequestMapping("/hotels/{hotel}") + class BookingController { + + @GetMapping("/bookings/{booking}") + fun getBooking(@PathVariable booking: Long): ModelAndView { + // ... + } + } +---- You can prepare a link by referring to the method by name, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42); URI uri = uriComponents.encode().toUri(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uriComponents = MvcUriComponentsBuilder + .fromMethodName(BookingController::class.java, "getBooking", 21).buildAndExpand(42) + + val uri = uriComponents.encode().toUri() +---- In the preceding example, we provide actual method argument values (in this case, the long value: `21`) to be used as a path variable and inserted into the URL. Furthermore, we provide the @@ -3264,14 +4057,22 @@ There are additional ways to use `MvcUriComponentsBuilder`. For example, you can akin to mock testing through proxies to avoid referring to the controller method by name, as the following example shows (the example assumes static import of `MvcUriComponentsBuilder.on`): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val uriComponents = MvcUriComponentsBuilder + .fromMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42) + + val uri = uriComponents.encode().toUri() +---- NOTE: Controller method signatures are limited in their design when they are supposed to be usable for link creation with `fromMethodCall`. Aside from needing a proper parameter signature, @@ -3293,8 +4094,8 @@ For such cases, you can use the static `fromXxx` overloaded methods that accept with a base URL and then use the instance-based `withXxx` methods. For example, the following listing uses `withMethodCall`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en"); MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base); @@ -3302,6 +4103,15 @@ following listing uses `withMethodCall`: URI uri = uriComponents.encode().toUri(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en") + val builder = MvcUriComponentsBuilder.relativeTo(base) + builder.withMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42) + + val uri = uriComponents.encode().toUri() +---- NOTE: As of 5.1, `MvcUriComponentsBuilder` ignores information from the `Forwarded` and `X-Forwarded-*` headers, which specify the client-originated address. Consider using the @@ -3318,21 +4128,30 @@ by referring to the implicitly or explicitly assigned name for each request mapp Consider the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @RequestMapping("/people/{id}/addresses") + public class PersonAddressController { + + @RequestMapping("/{country}") + public HttpEntity getAddress(@PathVariable String country) { ... } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- @RequestMapping("/people/{id}/addresses") - public class PersonAddressController { + class PersonAddressController { @RequestMapping("/{country}") - public HttpEntity getAddress(@PathVariable String country) { ... } + fun getAddress(@PathVariable country: String): HttpEntity { ... } } ---- Given the preceding controller, you can prepare a link from a JSP, as follows: -[source,jsp,indent=0] -[subs="verbatim,quotes"] +[source,jsp,indent=0,subs="verbatim,quotes"] ---- <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> ... @@ -3378,8 +4197,8 @@ Once the asynchronous request processing feature is < { + val deferredResult = DeferredResult() + // Save the deferredResult somewhere.. + return deferredResult + } + + // From some other thread... + deferredResult.setResult(result) ---- The controller can produce the return value asynchronously, from a different thread -- for @@ -3405,8 +4238,8 @@ example, in response to an external event (JMS message), a scheduled task, or ot A controller can wrap any supported return value with `java.util.concurrent.Callable`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @PostMapping public Callable processUpload(final MultipartFile file) { @@ -3417,7 +4250,15 @@ as the following example shows: return "someView"; } }; - + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @PostMapping + fun processUpload(file: MultipartFile) = Callable { + // ... + "someView" } ---- @@ -3552,8 +4393,8 @@ each object is serialized with an <> and written to the response, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/events") public ResponseBodyEmitter handle() { @@ -3571,6 +4412,23 @@ response, as the following example shows: // and done at some point emitter.complete(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/events") + fun handle() = ResponseBodyEmitter().apply { + // Save the emitter somewhere.. + } + + // In some other thread + emitter.send("Hello once") + + // and again later on + emitter.send("Hello again") + + // and done at some point + emitter.complete() +---- You can also use `ResponseBodyEmitter` as the body in a `ResponseEntity`, letting you customize the status and headers of the response. @@ -3591,8 +4449,8 @@ https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from t are formatted according to the W3C SSE specification. To produce an SSE stream from a controller, return `SseEmitter`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter handle() { @@ -3610,6 +4468,23 @@ stream from a controller, return `SseEmitter`, as the following example shows: // and done at some point emitter.complete(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) + fun handle() = SseEmitter().apply { + // Save the emitter somewhere.. + } + + // In some other thread + emitter.send("Hello once") + + // and again later on + emitter.send("Hello again") + + // and done at some point + emitter.complete() +---- While SSE is the main option for streaming into browsers, note that Internet Explorer does not support Server-Sent Events. Consider using Spring's @@ -3627,8 +4502,8 @@ Sometimes, it is useful to bypass message conversion and stream directly to the `OutputStream` (for example, for a file download). You can use the `StreamingResponseBody` return value type to do so, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/download") public StreamingResponseBody handle() { @@ -3640,6 +4515,14 @@ return value type to do so, as the following example shows: }; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/download") + fun handle() = StreamingResponseBody { + // write... + } +---- You can use `StreamingResponseBody` as the body in a `ResponseEntity` to customize the status and headers of the response. @@ -3800,8 +4683,8 @@ While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234] describes all directives for the `Cache-Control` response header, the `CacheControl` type takes a use case-oriented approach that focuses on the common scenarios: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Cache for an hour - "Cache-Control: max-age=3600" CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); @@ -3814,6 +4697,20 @@ use case-oriented approach that focuses on the common scenarios: // "Cache-Control: max-age=864000, public, no-transform" CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Cache for an hour - "Cache-Control: max-age=3600" + val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS) + + // Prevent caching - "Cache-Control: no-store" + val ccNoStore = CacheControl.noStore() + + // Cache for ten days in public and private caches, + // public caches should not transform the response + // "Cache-Control: max-age=864000, public, no-transform" + val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic() +---- `WebContentGenerator` also accept a simpler `cachePeriod` property (defined in seconds) that works as follows: @@ -3834,8 +4731,8 @@ Controllers can add explicit support for HTTP caching. We recommended doing so, against conditional request headers. A controller can add an `ETag` header and `Cache-Control` settings to a `ResponseEntity`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @GetMapping("/book/{id}") public ResponseEntity showBook(@PathVariable Long id) { @@ -3850,6 +4747,22 @@ settings to a `ResponseEntity`, as the following example shows: .body(book); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @GetMapping("/book/{id}") + fun showBook(@PathVariable id: Long): ResponseEntity { + + val book = findBook(id); + val version = book.getVersion() + + return ResponseEntity + .ok() + .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS)) + .eTag(version) // lastModified is also available + .body(book) + } +---- The preceding example sends an 304 (NOT_MODIFIED) response with an empty body if the comparison to the conditional request headers indicates that the content has not changed. Otherwise, the @@ -3858,27 +4771,47 @@ to the conditional request headers indicates that the content has not changed. O You can also make the check against conditional request headers in the controller, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RequestMapping public String myHandleMethod(WebRequest webRequest, Model model) { - long eTag = ... <1> + long eTag = ... // <1> if (request.checkNotModified(eTag)) { - return null; <2> + return null; // <2> } - model.addAttribute(...); <3> + model.addAttribute(...); // <3> return "myViewName"; } ---- +<1> Application-specific calculation. +<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing. +<3> Continue with the request processing. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RequestMapping + fun myHandleMethod(webRequest: WebRequest, model: Model): String? { + + val eTag: Long = ... // <1> + + if (request.checkNotModified(eTag)) { + return null // <2> + } + model[...] = ... // <3> + return "myViewName" + } +---- <1> Application-specific calculation. <2> The response has been set to 304 (NOT_MODIFIED) -- no further processing. <3> Continue with the request processing. + There are three variants for checking conditional requests against `eTag` values, `lastModified` values, or both. For conditional `GET` and `HEAD` requests, you can set the response to 304 (NOT_MODIFIED). For conditional `POST`, `PUT`, and `DELETE`, you can instead set the response @@ -3929,20 +4862,26 @@ and <>. In Java configuration, you can use the `@EnableWebMvc` annotation to enable MVC configuration, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc public class WebConfig { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig +---- In XML configuration, you can use the `` element to enable MVC configuration, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- `. You can view the https://schema.spring.io/mvc/spring-mvc.xsd[Spring MVC XML schema] or use @@ -4001,8 +4950,8 @@ formatting library is also installed if Joda-Time is present on the classpath. In Java configuration, you can register custom formatters and converters, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4014,11 +4963,22 @@ following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun addFormatters(registry: FormatterRegistry) { + // ... + } + } +---- The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- > for use with `@Valid` In Java configuration, you can customize the global `Validator` instance, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override - public Validator getValidator(); { + public Validator getValidator() { + // ... + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun getValidator(): Validator { // ... } } @@ -4089,8 +5061,7 @@ following example shows: The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4191,8 +5186,8 @@ more details. In Java configuration, you can customize requested content type resolution, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4205,11 +5200,24 @@ following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) { + configurer.mediaType("json", MediaType.APPLICATION_JSON) + configurer.mediaType("xml", MediaType.APPLICATION_XML) + } + } +---- + The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4238,8 +5246,8 @@ You can customize `HttpMessageConverter` in Java configuration by overriding The following example adds XML and Jackson JSON converters with a customized `ObjectMapper` instead of the default ones: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4256,6 +5264,21 @@ The following example adds XML and Jackson JSON converters with a customized } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfiguration : WebMvcConfigurer { + + override fun configureMessageConverters(converters: MutableList>) { + val builder = Jackson2ObjectMapperBuilder() + .indentOutput(true) + .dateFormat(SimpleDateFormat("yyyy-MM-dd")) + .modulesToInstall(ParameterNamesModule()) + converters.add(MappingJackson2HttpMessageConverter(builder.build())) + converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build())) +---- In the preceding example, {api-spring-framework}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`] @@ -4288,8 +5311,7 @@ Other interesting Jackson modules are available: The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4321,8 +5343,8 @@ logic to execute before the view generates the response. The following example of Java configuration forwards a request for `/` to a view called `home`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4334,12 +5356,23 @@ The following example of Java configuration forwards a request for `/` to a view } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun addViewControllers(registry: ViewControllerRegistry) { + registry.addViewController("/").setViewName("home") + } + } +---- The following example achieves the same thing as the preceding example, but with XML, by using the `` element: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -4355,8 +5388,8 @@ The MVC configuration simplifies the registration of view resolvers. The following Java configuration example configures content negotiation view resolution by using JSP and Jackson as a default `View` for JSON rendering: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4369,11 +5402,24 @@ resolution by using JSP and Jackson as a default `View` for JSON rendering: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configureViewResolvers(registry: ViewResolverRegistry) { + registry.enableContentNegotiation(MappingJackson2JsonView()) + registry.jsp() + } + } +---- + The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4390,8 +5436,7 @@ configuration of the underlying view technology. The MVC namespace provides dedicated elements. The following example works with FreeMarker: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4412,8 +5457,8 @@ The MVC namespace provides dedicated elements. The following example works with In Java configuration, you can add the respective `Configurer` bean, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4433,6 +5478,24 @@ as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configureViewResolvers(registry: ViewResolverRegistry) { + registry.enableContentNegotiation(MappingJackson2JsonView()) + registry.freeMarker().cache(false) + } + + @Bean + fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply { + setTemplateLoaderPath("/freemarker") + } + } +---- @@ -4452,8 +5515,8 @@ status code is returned. The following listing shows how to do so with Java configuration: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4467,11 +5530,24 @@ The following listing shows how to do so with Java configuration: } } ---- +[source,kotlin,indent=0,subs="verbatim",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun addResourceHandlers(registry: ResourceHandlerRegistry) { + registry.addResourceHandler("/resources/**") + .addResourceLocations("/public", "classpath:/static/") + .setCachePeriod(31556926) + } + } +---- The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4562,8 +5652,8 @@ lower than that of the `DefaultServletHttpRequestHandler`, which is `Integer.MAX The following example shows how to enable the feature by using the default setup: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4575,11 +5665,22 @@ The following example shows how to enable the feature by using the default setup } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) { + configurer.enable() + } + } +---- The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -4593,8 +5694,8 @@ If the default Servlet has been custom-configured with a different name, or if a different Servlet container is being used where the default Servlet name is unknown, then you must explicitly provide the default Servlet's name, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4604,14 +5705,25 @@ then you must explicitly provide the default Servlet's name, as the following ex public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable("myCustomDefaultServlet"); } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) { + configurer.enable("myCustomDefaultServlet") + } } ---- + The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -4628,8 +5740,8 @@ For details on the individual options, see the The following example shows how to customize path matching in Java configuration: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableWebMvc @@ -4656,14 +5768,41 @@ The following example shows how to customize path matching in Java configuration public PathMatcher antPathMatcher() { //... } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebMvc + class WebConfig : WebMvcConfigurer { + + override fun configurePathMatch(configurer: PathMatchConfigurer) { + configurer + .setUseSuffixPatternMatch(true) + .setUseTrailingSlashMatch(false) + .setUseRegisteredSuffixPatternMatch(true) + .setPathMatcher(antPathMatcher()) + .setUrlPathHelper(urlPathHelper()) + .addPathPrefix("/api", + HandlerTypePredicate.forAnnotation(RestController::class.java)) + } + + @Bean + fun urlPathHelper(): UrlPathHelper { + //... + } + @Bean + fun antPathMatcher(): PathMatcher { + //... + } } ---- The following example shows how to achieve the same configuration in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ` declaration.