parent
7cfae94d70
commit
0d3fb0ee0f
|
@ -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
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
|
||||
RI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
[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
|
||||
|
|
|
@ -83,12 +83,12 @@ 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 {
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@CrossOrigin
|
||||
@GetMapping("/{id}")
|
||||
|
@ -100,7 +100,26 @@ public class AccountController {
|
|||
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,17 +156,35 @@ 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(maxAge = 3600)
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@CrossOrigin("https://domain2.com")
|
||||
@GetMapping("/{id}")
|
||||
|
@ -159,7 +196,27 @@ public class AccountController {
|
|||
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,12 +253,12 @@ 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) {
|
||||
|
@ -215,7 +272,27 @@ public class WebConfig implements WebMvcConfigurer {
|
|||
|
||||
// Add more mappings...
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
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)
|
||||
|
||||
// Add more mappings...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
@ -226,8 +303,7 @@ public class WebConfig implements WebMvcConfigurer {
|
|||
To enable CORS in the XML namespace, you can use the `<mvc:cors>` element,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<mvc:cors>
|
||||
|
||||
|
@ -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();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
|
||||
// Possibly...
|
||||
// config.applyPermitDefaultValues()
|
||||
// Possibly...
|
||||
// config.applyPermitDefaultValues()
|
||||
|
||||
config.setAllowCredentials(true);
|
||||
config.addAllowedOrigin("https://domain1.com");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.setAllowCredentials(true);
|
||||
config.addAllowedOrigin("https://domain1.com");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
CorsFilter filter = new CorsFilter(source);
|
||||
CorsFilter filter = new CorsFilter(source);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val config = CorsConfiguration()
|
||||
|
||||
// Possibly...
|
||||
// config.applyPermitDefaultValues()
|
||||
|
||||
config.allowCredentials = true
|
||||
config.addAllowedOrigin("https://domain1.com")
|
||||
config.addAllowedHeader("*")
|
||||
config.addAllowedMethod("*")
|
||||
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", config)
|
||||
|
||||
val filter = CorsFilter(source)
|
||||
----
|
||||
|
|
|
@ -46,8 +46,8 @@ 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
|
||||
|
@ -55,7 +55,7 @@ The following example shows how to configure FreeMarker as a view technology:
|
|||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.freemarker();
|
||||
registry.freeMarker();
|
||||
}
|
||||
|
||||
// Configure FreeMarker...
|
||||
|
@ -68,11 +68,29 @@ The following example shows how to configure FreeMarker as a view technology:
|
|||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.freeMarker()
|
||||
}
|
||||
|
||||
// Configure FreeMarker...
|
||||
|
||||
@Bean
|
||||
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"]
|
||||
----
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
|
@ -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"]
|
||||
----
|
||||
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
|
||||
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
|
||||
|
@ -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"]
|
||||
----
|
||||
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
|
||||
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
|
||||
|
@ -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"]
|
||||
----
|
||||
<!-- FreeMarker macros have to be imported into a namespace.
|
||||
We strongly recommend sticking to 'spring'. -->
|
||||
|
@ -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 "<br>"/>
|
||||
|
@ -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:
|
||||
<input type="text" name="name" value="">
|
||||
|
@ -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:
|
||||
<input type="radio" name="address.town" value="London">London</input>
|
||||
|
@ -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<String, String> referenceData(HttpServletRequest request) throws Exception {
|
||||
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
|
||||
Map<String, String> cityMap = new LinkedHashMap<>();
|
||||
cityMap.put("LDN", "London");
|
||||
cityMap.put("PRS", "Paris");
|
||||
cityMap.put("NYC", "New York");
|
||||
|
||||
Map<String, String> model = new HashMap<>();
|
||||
Map<String, Object> 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<String, *> {
|
||||
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:
|
||||
<input type="radio" name="address.town" value="LDN">London</input>
|
||||
|
@ -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"]
|
||||
----
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
|
@ -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 '<!DOCTYPE html>'
|
||||
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"]
|
||||
----
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
|
@ -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"]
|
||||
----
|
||||
<html>
|
||||
<head>
|
||||
|
@ -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"]
|
||||
----
|
||||
<!-- the ResourceBundleViewResolver -->
|
||||
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
|
||||
|
@ -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"]
|
||||
----
|
||||
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
|
||||
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:form>
|
||||
<table>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form method="POST">
|
||||
<table>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:form modelAttribute="user">
|
||||
<table>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:form>
|
||||
<table>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Interests:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:form>
|
||||
<table>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Sex:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Sex:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Password:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Password:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Skills:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Skills:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>House:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>House:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Country:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Country:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<tr>
|
||||
<td>Notes:</td>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:hidden path="house"/>
|
||||
----
|
||||
|
||||
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"]
|
||||
----
|
||||
<input name="house" type="hidden" value="Gryffindor"/>
|
||||
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:form>
|
||||
<table>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form method="POST">
|
||||
<table>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:form>
|
||||
<form:errors path="*" cssClass="errorBox"/>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form method="POST">
|
||||
<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
|
||||
|
@ -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"]
|
||||
----
|
||||
<form:form method="delete">
|
||||
<p class="submit"><input type="submit" value="Delete Pet"/></p>
|
||||
|
@ -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"]
|
||||
----
|
||||
<filter>
|
||||
<filter-name>httpMethodFilter</filter-name>
|
||||
|
@ -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"]
|
||||
----
|
||||
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
|
||||
<property name="definitions">
|
||||
|
@ -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"]
|
||||
----
|
||||
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
|
||||
<property name="definitions">
|
||||
|
@ -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"]
|
||||
----
|
||||
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
|
||||
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
|
||||
|
@ -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"]
|
||||
----
|
||||
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
|
||||
<property name="basename" value="views"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
[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"]
|
||||
----
|
||||
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
|
||||
<property name="definitions">
|
||||
|
@ -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<String, Any>,
|
||||
feed: Feed, request: HttpServletRequest) {
|
||||
// implementation omitted
|
||||
}
|
||||
|
||||
override fun buildFeedEntries(model: Map<String, Any>,
|
||||
request: HttpServletRequest, response: HttpServletResponse): List<Entry> {
|
||||
// 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<String, Any>,
|
||||
feed: Channel, request: HttpServletRequest) {
|
||||
// implementation omitted
|
||||
}
|
||||
|
||||
override fun buildFeedItems(model: Map<String, Any>,
|
||||
request: HttpServletRequest, response: HttpServletResponse): List<Item> {
|
||||
// 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<String, Any>, doc: Document, writer: PdfWriter,
|
||||
request: HttpServletRequest, response: HttpServletResponse) {
|
||||
|
||||
val words = model["wordList"] as List<String>
|
||||
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,13 +2099,13 @@ 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 {
|
||||
@EnableWebMvc
|
||||
@ComponentScan
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
public XsltViewResolver xsltViewResolver() {
|
||||
|
@ -1980,9 +2114,23 @@ public class WebConfig implements WebMvcConfigurer {
|
|||
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"]
|
||||
----
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
@ -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"]
|
||||
----
|
||||
<html>
|
||||
<head>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue