Consistent use of tabs for sample code in the reference documentation

This commit is contained in:
Juergen Hoeller 2017-11-20 22:28:00 +01:00
parent 08c78554b9
commit 6f24c0de17
18 changed files with 1191 additions and 1213 deletions

View File

@ -4,6 +4,7 @@
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:
This part of the reference documentation covers all of those technologies that are

View File

@ -309,23 +309,23 @@ Typically, such configuration will live in a ".groovy" file with a structure as
[source,java,indent=0]
[subs="verbatim,quotes"]
----
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
----
This configuration style is largely equivalent to XML bean definitions and even
@ -373,7 +373,7 @@ delegates, e.g. with `XmlBeanDefinitionReader` for XML files:
----
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
context.refresh();
----
Or with `GroovyBeanDefinitionReader` for Groovy files:
@ -383,7 +383,7 @@ Or with `GroovyBeanDefinitionReader` for Groovy files:
----
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
context.refresh();
----
Such reader delegates can be mixed and matched on the same `ApplicationContext`,
@ -1685,7 +1685,7 @@ The preceding example is equivalent to the following Java code:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
exampleBean.setEmail("")
exampleBean.setEmail("")
----
The `<null/>` element handles `null` values. For example:
@ -3822,7 +3822,6 @@ Find below the custom `BeanPostProcessor` implementation class definition:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
@ -5400,7 +5399,7 @@ comma/semicolon/space-separated list that includes the parent package of each cl
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
...
}
----
@ -5511,12 +5510,12 @@ and using "stub" repositories instead.
[subs="verbatim,quotes"]
----
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
----
and the equivalent using XML
@ -5746,10 +5745,10 @@ fully-qualified class name when configuring the scanner:
[subs="verbatim,quotes"]
----
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
----
[source,xml,indent=0]
@ -5803,8 +5802,8 @@ fully-qualified class name when configuring the scanner:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
...
}
...
}
----
[source,xml,indent=0]
@ -5828,8 +5827,8 @@ the following configuration will result in standard JDK dynamic proxies:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
...
}
...
}
----
[source,xml,indent=0]
@ -6144,7 +6143,7 @@ exact same way as when using Spring annotations:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
...
}
----
@ -6374,7 +6373,7 @@ To enable component scanning, just annotate your `@Configuration` class as follo
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
...
}
----
@ -6693,7 +6692,7 @@ method directly during construction:
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
return foo;
}
// ...
@ -7011,7 +7010,7 @@ another configuration class:
@Configuration
public class ConfigA {
@Bean
@Bean
public A a() {
return new A();
}
@ -8681,18 +8680,18 @@ environment provides:
[subs="verbatim,quotes"]
----
public class EntityCreatedEvent<T>
extends ApplicationEvent implements ResolvableTypeProvider {
extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}
----
[TIP]

View File

@ -1,6 +1,9 @@
[[databuffers]]
= Data Buffers and Codecs
== Introduction
The `DataBuffer` interface defines an abstraction over byte buffers.
@ -9,6 +12,9 @@ Netty does not use `ByteBuffer`, but instead offers `ByteBuf` as an alternative.
Spring's `DataBuffer` is a simple abstraction over `ByteBuf` that can also be used on non-Netty
platforms (i.e. Servlet 3.1+).
== `DataBufferFactory`
The `DataBufferFactory` offers functionality to allocate new data buffers, as well as to wrap
@ -25,6 +31,9 @@ to be used on Netty platforms, such as Reactor Netty.
The other implementation, the `DefaultDataBufferFactory`, is used on other platforms, such as
Servlet 3.1+ servers.
== The `DataBuffer` interface
The `DataBuffer` interface is similar to `ByteBuffer`, but offers a number of advantages.
@ -35,7 +44,7 @@ writing, and a separate `flip()` operation to switch between the two I/O operat
In general, the following invariant holds for the read position, write position, and the capacity:
--
`0` <= _read position_ <= _write position_ <= _capacity_
`0` <= _read position_ <= _write position_ <= _capacity_
--
When reading bytes from the `DataBuffer`, the read position is automatically updated in accordance with
@ -54,6 +63,8 @@ Netty platforms, such as Reactor Netty.
The other implementation, the `DefaultDataBuffer`, is used on other platforms, such as Servlet 3.1+
servers.
=== `PooledDataBuffer`
The `PooledDataBuffer` is an extension to `DataBuffer` that adds methods for reference counting.
@ -67,6 +78,7 @@ buffers.
These methods take a plain `DataBuffer` as parameter, but only call `retain` or `release` if the
passed data buffer is an instance of `PooledDataBuffer`.
[[databuffer-reference-counting]]
==== Reference Counting
@ -95,23 +107,25 @@ throw exceptions:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
DataBufferFactory factory = ...
DataBuffer buffer = factory.allocateBuffer(); <1>
boolean release = true; <2>
try {
writeDataToBuffer(buffer); <3>
putBufferInHttpBody(buffer);
release = false; <4>
} finally {
if (release) {
DataBufferUtils.release(buffer); <5>
}
}
DataBufferFactory factory = ...
DataBuffer buffer = factory.allocateBuffer(); <1>
boolean release = true; <2>
try {
writeDataToBuffer(buffer); <3>
putBufferInHttpBody(buffer);
release = false; <4>
}
finally {
if (release) {
DataBufferUtils.release(buffer); <5>
}
}
private void writeDataToBuffer(DataBuffer buffer) throws IOException { <3>
...
}
private void writeDataToBuffer(DataBuffer buffer) throws IOException { <3>
...
}
----
<1> A new buffer is allocated.
<2> A boolean flag indicates whether the allocated buffer should be released.
<3> This example method loads data into the buffer. Note that the method can throw an `IOException`,
@ -121,6 +135,8 @@ released as part of sending the HTTP body across the wire.
<5> If an exception did occur, the flag is still set to `true`, and the buffer will be released
here.
=== DataBufferUtils
`DataBufferUtils` contains various utility methods that operate on data buffers.
@ -129,6 +145,9 @@ It contains methods for reading a `Flux` of `DataBuffer` objects from an `InputS
`DataBufferUtils` also exposes `retain` and `release` methods that operate on plain `DataBuffer`
instances (so that casting to a `PooledDataBuffer` is not required).
[codecs]
== Codecs

View File

@ -444,8 +444,8 @@ this:
----
com/
foo/
services.xml
daos.xml
services.xml
daos.xml
MessengerService.class
----

View File

@ -4,6 +4,7 @@
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:
This part of the reference documentation is concerned with data access and the
@ -1943,13 +1944,13 @@ transaction in which it has been published as committed successfully:
[subs="verbatim,quotes"]
----
@Component
public class MyComponent {
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
----
The `TransactionalEventListener` annotation exposes a `phase` attribute that allows to customize
@ -4612,13 +4613,13 @@ standalone environment or in a standalone integration test like in the following
[subs="verbatim,quotes"]
----
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
@ -4637,17 +4638,17 @@ Config like in the following example.
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();
}
}
----

View File

@ -6,6 +6,7 @@
:doc-spring-gemfire: {doc-root}/spring-gemfire/docs/current/reference
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:
This part of the reference documentation covers the Spring Framework's integration with
@ -1102,8 +1103,8 @@ construct a `HttpComponentsClientHttpRequestFactory` like so:
[subs="verbatim,quotes"]
----
HttpClient httpClient = HttpClientBuilder.create().build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
----
====
@ -2447,12 +2448,12 @@ bean as a JMS listener endpoint.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Component
public class MyService {
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(String data) { ... }
}
@JmsListener(destination = "myDestination")
public void processOrder(String data) { ... }
}
----
The idea of the example above is that whenever a message is available on the
@ -2517,12 +2518,12 @@ element.
----
<jms:annotation-driven/>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="concurrency" value="3-10"/>
</bean>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="concurrency" value="3-10"/>
</bean>
----
@ -2574,10 +2575,10 @@ a custom header:
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(Order order, @Header("order_type") String orderType) {
...
}
@JmsListener(destination = "myDestination")
public void processOrder(Order order, @Header("order_type") String orderType) {
...
}
}
----
@ -2624,17 +2625,17 @@ annotate the payload with `@Valid` and configure the necessary validator as foll
@EnableJms
public class AppConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
}
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
}
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(myValidator());
return factory;
}
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(myValidator());
return factory;
}
}
----
@ -2658,8 +2659,8 @@ as follow to automatically send a response:
@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
// order processing
return status;
// order processing
return status;
}
----
@ -2678,11 +2679,11 @@ If you need to set additional headers in a transport-independent manner, you cou
@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
// order processing
return MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
// order processing
return MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
}
----
@ -2695,11 +2696,11 @@ example can be rewritten as follows:
----
@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
// order processing
Message<OrderStatus> response = MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
// order processing
Message<OrderStatus> response = MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
return JmsResponse.forQueue(response, "status");
}
----
@ -3722,7 +3723,7 @@ this interface as the definition for the management interface:
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</bean>
</beans>
----
@ -6470,10 +6471,10 @@ however, the exception is uncaught and cannot be transmitted. For those cases, a
----
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
}
----
@ -7723,7 +7724,7 @@ the method again.
[subs="verbatim,quotes"]
----
@CacheResult(cacheName="books", **exceptionCacheName="failures"**
**cachedExceptions = InvalidIsbnNotFoundException.class**)
**cachedExceptions = InvalidIsbnNotFoundException.class**)
public Book findBook(ISBN isbn)
----
@ -7853,11 +7854,11 @@ Again, to use it, one simply needs to declare the appropriate `CacheManager`:
[subs="verbatim,quotes"]
----
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
<!-- EhCache library setup -->
<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>
----
This setup bootstraps the ehcache library inside Spring IoC (through the `ehcache` bean) which
@ -7878,7 +7879,7 @@ Configuring a `CacheManager` that creates the cache on demand is straightforward
[subs="verbatim,quotes"]
----
<bean id="cacheManager"
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
----
It is also possible to provide the caches to use explicitly. In that case, only those
@ -7924,8 +7925,8 @@ Again, to use it, one simply needs to declare the appropriate `CacheManager`:
[subs="verbatim,quotes"]
----
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>

View File

@ -1,9 +1,10 @@
[[lanugages]]
[[languages]]
= Language Support
:doc-root: https://docs.spring.io
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:

View File

@ -58,18 +58,18 @@ for their APIs, thus giving a better Kotlin development experience overall.
To retrieve a list of `Foo` objects in Java, one would normally write:
[source,java]
[source,java,indent=0]
----
Flux<User> users = client.get().retrieve().bodyToFlux(User.class)
Flux<User> users = client.get().retrieve().bodyToFlux(User.class)
----
Whilst with Kotlin and Spring Framework extensions, one is able to write:
[source,kotlin]
[source,kotlin,indent=0]
----
val users = client.get().retrieve().bodyToFlux<User>()
// or (both are equivalent)
val users : Flux<User> = client.get().retrieve().bodyToFlux()
val users = client.get().retrieve().bodyToFlux<User>()
// or (both are equivalent)
val users : Flux<User> = client.get().retrieve().bodyToFlux()
----
As in Java, `users` in Kotlin is strongly typed, but Kotlin's clever type inference allows
@ -172,24 +172,23 @@ This mechanism is very efficient as it does not require any reflection or CGLIB
In Java, one may for example write:
[source,java]
[source,java,indent=0]
----
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new
Bar(context.getBean(Foo.class))
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class))
);
----
Whilst in Kotlin with reified type parameters and `GenericApplicationContext`
Kotlin extensions one can instead simply write:
[source,kotlin]
[source,kotlin,indent=0]
----
val context = GenericApplicationContext().apply {
registerBean<Foo>()
registerBean { Bar(it.getBean<Foo>()) }
}
val context = GenericApplicationContext().apply {
registerBean<Foo>()
registerBean { Bar(it.getBean<Foo>()) }
}
----
In order to allow a more declarative approach and cleaner syntax, Spring Framework provides
@ -198,36 +197,36 @@ It declares an `ApplicationContextInitializer` via a clean declarative API
which enables one to deal with profiles and `Environment` for customizing
how beans are registered.
[source,kotlin]
[source,kotlin,indent=0]
----
fun beans() = beans {
bean<UserHandler>()
bean<Routes>()
bean<WebHandler>("webHandler") {
RouterFunctions.toWebHandler(
ref<Routes>().router(),
HandlerStrategies.builder().viewResolver(ref()).build()
)
}
bean("messageSource") {
ReloadableResourceBundleMessageSource().apply {
setBasename("messages")
setDefaultEncoding("UTF-8")
}
}
bean {
val prefix = "classpath:/templates/"
val suffix = ".mustache"
val loader = MustacheResourceTemplateLoader(prefix, suffix)
MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply {
setPrefix(prefix)
setSuffix(suffix)
}
}
profile("foo") {
bean<Foo>()
}
}
fun beans() = beans {
bean<UserHandler>()
bean<Routes>()
bean<WebHandler>("webHandler") {
RouterFunctions.toWebHandler(
ref<Routes>().router(),
HandlerStrategies.builder().viewResolver(ref()).build()
)
}
bean("messageSource") {
ReloadableResourceBundleMessageSource().apply {
setBasename("messages")
setDefaultEncoding("UTF-8")
}
}
bean {
val prefix = "classpath:/templates/"
val suffix = ".mustache"
val loader = MustacheResourceTemplateLoader(prefix, suffix)
MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply {
setPrefix(prefix)
setSuffix(suffix)
}
}
profile("foo") {
bean<Foo>()
}
}
----
In this example, `bean<Routes>()` is using autowiring by constructor and `ref<Routes>()`
@ -235,12 +234,12 @@ is a shortcut for `applicationContext.getBean(Routes::class.java)`.
This `beans()` function can then be used to register beans on the application context.
[source,kotlin]
[source,kotlin,indent=0]
----
val context = GenericApplicationContext().apply {
beans().initialize(this)
refresh()
}
val context = GenericApplicationContext().apply {
beans().initialize(this)
refresh()
}
----
[NOTE]
@ -276,24 +275,24 @@ Spring Framework now comes with a
that allows one to leverage the <<web-reactive#webflux-fn,WebFlux functional
API>> for writing clean and idiomatic Kotlin code:
[source,kotlin]
[source,kotlin,indent=0]
----
router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
}
router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
}
----
[NOTE]
@ -326,18 +325,18 @@ https://github.com/Kotlin/kotlinx.html[kotlinx.html] DSL or simply using Kotlin
This can allow one to write Kotlin templates with full autocompletion and
refactoring support in a supported IDE:
[source,kotlin]
[source,kotlin,indent=0]
----
import io.spring.demo.*
import io.spring.demo.*
"""
${include("header")}
<h1>${i18n("title")}</h1>
<ul>
${users.joinToLine{ "<li>${i18n("user")} ${it.firstname} ${it.lastname}</li>" }}
</ul>
${include("footer")}
"""
"""
${include("header")}
<h1>${i18n("title")}</h1>
<ul>
${users.joinToLine{ "<li>${i18n("user")} ${it.firstname} ${it.lastname}</li>" }}
</ul>
${include("footer")}
"""
----
See https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
@ -394,9 +393,9 @@ you will be able to write your Kotlin beans without any additional `open` keywor
In Kotlin, it is very convenient and considered best practice to declare read-only properties
within the primary constructor, as in the following example:
[source,kotlin]
[source,kotlin,indent=0]
----
class Person(val name: String, val age: Int)
class Person(val name: String, val age: Int)
----
You can optionally add https://kotlinlang.org/docs/reference/data-classes.html[the `data` keyword]
@ -410,12 +409,12 @@ in the primary constructor:
This allows for easy changes to individual properties even if `Person` properties are read-only:
[source,kotlin]
[source,kotlin,indent=0]
----
data class Person(val name: String, val age: Int)
data class Person(val name: String, val age: Int)
val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
----
Common persistence technologies such as JPA require a default constructor, preventing this
@ -442,13 +441,13 @@ mappings (like with MongoDB, Redis, Cassandra, etc).
Our recommendation is to try and favor constructor injection with `val` read-only (and non-nullable when possible)
https://kotlinlang.org/docs/reference/properties.html[properties].
[source,kotlin]
[source,kotlin,indent=0]
----
@Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
)
@Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
)
----
[NOTE]
@ -461,17 +460,17 @@ explicit `@Autowired constructor` in the example shown above.
If one really needs to use field injection, use the `lateinit var` construct,
i.e.,
[source,kotlin]
[source,kotlin,indent=0]
----
@Component
class YourBean {
@Component
class YourBean {
@Autowired
lateinit var mongoTemplate: MongoTemplate
@Autowired
lateinit var mongoTemplate: MongoTemplate
@Autowired
lateinit var solrClient: SolrClient
}
@Autowired
lateinit var solrClient: SolrClient
}
----
@ -487,28 +486,27 @@ character will need to be escaped by writing `@Value("\${property}")`.
As an alternative, it is possible to customize the properties placeholder prefix by declaring
the following configuration beans:
[source,kotlin]
[source,kotlin,indent=0]
----
@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
}
@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
}
----
Existing code (like Spring Boot actuators or `@LocalServerPort`) that
uses the `${...}` syntax, can be customised with configuration beans, like
this:
Existing code (like Spring Boot actuators or `@LocalServerPort`) that uses the `${...}` syntax,
can be customised with configuration beans, like as follows:
[source,kotlin]
[source,kotlin,indent=0]
----
@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
setIgnoreUnresolvablePlaceholders(true)
}
@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
setIgnoreUnresolvablePlaceholders(true)
}
@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
----
[NOTE]
@ -535,20 +533,20 @@ attribute it is specified as a `vararg` parameter.
To understand what that means, let's take `@RequestMapping`, which is one
of the most widely used Spring annotations as an example. This Java annotation is declared as:
[source,java]
[source,java,indent=0]
----
public @interface RequestMapping {
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
RequestMethod[] method() default {};
// ...
}
// ...
}
----
The typical use case for `@RequestMapping` is to map a handler method to a specific path
@ -568,8 +566,8 @@ use a shortcut annotation such as `@GetMapping` or `@PostMapping`, etc.
[NOTE]
====
Remininder: if the `@RequestMapping` `method` attribute is not specified, all HTTP methods will be matched,
not only the `GET` methods.
Reminder: If the `@RequestMapping` `method` attribute is not specified,
all HTTP methods will be matched, not only the `GET` methods.
====
Improving the syntax and consistency of Kotlin annotation array attributes is discussed in

View File

@ -1,10 +1,9 @@
[[overview]]
= Spring Framework Overview
:toc: left
:toclevels: 1
:docinfo1:
[[overview]]
= Spring Framework Overview
Spring makes it easy to create Java enterprise applications. It provides everything you
need to embrace the Java language in an enterprise environment, with support for Groovy
and Kotlin as alternative languages on the JVM, and with the flexibility to create many

View File

@ -32,7 +32,7 @@ Use this server setup to test one `@Controller` at a time:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client = WebTestClient.bindToController(new TestController()).build();
client = WebTestClient.bindToController(new TestController()).build();
----
The above loads the <<web-reactive.adoc#webflux-config,WebFlux Java config>> and
@ -51,8 +51,8 @@ Use this option to set up a server from a
[source,java,intent=0]
[subs="verbatim,quotes"]
----
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
----
Internally the provided configuration is passed to `RouterFunctions.toWebHandler`.
@ -70,21 +70,20 @@ some subset of it:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = WebConfig.class) // <1>
public class MyTests {
@Autowired
private ApplicationContext context; // <2>
private WebTestClient client;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = WebConfig.class) // <1>
public class MyTests {
@Before
public void setUp() {
client = WebTestClient.bindToApplicationContext(context).build(); // <3>
}
@Autowired
private ApplicationContext context; // <2>
}
private WebTestClient client;
@Before
public void setUp() {
client = WebTestClient.bindToApplicationContext(context).build(); // <3>
}
}
----
<1> Specify the configuration to load
@ -107,7 +106,7 @@ This server setup option allows you to connect to a running server:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
----
@ -122,10 +121,10 @@ are readily available following `bindToServer`. For all others, you need to use
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client = WebTestClient.bindToController(new TestController())
.configureClient()
.baseUrl("/test")
.build();
client = WebTestClient.bindToController(new TestController())
.configureClient()
.baseUrl("/test")
.build();
----
@ -143,12 +142,12 @@ Typically you start by asserting the response status and headers:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
// ...
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
// ...
----
Then you specify how to decode and consume the response body:
@ -162,32 +161,32 @@ Then you can use built-in assertions for the body. Here is one example:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList(Person.class).hasSize(3).contains(person);
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList(Person.class).hasSize(3).contains(person);
----
You can go beyond the built-in assertions and create your own:
----
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.consumeWith(result -> {
// custom assertions (e.g. AssertJ)...
});
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.consumeWith(result -> {
// custom assertions (e.g. AssertJ)...
});
----
You can also exit the workflow and get a result:
----
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
----
[TIP]
@ -208,10 +207,10 @@ that resources are released:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound()
.expectBody(Void.class);
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound()
.expectBody(Void.class);
----
Or if you want to assert there is no response content, use this:
@ -219,11 +218,11 @@ Or if you want to assert there is no response content, use this:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client.post().uri("/persons")
.body(personMono, Person.class)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty;
client.post().uri("/persons")
.body(personMono, Person.class)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty;
----
@ -238,11 +237,11 @@ http://jsonassert.skyscreamer.org[JSONAssert] to verify JSON content:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
----
You can also use https://github.com/jayway/JsonPath[JSONPath] expressions:
@ -250,12 +249,12 @@ You can also use https://github.com/jayway/JsonPath[JSONPath] expressions:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason");
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason");
----
@ -269,11 +268,11 @@ and header assertions, as shown below:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.returnResult(MyEvent.class);
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.returnResult(MyEvent.class);
----
@ -284,14 +283,14 @@ from the `reactor-test` module to do that, for example:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Flux<Event> eventFux = result.getResponseBody();
Flux<Event> eventFux = result.getResponseBody();
StepVerifier.create(eventFlux)
.expectNext(person)
.expectNextCount(4)
.consumeNextWith(p -> ...)
.thenCancel()
.verify();
StepVerifier.create(eventFlux)
.expectNext(person)
.expectNextCount(4)
.consumeNextWith(p -> ...)
.thenCancel()
.verify();
----

View File

@ -5,6 +5,7 @@
:doc-spring-boot: {doc-root}/spring-boot/docs/current/reference
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:
The adoption of the test-driven-development (TDD) approach to software
@ -1142,13 +1143,13 @@ example, a custom `@EnabledOnMac` annotation can be created as follows.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
----
===== @DisabledIf
@ -1179,13 +1180,13 @@ example, a custom `@DisabledOnMac` annotation can be created as follows.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
----
@ -3901,15 +3902,15 @@ empty list in order to disable the default listeners, which otherwise would requ
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// execute test logic...
}
}
@Test
public void testMethod() {
// execute test logic...
}
}
----
[[testcontext-junit4-rules]]
@ -3938,21 +3939,21 @@ demonstrates the proper way to declare these rules in an integration test.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// execute test logic...
}
}
@Test
public void testMethod() {
// execute test logic...
}
}
----
[[testcontext-support-classes-junit4]]
@ -4026,17 +4027,17 @@ The following code listing demonstrates how to configure a test class to use the
[source,java,indent=0]
[subs="verbatim,quotes"]
----
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// execute test logic...
}
}
@Test
void testMethod() {
// execute test logic...
}
}
----
Since annotations in JUnit 5 can also be used as meta-annotations, Spring is able to
@ -4049,16 +4050,16 @@ configuration used in the previous example.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// execute test logic...
}
}
@Test
void testMethod() {
// execute test logic...
}
}
----
Similarly, the following example uses `@SpringJUnitWebConfig` to create a
@ -4067,16 +4068,16 @@ Similarly, the following example uses `@SpringJUnitWebConfig` to create a
[source,java,indent=0]
[subs="verbatim,quotes"]
----
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {
@Test
void testMethod() {
// execute test logic...
}
}
@Test
void testMethod() {
// execute test logic...
}
}
----
See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in
@ -4121,18 +4122,18 @@ dependencies to be `final` and therefore _immutable_.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
private final OrderService orderService;
@Autowired
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService.
}
@Autowired
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService.
}
// tests that use the injected OrderService
}
// tests that use the injected OrderService
}
----
[[testcontext-junit-jupiter-di-method]]
@ -4149,14 +4150,14 @@ In the following example, Spring will inject the `OrderService` from the
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@Test
void deleteOrder(@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
}
}
@Test
void deleteOrder(@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
}
}
----
Due to the robustness of the `ParameterResolver` support in JUnit Jupiter, it is also
@ -4171,17 +4172,17 @@ use of `@RepeatedTest` from JUnit Jupiter allows the test method to gain access
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
@Autowired OrderService orderService) {
@RepeatedTest(10)
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
----
[[testcontext-support-classes-testng]]
@ -4276,29 +4277,28 @@ JUnit Jupiter based example of using Spring MVC Test:
[source,java,indent=0]
----
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class ExampleTests {
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class ExampleTests {
private MockMvc mockMvc;
private MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
void getAccount() throws Exception {
this.mockMvc.perform(get("/accounts/1")
.accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
@Test
void getAccount() throws Exception {
this.mockMvc.perform(get("/accounts/1")
.accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
----
The above test relies on the `WebApplicationContext` support of the __TestContext framework__
@ -4461,13 +4461,13 @@ session across requests. It can be used as follows:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
// static import of SharedHttpSessionConfigurer.sharedHttpSession
// static import of SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.apply(sharedHttpSession())
.build();
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.apply(sharedHttpSession())
.build();
// Use mockMvc to perform requests...
// Use mockMvc to perform requests...
----
See `ConfigurableMockMvcBuilder` for a list of all MockMvc builder features
@ -4761,45 +4761,45 @@ paging through all messages. How would you go about testing it?
With Spring MVC Test, we can easily test if we are able to create a `Message`.
[source,java]
[source,java,indent=0]
----
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
----
What if we want to test our form view that allows us to create the message? For example,
assume our form looks like the following snippet:
[source,xml]
[source,xml,indent=0]
----
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
----
How do we ensure that our form will produce the correct request to create a new message? A
naive attempt would look like this:
[source,java]
[source,java,indent=0]
----
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='summary']").exists())
.andExpect(xpath("//textarea[@name='text']").exists());
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='summary']").exists())
.andExpect(xpath("//textarea[@name='text']").exists());
----
This test has some obvious drawbacks. If we update our controller to use the parameter
@ -4807,21 +4807,21 @@ This test has some obvious drawbacks. If we update our controller to use the par
form is out of synch with the controller. To resolve this we can combine our two tests.
[[spring-mvc-test-server-htmlunit-mock-mvc-test]]
[source,java]
[source,java,indent=0]
----
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
----
This would reduce the risk of our test incorrectly passing, but there are still some
@ -4908,19 +4908,19 @@ In order to use HtmlUnit with Apache HttpComponents 4.5+, you will need to use H
We can easily create an HtmlUnit `WebClient` that integrates with `MockMvc` using the
`MockMvcWebClientBuilder` as follows.
[source,java]
[source,java,indent=0]
----
@Autowired
WebApplicationContext context;
@Autowired
WebApplicationContext context;
WebClient webClient;
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
----
[NOTE]
@ -4941,9 +4941,9 @@ Now we can use HtmlUnit as we normally would, but without the need to deploy our
application to a Servlet container. For example, we can request the view to create
a message with the following.
[source,java]
[source,java,indent=0]
----
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
----
[NOTE]
@ -4955,29 +4955,29 @@ illustrated in <<Advanced MockMvcWebClientBuilder>>.
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit
it to create a message.
[source,java]
[source,java,indent=0]
----
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
----
Finally, we can verify that a new message was created successfully. The following
assertions use the http://joel-costigliola.github.io/assertj/[AssertJ] library.
[source,java]
[source,java,indent=0]
----
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
----
This improves on our <<spring-mvc-test-server-htmlunit-mock-mvc-test,MockMvc test>> in a
@ -4999,59 +4999,59 @@ In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest w
by building a `WebClient` based on the `WebApplicationContext` loaded for us by the Spring
TestContext Framework. This approach is repeated here.
[source,java]
[source,java,indent=0]
----
@Autowired
WebApplicationContext context;
@Autowired
WebApplicationContext context;
WebClient webClient;
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
----
We can also specify additional configuration options.
[source,java]
[source,java,indent=0]
----
WebClient webClient;
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
----
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
instance separately and supplying it to the `MockMvcWebClientBuilder` as follows.
[source,java]
[source,java,indent=0]
----
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
----
This is more verbose, but by building the `WebClient` with a `MockMvc` instance we have
@ -5094,27 +5094,27 @@ be displayed afterwards.
If one of the fields were named "summary", then we might have something like the
following repeated in multiple places within our tests.
[source,java]
[source,java,indent=0]
----
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
----
So what happens if we change the `id` to "smmry"? Doing so would force us to update all
of our tests to incorporate this change! Of course, this violates the _DRY Principle_; so
we should ideally extract this code into its own method as follows.
[source,java]
[source,java,indent=0]
----
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
// ...
}
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
// ...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
----
This ensures that we do not have to update all of our tests if we change the UI.
@ -5122,39 +5122,39 @@ This ensures that we do not have to update all of our tests if we change the UI.
We might even take this a step further and place this logic within an Object that
represents the `HtmlPage` we are currently on.
[source,java]
[source,java,indent=0]
----
public class CreateMessagePage {
public class CreateMessagePage {
final HtmlPage currentPage;
final HtmlPage currentPage;
final HtmlTextInput summaryInput;
final HtmlTextInput summaryInput;
final HtmlSubmitInput submit;
final HtmlSubmitInput submit;
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
----
Formerly, this pattern is known as the
@ -5171,18 +5171,18 @@ includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver`
We can easily create a Selenium `WebDriver` that integrates with `MockMvc` using the
`MockMvcHtmlUnitDriverBuilder` as follows.
[source,java]
[source,java,indent=0]
----
@Autowired
WebApplicationContext context;
@Autowired
WebApplicationContext context;
WebDriver driver;
WebDriver driver;
@Before
public void setup() {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
@Before
public void setup() {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
----
@ -5204,17 +5204,17 @@ Now we can use WebDriver as we normally would, but without the need to deploy ou
application to a Servlet container. For example, we can request the view to create
a message with the following.
[source,java]
[source,java,indent=0]
----
CreateMessagePage page = CreateMessagePage.to(driver);
CreateMessagePage page = CreateMessagePage.to(driver);
----
We can then fill out the form and submit it to create a message.
[source,java]
[source,java,indent=0]
----
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
----
This improves on the design of our
@ -5223,35 +5223,35 @@ Pattern_. As we mentioned in <<spring-mvc-test-server-htmlunit-webdriver-why>>,
use the Page Object Pattern with HtmlUnit, but it is much easier with WebDriver. Let's
take a look at our new `CreateMessagePage` implementation.
[source,java]
[source,java,indent=0]
----
public class CreateMessagePage
extends AbstractPage { // <1>
public class CreateMessagePage
extends AbstractPage { // <1>
// <2>
private WebElement summary;
private WebElement text;
// <2>
private WebElement summary;
private WebElement text;
// <3>
@FindBy(css = "input[type=submit]")
private WebElement submit;
// <3>
@FindBy(css = "input[type=submit]")
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
super(driver);
public CreateMessagePage(WebDriver driver) {
super(driver);
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("http://localhost:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("http://localhost:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
----
<1> The first thing you will notice is that `CreateMessagePage` extends the
@ -5277,39 +5277,39 @@ annotation to look up our submit button using a css selector, *input[type=submit
Finally, we can verify that a new message was created successfully. The following
assertions use the https://code.google.com/p/fest/[FEST assertion library].
[source,java]
[source,java,indent=0]
----
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
----
We can see that our `ViewMessagePage` allows us to interact with our custom domain
model. For example, it exposes a method that returns a `Message` object.
[source,java]
[source,java,indent=0]
----
public Message getMessage() throws ParseException {
Message message = new Message();
message.setId(getId());
message.setCreated(getCreated());
message.setSummary(getSummary());
message.setText(getText());
return message;
}
public Message getMessage() throws ParseException {
Message message = new Message();
message.setId(getId());
message.setCreated(getCreated());
message.setSummary(getSummary());
message.setText(getText());
return message;
}
----
We can then leverage the rich domain objects in our assertions.
Lastly, don't forget to _close_ the `WebDriver` instance when the test is complete.
[source,java]
[source,java,indent=0]
----
@After
public void destroy() {
if (driver != null) {
driver.close();
@After
public void destroy() {
if (driver != null) {
driver.close();
}
}
}
----
For additional information on using WebDriver, refer to the Selenium
@ -5322,59 +5322,59 @@ In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simpl
possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by
the Spring TestContext Framework. This approach is repeated here.
[source,java]
[source,java,indent=0]
----
@Autowired
WebApplicationContext context;
@Autowired
WebApplicationContext context;
WebDriver driver;
WebDriver driver;
@Before
public void setup() {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
@Before
public void setup() {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
----
We can also specify additional configuration options.
[source,java]
[source,java,indent=0]
----
WebDriver driver;
WebDriver driver;
@Before
public void setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
@Before
public void setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
----
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder` as follows.
[source,java]
[source,java,indent=0]
----
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
----
This is more verbose, but by building the `WebDriver` with a `MockMvc` instance we have

View File

@ -4,6 +4,7 @@
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:
This part of the documentation covers support for reactive stack, web applications built on a

View File

@ -4,6 +4,7 @@
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:
This part of the documentation covers support for Servlet stack, web applications built on the

View File

@ -32,12 +32,12 @@ The `retrieve()` method is the easiest way to get a response body and decode it:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient client = WebClient.create("http://example.org");
WebClient client = WebClient.create("http://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
----
You can also get a stream of objects decoded from the response:
@ -45,10 +45,10 @@ You can also get a stream of objects decoded from the response:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
----
By default, responses with 4xx or 5xx status codes result in an error of type
@ -57,12 +57,12 @@ By default, responses with 4xx or 5xx status codes result in an error of type
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
----
@ -77,10 +77,10 @@ to `retrieve()` but also provides access to the `ClientResponse`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
----
At this level you can also create a full `ResponseEntity`:
@ -88,10 +88,10 @@ At this level you can also create a full `ResponseEntity`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(Person.class));
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(Person.class));
----
Note that unlike `retrieve()`, with `exchange()` there are no automatic error signals for
@ -117,14 +117,14 @@ The request body can be encoded from an Object:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<Person> personMono = ... ;
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
----
You can also have a stream of objects encoded:
@ -132,14 +132,14 @@ You can also have a stream of objects encoded:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Flux<Person> personFlux = ... ;
Flux<Person> personFlux = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(personFlux, Person.class)
.retrieve()
.bodyToMono(Void.class);
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(personFlux, Person.class)
.retrieve()
.bodyToMono(Void.class);
----
Or if you have the actual value, use the `syncBody` shortcut method:
@ -147,14 +147,14 @@ Or if you have the actual value, use the `syncBody` shortcut method:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Person person = ... ;
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);
----
@ -169,13 +169,13 @@ content is automatically set to `"application/x-www-form-urlencoded"` by the
[source,java,intent=0]
[subs="verbatim,quotes"]
----
MultiValueMap<String, String> formData = ... ;
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(formData)
.retrieve()
.bodyToMono(Void.class);
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(formData)
.retrieve()
.bodyToMono(Void.class);
----
You can also supply form data in-line via `BodyInserters`:
@ -183,13 +183,13 @@ You can also supply form data in-line via `BodyInserters`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
import static org.springframework.web.reactive.function.BodyInserters.*;
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
----
@ -204,18 +204,18 @@ headers. `MultipartBodyBuilder` can be used to build the parts:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(parts)
.retrieve()
.bodyToMono(Void.class);
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(parts)
.retrieve()
.bodyToMono(Void.class);
----
Note that the content type for each part is automatically set based on the extension of the
@ -227,13 +227,13 @@ You can also supply multipart data in-line via `BodyInserters`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
import static org.springframework.web.reactive.function.BodyInserters.*;
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);
----
@ -250,14 +250,14 @@ To customize the underlying HTTP client:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
SslContext sslContext = ...
SslContext sslContext = ...
ClientHttpConnector connector = new ReactorClientHttpConnector(
builder -> builder.sslContext(sslContext));
ClientHttpConnector connector = new ReactorClientHttpConnector(
builder -> builder.sslContext(sslContext));
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.build();
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.build();
----
To customize the <<web-reactive.adoc#webflux-codecs,HTTP codecs>> used for encoding and
@ -266,16 +266,15 @@ decoding HTTP messages:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
// ...
})
.build();
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies)
.build();
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
// ...
})
.build();
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies)
.build();
----
The builder can be used to insert <<webflux-client-filter>>.
@ -289,9 +288,9 @@ build a new `WebClient`, based on, but without affecting the current instance:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient modifiedClient = client.mutate()
// user builder methods...
.build();
WebClient modifiedClient = client.mutate()
// user builder methods...
.build();
----
@ -305,16 +304,14 @@ build a new `WebClient`, based on, but without affecting the current instance:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
----
`ExchangeFilterFunctions` provides a filter for basic authentication:
@ -323,11 +320,11 @@ build a new `WebClient`, based on, but without affecting the current instance:
[subs="verbatim,quotes"]
----
// static import of ExchangeFilterFunctions.basicAuthentication
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "pwd"))
.build();
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "pwd"))
.build();
----
You can also mutate an existing `WebClient` instance without affecting the original:
@ -335,7 +332,7 @@ You can also mutate an existing `WebClient` instance without affecting the origi
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient filteredClient = client.mutate()
.filter(basicAuthentication("user", "pwd")
.build();
WebClient filteredClient = client.mutate()
.filter(basicAuthentication("user", "pwd")
.build();
----

View File

@ -28,17 +28,16 @@ Creating a WebSocket server is as simple as implementing `WebSocketHandler`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
public class MyWebSocketHandler implements WebSocketHandler {
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
// ...
@Override
public Mono<Void> handle(WebSocketSession session) {
// ...
}
}
}
----
Spring WebFlux provides a `WebSocketHandlerAdapter` that can adapt WebSocket
@ -49,26 +48,25 @@ adapter is registered as a bean, you can map requests to your handler, for examp
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
static class WebConfig {
@Configuration
static class WebConfig {
@Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/path", new MyWebSocketHandler());
@Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/path", new MyWebSocketHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1); // before annotated controllers
return mapping;
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1); // before annotated controllers
return mapping;
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
----
@ -98,22 +96,21 @@ WebSocket options when running on Tomcat:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
static class WebConfig {
@Configuration
static class WebConfig {
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter(webSocketService());
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter(webSocketService());
}
@Bean
public WebSocketService webSocketService() {
TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
strategy.setMaxSessionIdleTimeout(0L);
return new HandshakeWebSocketService(strategy);
}
}
@Bean
public WebSocketService webSocketService() {
TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
strategy.setMaxSessionIdleTimeout(0L);
return new HandshakeWebSocketService(strategy);
}
}
----
Check the upgrade strategy for your server to see what options are available. Currently
@ -157,9 +154,9 @@ WebSocketClient client = new ReactorNettyWebSocketClient();
URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
session.receive()
.doOnNext(System.out::println)
.then());
session.receive()
.doOnNext(System.out::println)
.then());
----
Some clients, e.g. Jetty, implement `Lifecycle` and need to be started in stopped

View File

@ -57,7 +57,7 @@ Configuring the Groovy Markup Template Engine is quite easy:
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.groovy();
}
@ -1287,7 +1287,7 @@ The HTML would look like:
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</table>
</form>
----

View File

@ -47,26 +47,23 @@ the `DispatcherServlet`. This class is auto-detected by the Servlet container
[source,java,indent=0]
[subs="verbatim,quotes"]
----
public class MyWebApplicationInitializer implements WebApplicationInitializer {
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
cxt.register(AppConfig.class);
cxt.refresh();
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(cxt);
// Register and map the Servlet
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
----
[NOTE]
@ -83,29 +80,29 @@ Below is an example of `web.xml` configuration to register and initialize the `D
----
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
----
@ -150,23 +147,23 @@ Below is example configuration with a `WebApplicationContext` hierarchy:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { App1Config.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
----
[TIP]
@ -182,29 +179,29 @@ And the `web.xml` equivalent:
----
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
----
@ -319,7 +316,6 @@ example of registering a `DispatcherServlet`:
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
----
@ -351,7 +347,6 @@ This is recommended for applications that use Java-based Spring configuration:
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
----
@ -379,7 +374,6 @@ If using XML-based Spring configuration, you should extend directly from
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
----
@ -1933,13 +1927,10 @@ following the `@ModelAttribute` argument:
----
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(**@ModelAttribute("pet") Pet pet**, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
----
@ -1955,21 +1946,20 @@ controller or alternatively use the `binding` flag on the annotation:
[subs="verbatim,quotes"]
----
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
**@ModelAttribute(binding=false)** Account account) {
// ...
}
@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
**@ModelAttribute(binding=false)** Account account) {
// ...
}
----
In addition to data binding you can also invoke validation using your own custom
@ -1982,14 +1972,11 @@ subsequently reported back to the user:
----
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(**@ModelAttribute("pet") Pet pet**, BindingResult result) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "petForm";
}
// ...
}
----
@ -2001,13 +1988,10 @@ annotation:
----
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(**@Valid @ModelAttribute("pet") Pet pet**, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
----
@ -2327,31 +2311,32 @@ object and converting from an object to the HTTP response body. The
`RequestMappingHandlerAdapter` supports the `@RequestBody` annotation with the following
default `HttpMessageConverters`:
* `ByteArrayHttpMessageConverter` converts byte arrays.
* `StringHttpMessageConverter` converts strings.
* `FormHttpMessageConverter` converts form data to/from a MultiValueMap<String, String>.
* `SourceHttpMessageConverter` converts to/from a javax.xml.transform.Source.
* `ByteArrayHttpMessageConverter` converts byte arrays
* `StringHttpMessageConverter` converts strings
* `FormHttpMessageConverter` converts form data to/from a `MultiValueMap<String, String>`
* `SourceHttpMessageConverter` converts to/from a `javax.xml.transform.Source``
For more information on these converters, see <<integration.adoc#rest-message-conversion,
Message Converters>>. Also note that if using the MVC namespace or the MVC Java config, a
wider range of message converters are registered by default. See <<mvc-config-enable>> for
more information.
Message Converters>>. Also note that if using the MVC namespace or the MVC Java config,
a wider range of message converters are registered by default, including default JSON
and XML payload converters (if e.g. Jackson, Gson and/or JAXB2 are present at runtime).
See <<mvc-config-enable>> for more information on MVC setup options.
If you intend to read and write XML, you will need to configure the
`MarshallingHttpMessageConverter` with a specific `Marshaller` and an `Unmarshaller`
implementation from the `org.springframework.oxm` package. The example below shows how
to do that directly in your configuration but if your application is configured through
the MVC namespace or the MVC Java config see <<mvc-config-enable>> instead.
For a custom example, if you intend to read and write XML using the `spring-oxm` module,
you need to configure the `MarshallingHttpMessageConverter` with a specific `Marshaller`
implementation from the `org.springframework.oxm` package. The example below shows how to
do that directly in your configuration but if your application is configured through the
MVC namespace or the MVC Java config see <<mvc-config-enable>> instead.
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<util:list id="beanList">
<list>
<ref bean="stringHttpMessageConverter"/>
<ref bean="marshallingHttpMessageConverter"/>
</util:list>
<list>
</property>
</bean>
@ -2360,11 +2345,10 @@ the MVC namespace or the MVC Java config see <<mvc-config-enable>> instead.
<bean id="marshallingHttpMessageConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="castorMarshaller"/>
<property name="unmarshaller" ref="castorMarshaller"/>
<constructor-arg ref="xstreamMarshaller"/>
</bean>
<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
----
An `@RequestBody` method parameter can be annotated with `@Valid`, in which case it will
@ -2833,9 +2817,8 @@ Spring MVC also provides a mechanism for building links to controller methods. F
@GetMapping("/bookings/{booking}")
public String getBooking(@PathVariable Long booking) {
// ...
}
// ...
}
}
----
@ -2923,12 +2906,12 @@ For example given:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity getAddress(@PathVariable String country) { ... }
}
@RequestMapping("/{country}")
public HttpEntity getAddress(@PathVariable String country) { ... }
}
----
You can prepare a link from a JSP as follows:
@ -3822,16 +3805,16 @@ accepted as an argument in several Spring Web MVC APIs.
[subs="verbatim,quotes"]
----
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();
// Prevent caching - "Cache-Control: no-store"
CacheControl 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"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
.noTransform().cachePublic();
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
.noTransform().cachePublic();
----
@ -4029,7 +4012,7 @@ In XML use the `<mvc:annotation-driven>` element:
The above registers a number of Spring MVC
<<mvc-servlet-special-bean-types,infrastructure beans>> also adapting to dependencies
available on the classpath -- for JSON, XML, etc.
available on the classpath: e.g. payload converters for JSON, XML, etc.
@ -4047,7 +4030,6 @@ In Java config implement `WebMvcConfigurer` interface:
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
----
@ -4079,7 +4061,6 @@ In Java config, register custom formatters and converters:
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
----
@ -4153,7 +4134,6 @@ In Java config, you can customize the global `Validator` instance:
public Validator getValidator(); {
// ...
}
}
----
@ -4219,7 +4199,6 @@ In Java config, register interceptors to apply to incoming requests:
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
}
----
@ -4324,7 +4303,6 @@ Below is an example that adds Jackson JSON and XML converters with a customized
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build()));
}
}
----
@ -4365,23 +4343,23 @@ It is also possible to do the same in XML:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
----
@ -4406,7 +4384,6 @@ An example of forwarding a request for `"/"` to a view called `"home"` in Java:
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
----
@ -4442,7 +4419,6 @@ JSON rendering:
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
}
----
@ -4506,7 +4482,6 @@ In Java config simply add the respective "Configurer" bean:
configurer.setTemplateLoaderPath("/WEB-INF/");
return configurer;
}
}
----
@ -4541,7 +4516,6 @@ In Java config:
.addResourceLocations("/public", "classpath:/static/")
.setCachePeriod(31556926);
}
}
----
@ -4584,7 +4558,6 @@ For example in Java config;
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
----
@ -4647,7 +4620,6 @@ To enable the feature using the default setup use:
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
----
@ -4713,21 +4685,21 @@ Example in Java config:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper());
.setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper());
}
@Bean
public UrlPathHelper urlPathHelper() {
//...
//...
}
@Bean
public PathMatcher antPathMatcher() {
//...
//...
}
}
@ -4738,17 +4710,17 @@ In XML, the same:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<mvc:annotation-driven>
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<mvc:annotation-driven>
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
----
@ -4797,7 +4769,6 @@ hook of the Spring `ApplicationContext`:
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
// ...
}
}
----

View File

@ -828,20 +828,19 @@ Consider also customizing these server-side SockJS related properties (see Javad
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/sockjs").withSockJS()
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000)
.setDisconnectDelay(30 * 1000);
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/sockjs").withSockJS()
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000)
.setDisconnectDelay(30 * 1000);
}
// ...
}
// ...
}
----
@ -992,10 +991,10 @@ broadcasting to other connected clients):
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic", "/queue");
}
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic", "/queue");
}
}
----
@ -1458,20 +1457,19 @@ In Java config:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
// ...
// ...
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
registry.setPathMatcher(new AntPathMatcher("."));
}
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
registry.setPathMatcher(new AntPathMatcher("."));
}
}
----
In XML config:
@ -1479,25 +1477,25 @@ In XML config:
[source,xml,indent=0]
[subs="verbatim,quotes,attributes"]
----
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
<websocket:stomp-endpoint path="/stomp" />
<websocket:simple-broker prefix="/topic, /queue"/>
</websocket:message-broker>
<websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
<websocket:stomp-endpoint path="/stomp"/>
<websocket:simple-broker prefix="/topic, /queue"/>
</websocket:message-broker>
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
<constructor-arg index="0" value="." />
</bean>
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
<constructor-arg index="0" value="."/>
</bean>
</beans>
</beans>
----
And below is a simple example to illustrate a controller with "." separator:
@ -1505,15 +1503,15 @@ And below is a simple example to illustrate a controller with "." separator:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Controller
@MessageMapping("foo")
public class FooController {
@MessageMapping("bar.{baz}")
public void handleBaz(@DestinationVariable String baz) {
}
@Controller
@MessageMapping("foo")
public class FooController {
}
@MessageMapping("bar.{baz}")
public void handleBaz(@DestinationVariable String baz) {
// ...
}
}
----
If the application prefix is set to "/app" then the foo method is effectively mapped to "/app/foo.bar.{baz}".
@ -1612,30 +1610,26 @@ user and associate it with subsequent STOMP messages on the same session:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
@EnableWebSocketMessageBroker
public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Configuration
@EnableWebSocketMessageBroker
public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Authentication user = ... ; // access authentication header(s)
accessor.setUser(user);
}
return message;
}
});
}
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Authentication user = ... ; // access authentication header(s)
accessor.setUser(user);
}
return message;
}
});
}
}
----
Also note that when using Spring Security's authorization for messages, at present
@ -1674,16 +1668,16 @@ the class-level to share a common destination):
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Controller
public class PortfolioController {
@Controller
public class PortfolioController {
@MessageMapping("/trade")
@SendToUser("/queue/position-updates")
public TradeResult executeTrade(Trade trade, Principal principal) {
// ...
return tradeResult;
}
}
@MessageMapping("/trade")
@SendToUser("/queue/position-updates")
public TradeResult executeTrade(Trade trade, Principal principal) {
// ...
return tradeResult;
}
}
----
If the user has more than one session, by default all of the sessions subscribed
@ -1694,24 +1688,23 @@ setting the `broadcast` attribute to false, for example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Controller
public class MyController {
@Controller
public class MyController {
@MessageMapping("/action")
public void handleAction() throws Exception{
// raise MyBusinessException here
}
@MessageMapping("/action")
public void handleAction() throws Exception{
// raise MyBusinessException here
}
@MessageExceptionHandler
@SendToUser(destinations="/queue/errors", broadcast=false)
public ApplicationError handleException(MyBusinessException exception) {
// ...
return appError;
}
}
@MessageExceptionHandler
@SendToUser(destinations="/queue/errors", broadcast=false)
public ApplicationError handleException(MyBusinessException exception) {
// ...
return appError;
}
}
----
[NOTE]
====
While user destinations generally imply an authenticated user, it isn't required
@ -1817,15 +1810,15 @@ to intercept inbound messages:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new MyChannelInterceptor());
}
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new MyChannelInterceptor());
}
}
----
A custom `ChannelInterceptor` can extend the empty method base class
@ -1835,16 +1828,16 @@ to access information about the message.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
public class MyChannelInterceptor extends ChannelInterceptorAdapter {
public class MyChannelInterceptor extends ChannelInterceptorAdapter {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getStompCommand();
// ...
return message;
}
}
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getStompCommand();
// ...
return message;
}
}
----
@ -1859,10 +1852,10 @@ To begin create and configure `WebSocketStompClient`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setTaskScheduler(taskScheduler); // for heartbeats
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setTaskScheduler(taskScheduler); // for heartbeats
----
In the above example `StandardWebSocketClient` could be replaced with `SockJsClient`
@ -1875,9 +1868,9 @@ Next establish a connection and provide a handler for the STOMP session:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String url = "ws://127.0.0.1:8080/endpoint";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(url, sessionHandler);
String url = "ws://127.0.0.1:8080/endpoint";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(url, sessionHandler);
----
When the session is ready for use the handler is notified:
@ -1887,10 +1880,10 @@ When the session is ready for use the handler is notified:
----
public class MyStompSessionHandler extends StompSessionHandlerAdapter {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
// ...
}
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
// ...
}
}
----
@ -1913,15 +1906,15 @@ Object type the payload should be deserialized to:
----
session.subscribe("/topic/foo", new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
// ...
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
// ...
}
});
----
@ -1970,11 +1963,11 @@ inbound client messages and may be accessed from a controller method, for exampl
@Controller
public class MyController {
@MessageMapping("/action")
public void handle(SimpMessageHeaderAccessor headerAccessor) {
Map<String, Object> attrs = headerAccessor.getSessionAttributes();
// ...
}
@MessageMapping("/action")
public void handle(SimpMessageHeaderAccessor headerAccessor) {
Map<String, Object> attrs = headerAccessor.getSessionAttributes();
// ...
}
}
----
@ -1987,38 +1980,38 @@ scope proxy mode for WebSocket-scoped beans:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {
@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {
@PostConstruct
public void init() {
// Invoked after dependencies injected
}
@PostConstruct
public void init() {
// Invoked after dependencies injected
}
// ...
// ...
@PreDestroy
public void destroy() {
// Invoked when the WebSocket session ends
}
}
@PreDestroy
public void destroy() {
// Invoked when the WebSocket session ends
}
}
@Controller
public class MyController {
@Controller
public class MyController {
private final MyBean myBean;
private final MyBean myBean;
@Autowired
public MyController(MyBean myBean) {
this.myBean = myBean;
}
@Autowired
public MyController(MyBean myBean) {
this.myBean = myBean;
}
@MessageMapping("/action")
public void handle() {
// this.myBean from the current WebSocket session
}
}
@MessageMapping("/action")
public void handle() {
// this.myBean from the current WebSocket session
}
}
----
As with any custom scope, Spring initializes a new `MyBean` instance the first
@ -2205,49 +2198,49 @@ every 30 minutes. This bean can be exported to JMX through Spring's
Below is a summary of the available information.
Client WebSocket Sessions::
Current::: indicates how many client sessions there are
currently with the count further broken down by WebSocket vs HTTP
streaming and polling SockJS sessions.
Total::: indicates how many total sessions have been established.
Abnormally Closed:::
Connect Failures:::: these are sessions that got established but were
closed after not having received any messages within 60 seconds. This is
usually an indication of proxy or network issues.
Send Limit Exceeded:::: sessions closed after exceeding the configured send
timeout or the send buffer limits which can occur with slow clients
(see previous section).
Transport Errors:::: sessions closed after a transport error such as
failure to read or write to a WebSocket connection or
HTTP request/response.
STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames
processed indicating how many clients connected on the STOMP level. Note that
the DISCONNECT count may be lower when sessions get closed abnormally or when
clients close without sending a DISCONNECT frame.
Current::: indicates how many client sessions there are
currently with the count further broken down by WebSocket vs HTTP
streaming and polling SockJS sessions.
Total::: indicates how many total sessions have been established.
Abnormally Closed:::
Connect Failures:::: these are sessions that got established but were
closed after not having received any messages within 60 seconds. This is
usually an indication of proxy or network issues.
Send Limit Exceeded:::: sessions closed after exceeding the configured send
timeout or the send buffer limits which can occur with slow clients
(see previous section).
Transport Errors:::: sessions closed after a transport error such as
failure to read or write to a WebSocket connection or
HTTP request/response.
STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames
processed indicating how many clients connected on the STOMP level. Note that
the DISCONNECT count may be lower when sessions get closed abnormally or when
clients close without sending a DISCONNECT frame.
STOMP Broker Relay::
TCP Connections::: indicates how many TCP connections on behalf of client
WebSocket sessions are established to the broker. This should be equal to the
number of client WebSocket sessions + 1 additional shared "system" connection
for sending messages from within the application.
STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames
forwarded to or received from the broker on behalf of clients. Note that a
DISCONNECT frame is sent to the broker regardless of how the client WebSocket
session was closed. Therefore a lower DISCONNECT frame count is an indication
that the broker is pro-actively closing connections, may be because of a
heartbeat that didn't arrive in time, an invalid input frame, or other.
TCP Connections::: indicates how many TCP connections on behalf of client
WebSocket sessions are established to the broker. This should be equal to the
number of client WebSocket sessions + 1 additional shared "system" connection
for sending messages from within the application.
STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames
forwarded to or received from the broker on behalf of clients. Note that a
DISCONNECT frame is sent to the broker regardless of how the client WebSocket
session was closed. Therefore a lower DISCONNECT frame count is an indication
that the broker is pro-actively closing connections, may be because of a
heartbeat that didn't arrive in time, an invalid input frame, or other.
Client Inbound Channel:: stats from thread pool backing the "clientInboundChannel"
providing insight into the health of incoming message processing. Tasks queueing
up here is an indication the application may be too slow to handle messages.
If there I/O bound tasks (e.g. slow database query, HTTP request to 3rd party
REST API, etc) consider increasing the thread pool size.
providing insight into the health of incoming message processing. Tasks queueing
up here is an indication the application may be too slow to handle messages.
If there I/O bound tasks (e.g. slow database query, HTTP request to 3rd party
REST API, etc) consider increasing the thread pool size.
Client Outbound Channel:: stats from the thread pool backing the "clientOutboundChannel"
providing insight into the health of broadcasting messages to clients. Tasks
queueing up here is an indication clients are too slow to consume messages.
One way to address this is to increase the thread pool size to accommodate the
number of concurrent slow clients expected. Another option is to reduce the
send timeout and send buffer size limits (see the previous section).
providing insight into the health of broadcasting messages to clients. Tasks
queueing up here is an indication clients are too slow to consume messages.
One way to address this is to increase the thread pool size to accommodate the
number of concurrent slow clients expected. Another option is to reduce the
send timeout and send buffer size limits (see the previous section).
SockJS Task Scheduler:: stats from thread pool of the SockJS task scheduler which
is used to send heartbeats. Note that when heartbeats are negotiated on the
STOMP level the SockJS heartbeats are disabled.
is used to send heartbeats. Note that when heartbeats are negotiated on the
STOMP level the SockJS heartbeats are disabled.