commit
bc7ba8cf2b
|
@ -605,11 +605,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
fun audit(auditable: Auditable) {
|
||||
|
@ -618,6 +618,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
======
|
||||
|
||||
[[aop-ataspectj-advice-params-generics]]
|
||||
=== Advice Parameters and Generics
|
||||
|
@ -770,12 +771,12 @@ Java::
|
|||
// ... use code and bean
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
|
@ -787,6 +788,7 @@ Java::
|
|||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
======
|
||||
|
||||
If the first parameter is of type `JoinPoint`, `ProceedingJoinPoint`, or
|
||||
`JoinPoint.StaticPart`, you can omit the name of the parameter from the value of the
|
||||
|
@ -807,12 +809,12 @@ Java::
|
|||
// ... use code, bean, and jp
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
|
@ -824,6 +826,7 @@ Java::
|
|||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
======
|
||||
|
||||
The special treatment given to the first parameter of type `JoinPoint`,
|
||||
`ProceedingJoinPoint`, or `JoinPoint.StaticPart` is particularly convenient for advice
|
||||
|
@ -842,11 +845,11 @@ Java::
|
|||
// ... use jp
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
fun audit(jp: JoinPoint) {
|
||||
|
@ -854,6 +857,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
======
|
||||
|
||||
|
||||
[[aop-ataspectj-advice-proceeding-with-the-call]]
|
||||
|
@ -879,11 +883,11 @@ Java::
|
|||
return pjp.proceed(new Object[] {newPattern});
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> References the `inDataAccessLayer` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
|
@ -895,6 +899,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> References the `inDataAccessLayer` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
In many cases, you do this binding anyway (as in the preceding example).
|
||||
|
||||
|
|
|
@ -59,11 +59,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Aspect
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
@ -102,6 +102,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
|
||||
the aspect higher than the transaction advice (we want a fresh transaction each time we
|
||||
|
|
|
@ -167,15 +167,15 @@ Java::
|
|||
public void tradingOperation() {} // <3>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `publicMethod` matches if a method execution join point represents the execution
|
||||
of any public method.
|
||||
<2> `inTrading` matches if a method execution is in the trading module.
|
||||
<3> `tradingOperation` matches if a method execution represents any public method in the
|
||||
trading module.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
|
@ -197,6 +197,7 @@ of any public method.
|
|||
<2> `inTrading` matches if a method execution is in the trading module.
|
||||
<3> `tradingOperation` matches if a method execution represents any public method in the
|
||||
trading module.
|
||||
======
|
||||
|
||||
It is a best practice to build more complex pointcut expressions out of smaller _named
|
||||
pointcuts_, as shown above. When referring to pointcuts by name, normal Java visibility
|
||||
|
|
|
@ -233,14 +233,14 @@ Java::
|
|||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> We use the Spring-provided `AbstractSingleBeanDefinitionParser` to handle a lot of
|
||||
the basic grunt work of creating a single `BeanDefinition`.
|
||||
<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our
|
||||
single `BeanDefinition` represents.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
|
||||
|
@ -274,6 +274,7 @@ single `BeanDefinition` represents.
|
|||
the basic grunt work of creating a single `BeanDefinition`.
|
||||
<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our
|
||||
single `BeanDefinition` represents.
|
||||
======
|
||||
|
||||
|
||||
In this simple case, this is all that we need to do. The creation of our single
|
||||
|
@ -540,7 +541,7 @@ Kotlin::
|
|||
fun setChildren(children: List<Component>) {
|
||||
this.children = children
|
||||
}
|
||||
|
||||
|
||||
override fun getObject(): Component? {
|
||||
if (this.children != null && this.children!!.isNotEmpty()) {
|
||||
for (child in children!!) {
|
||||
|
|
|
@ -374,11 +374,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> This line adds the `@Offline` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
|
@ -390,6 +390,7 @@ class MovieRecommender {
|
|||
}
|
||||
----
|
||||
<1> This line adds the `@Offline` annotation.
|
||||
======
|
||||
--
|
||||
|
||||
Now the bean definition only needs a qualifier `type`, as shown in the following example:
|
||||
|
|
|
@ -27,11 +27,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> This line injects a `@Resource`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
|
@ -40,6 +40,7 @@ class SimpleMovieLister {
|
|||
}
|
||||
----
|
||||
<1> This line injects a `@Resource`.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
@ -118,12 +119,12 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> The `context` field is injected based on the known resolvable dependency type:
|
||||
`ApplicationContext`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
|
@ -139,5 +140,6 @@ Java::
|
|||
----
|
||||
<1> The `context` field is injected based on the known resolvable dependency type:
|
||||
`ApplicationContext`.
|
||||
======
|
||||
--
|
||||
|
||||
|
|
|
@ -70,11 +70,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
@ -86,6 +86,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
======
|
||||
|
||||
You can also combine meta-annotations to create "`composed annotations`". For example,
|
||||
the `@RestController` annotation from Spring MVC is composed of `@Controller` and
|
||||
|
|
|
@ -184,11 +184,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `@Bean(destroyMethod = "")` disables default destroy method inference.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@Profile("production")
|
||||
|
@ -202,6 +202,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> `@Bean(destroyMethod = "")` disables default destroy method inference.
|
||||
======
|
||||
--
|
||||
|
||||
NOTE: As mentioned earlier, with `@Bean` methods, you typically choose to use programmatic
|
||||
|
@ -294,12 +295,12 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> The `standaloneDataSource` method is available only in the `development` profile.
|
||||
<2> The `jndiDataSource` method is available only in the `production` profile.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
@ -322,6 +323,7 @@ Java::
|
|||
----
|
||||
<1> The `standaloneDataSource` method is available only in the `development` profile.
|
||||
<2> The `jndiDataSource` method is available only in the `production` profile.
|
||||
======
|
||||
--
|
||||
|
||||
[NOTE]
|
||||
|
|
|
@ -144,11 +144,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> This annotation enables component scanning.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["com.acme"]) // <1>
|
||||
|
@ -157,6 +157,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> This annotation enables component scanning.
|
||||
======
|
||||
|
||||
|
||||
[TIP]
|
||||
|
|
|
@ -18,17 +18,18 @@ Java::
|
|||
Expression exp = parser.parseExpression("'Hello World'"); // <1>
|
||||
String message = (String) exp.getValue();
|
||||
----
|
||||
======
|
||||
<1> The value of the message variable is `'Hello World'`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val exp = parser.parseExpression("'Hello World'") // <1>
|
||||
val message = exp.value as String
|
||||
----
|
||||
<1> The value of the message variable is `'Hello World'`.
|
||||
======
|
||||
|
||||
|
||||
The SpEL classes and interfaces you are most likely to use are located in the
|
||||
|
@ -56,17 +57,18 @@ Java::
|
|||
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); // <1>
|
||||
String message = (String) exp.getValue();
|
||||
----
|
||||
======
|
||||
<1> The value of `message` is now 'Hello World!'.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val exp = parser.parseExpression("'Hello World'.concat('!')") // <1>
|
||||
val message = exp.value as String
|
||||
----
|
||||
<1> The value of `message` is now 'Hello World!'.
|
||||
======
|
||||
|
||||
The following example of calling a JavaBean property calls the `String` property `Bytes`:
|
||||
|
||||
|
@ -82,11 +84,11 @@ Java::
|
|||
Expression exp = parser.parseExpression("'Hello World'.bytes"); // <1>
|
||||
byte[] bytes = (byte[]) exp.getValue();
|
||||
----
|
||||
======
|
||||
<1> This line converts the literal to a byte array.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
|
@ -95,6 +97,7 @@ Java::
|
|||
val bytes = exp.value as ByteArray
|
||||
----
|
||||
<1> This line converts the literal to a byte array.
|
||||
======
|
||||
|
||||
SpEL also supports nested properties by using the standard dot notation (such as
|
||||
`prop1.prop2.prop3`) and also the corresponding setting of property values.
|
||||
|
@ -114,11 +117,11 @@ Java::
|
|||
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); // <1>
|
||||
int length = (Integer) exp.getValue();
|
||||
----
|
||||
======
|
||||
<1> `'Hello World'.bytes.length` gives the length of the literal.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
|
@ -127,6 +130,7 @@ Java::
|
|||
val length = exp.value as Int
|
||||
----
|
||||
<1> `'Hello World'.bytes.length` gives the length of the literal.
|
||||
======
|
||||
|
||||
The String's constructor can be called instead of using a string literal, as the following
|
||||
example shows:
|
||||
|
@ -141,17 +145,18 @@ Java::
|
|||
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // <1>
|
||||
String message = exp.getValue(String.class);
|
||||
----
|
||||
======
|
||||
<1> Construct a new `String` from the literal and make it be upper case.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val exp = parser.parseExpression("new String('hello world').toUpperCase()") // <1>
|
||||
val message = exp.getValue(String::class.java)
|
||||
----
|
||||
<1> Construct a new `String` from the literal and make it be upper case.
|
||||
======
|
||||
|
||||
|
||||
Note the use of the generic method: `public <T> T getValue(Class<T> desiredResultType)`.
|
||||
|
|
|
@ -63,11 +63,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> The `@Repository` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository // <1>
|
||||
class SomeMovieFinder : MovieFinder {
|
||||
|
@ -75,6 +75,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> The `@Repository` annotation.
|
||||
======
|
||||
|
||||
|
||||
Any DAO or repository implementation needs access to a persistence resource,
|
||||
|
|
|
@ -441,13 +441,13 @@ Java::
|
|||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Annotate the `DataSource` setter method with `@Autowired`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository // <1>
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { // <2>
|
||||
|
@ -460,6 +460,7 @@ Java::
|
|||
<1> Annotate the class with `@Repository`.
|
||||
<2> Constructor injection of the `DataSource`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
|
|
@ -89,13 +89,13 @@ Java::
|
|||
blobIs.close();
|
||||
clobReader.close();
|
||||
----
|
||||
======
|
||||
<1> Pass in the `lobHandler` that (in this example) is a plain `DefaultLobHandler`.
|
||||
<2> Using the method `setClobAsCharacterStream` to pass in the contents of the CLOB.
|
||||
<3> Using the method `setBlobAsBinaryStream` to pass in the contents of the BLOB.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val blobIn = File("spring2004.jpg")
|
||||
val blobIs = FileInputStream(blobIn)
|
||||
|
@ -119,6 +119,7 @@ Java::
|
|||
<1> Pass in the `lobHandler` that (in this example) is a plain `DefaultLobHandler`.
|
||||
<2> Using the method `setClobAsCharacterStream` to pass in the contents of the CLOB.
|
||||
<3> Using the method `setBlobAsBinaryStream` to pass in the contents of the BLOB.
|
||||
======
|
||||
|
||||
|
||||
[NOTE]
|
||||
|
@ -156,12 +157,12 @@ Java::
|
|||
}
|
||||
});
|
||||
----
|
||||
======
|
||||
<1> Using the method `getClobAsString` to retrieve the contents of the CLOB.
|
||||
<2> Using the method `getBlobAsBytes` to retrieve the contents of the BLOB.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table") { rs, _ ->
|
||||
val clobText = lobHandler.getClobAsString(rs, "a_clob") // <1>
|
||||
|
@ -171,6 +172,7 @@ Java::
|
|||
----
|
||||
<1> Using the method `getClobAsString` to retrieve the contents of the CLOB.
|
||||
<2> Using the method `getBlobAsBytes` to retrieve the contents of the BLOB.
|
||||
======
|
||||
|
||||
|
||||
[[jdbc-in-clause]]
|
||||
|
|
|
@ -547,13 +547,13 @@ Java::
|
|||
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Annotate the class with `@Component`.
|
||||
<2> Annotate the `ConnectionFactory` setter method with `@Autowired`.
|
||||
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Component // <1>
|
||||
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao { // <2>
|
||||
|
@ -566,6 +566,7 @@ Java::
|
|||
<1> Annotate the class with `@Component`.
|
||||
<2> Constructor injection of the `ConnectionFactory`.
|
||||
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
|
||||
======
|
||||
--
|
||||
|
||||
Regardless of which of the above template initialization styles you choose to use (or
|
||||
|
|
|
@ -299,14 +299,14 @@ Java::
|
|||
.rsocketConnector(connector -> connector.acceptor(responder)) // <3>
|
||||
.tcp("localhost", 7000);
|
||||
----
|
||||
======
|
||||
<1> Use `PathPatternRouteMatcher`, if `spring-web` is present, for efficient
|
||||
route matching.
|
||||
<2> Create a responder from a class with `@MessageMapping` and/or `@ConnectMapping` methods.
|
||||
<3> Register the responder.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val strategies = RSocketStrategies.builder()
|
||||
.routeMatcher(PathPatternRouteMatcher()) // <1>
|
||||
|
@ -323,6 +323,7 @@ Java::
|
|||
route matching.
|
||||
<2> Create a responder from a class with `@MessageMapping` and/or `@ConnectMapping` methods.
|
||||
<3> Register the responder.
|
||||
======
|
||||
|
||||
Note the above is only a shortcut designed for programmatic registration of client
|
||||
responders. For alternative scenarios, where client responders are in Spring configuration,
|
||||
|
@ -428,12 +429,12 @@ Java::
|
|||
return ... // <2>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Start the request asynchronously, independent from handling.
|
||||
<2> Perform handling and return completion `Mono<Void>`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ConnectMapping
|
||||
suspend fun handle(requester: RSocketRequester) {
|
||||
|
@ -447,6 +448,7 @@ Java::
|
|||
----
|
||||
<1> Start the request asynchronously, independent from handling.
|
||||
<2> Perform handling in the suspending function.
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
@ -469,13 +471,13 @@ Java::
|
|||
.retrieveFlux(AirportLocation.class); // <3>
|
||||
|
||||
----
|
||||
======
|
||||
<1> Specify a route to include in the metadata of the request message.
|
||||
<2> Provide data for the request message.
|
||||
<3> Declare the expected response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val viewBox: ViewBox = ...
|
||||
|
||||
|
@ -486,6 +488,7 @@ Java::
|
|||
<1> Specify a route to include in the metadata of the request message.
|
||||
<2> Provide data for the request message.
|
||||
<3> Declare the expected response.
|
||||
======
|
||||
|
||||
The interaction type is determined implicitly from the cardinality of the input and
|
||||
output. The above example is a `Request-Stream` because one value is sent and a stream
|
||||
|
|
|
@ -36,11 +36,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specify the configuration class.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class) // <1>
|
||||
class ConfigurationClassJUnitJupiterSpringTests {
|
||||
|
@ -48,6 +48,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specify the configuration class.
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to use the `@SpringJUnitConfig` annotation to specify the
|
||||
|
@ -64,11 +65,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specify the location of a configuration file.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(locations = ["/test-config.xml"]) // <1>
|
||||
class XmlJUnitJupiterSpringTests {
|
||||
|
@ -76,6 +77,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specify the location of a configuration file.
|
||||
======
|
||||
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management.adoc[Context Management] as well as the javadoc for
|
||||
|
@ -109,11 +111,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specify the configuration class.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitWebConfig(TestConfig::class) // <1>
|
||||
class ConfigurationClassJUnitJupiterSpringWebTests {
|
||||
|
@ -121,6 +123,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specify the configuration class.
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify the
|
||||
|
@ -137,11 +140,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specify the location of a configuration file.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitWebConfig(locations = ["/test-config.xml"]) // <1>
|
||||
class XmlJUnitJupiterSpringWebTests {
|
||||
|
@ -149,6 +152,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specify the location of a configuration file.
|
||||
======
|
||||
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management.adoc[Context Management] as well as the javadoc for
|
||||
|
|
|
@ -39,11 +39,11 @@ Java::
|
|||
// some logic that should run only on Java VMs from Oracle Corporation
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Run this test only when the Java vendor is "Oracle Corporation".
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1>
|
||||
@Test
|
||||
|
@ -52,6 +52,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Run this test only when the Java vendor is "Oracle Corporation".
|
||||
======
|
||||
|
||||
|
||||
Alternatively, you can configure `@IfProfileValue` with a list of `values` (with `OR`
|
||||
|
@ -70,11 +71,11 @@ Java::
|
|||
// some logic that should run only for unit and integration test groups
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Run this test for unit tests and integration tests.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) // <1>
|
||||
@Test
|
||||
|
@ -83,6 +84,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Run this test for unit tests and integration tests.
|
||||
======
|
||||
|
||||
|
||||
[[integration-testing-annotations-junit4-profilevaluesourceconfiguration]]
|
||||
|
@ -105,11 +107,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Use a custom profile value source.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ProfileValueSourceConfiguration(CustomProfileValueSource::class) // <1>
|
||||
class CustomProfileValueSourceTests {
|
||||
|
@ -117,6 +119,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Use a custom profile value source.
|
||||
======
|
||||
|
||||
|
||||
[[integration-testing-annotations-junit4-timed]]
|
||||
|
@ -141,11 +144,11 @@ Java::
|
|||
// some logic that should not take longer than 1 second to run
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Set the time period for the test to one second.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Timed(millis = 1000) // <1>
|
||||
fun testProcessWithOneSecondTimeout() {
|
||||
|
@ -153,6 +156,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Set the time period for the test to one second.
|
||||
======
|
||||
|
||||
|
||||
Spring's `@Timed` annotation has different semantics than JUnit 4's `@Test(timeout=...)`
|
||||
|
@ -186,11 +190,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Repeat this test ten times.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repeat(10) // <1>
|
||||
@Test
|
||||
|
@ -199,6 +203,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Repeat this test ten times.
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Indicate that the `dev` profile should be active.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@ActiveProfiles("dev") // <1>
|
||||
|
@ -32,6 +32,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Indicate that the `dev` profile should be active.
|
||||
======
|
||||
|
||||
|
||||
The following example indicates that both the `dev` and the `integration` profiles should
|
||||
|
@ -49,11 +50,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Indicate that the `dev` and `integration` profiles should be active.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@ActiveProfiles(["dev", "integration"]) // <1>
|
||||
|
@ -62,6 +63,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Indicate that the `dev` and `integration` profiles should be active.
|
||||
======
|
||||
|
||||
|
||||
NOTE: `@ActiveProfiles` provides support for inheriting active bean definition profiles
|
||||
|
|
|
@ -18,11 +18,11 @@ Java::
|
|||
// logic to be run after a transaction has ended
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Run this method after a transaction.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@AfterTransaction // <1>
|
||||
fun afterTransaction() {
|
||||
|
@ -30,5 +30,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Run this method after a transaction.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -20,11 +20,11 @@ Java::
|
|||
// logic to be run before a transaction is started
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Run this method before a transaction.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@BeforeTransaction // <1>
|
||||
fun beforeTransaction() {
|
||||
|
@ -32,5 +32,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Run this method before a transaction.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -21,11 +21,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Commit the result of the test to the database.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Commit // <1>
|
||||
@Test
|
||||
|
@ -34,5 +34,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Commit the result of the test to the database.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -26,11 +26,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Referring to an XML file.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration("/test-config.xml") // <1>
|
||||
class XmlApplicationContextTests {
|
||||
|
@ -38,6 +38,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Referring to an XML file.
|
||||
======
|
||||
|
||||
|
||||
The following example shows a `@ContextConfiguration` annotation that refers to a class:
|
||||
|
@ -53,11 +54,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Referring to a class.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration(classes = [TestConfig::class]) // <1>
|
||||
class ConfigClassApplicationContextTests {
|
||||
|
@ -65,6 +66,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Referring to a class.
|
||||
======
|
||||
|
||||
|
||||
As an alternative or in addition to declaring resource locations or component classes,
|
||||
|
@ -82,11 +84,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Declaring an initializer class.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration(initializers = [CustomContextInitializer::class]) // <1>
|
||||
class ContextInitializerTests {
|
||||
|
@ -94,6 +96,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Declaring an initializer class.
|
||||
======
|
||||
|
||||
|
||||
You can optionally use `@ContextConfiguration` to declare the `ContextLoader` strategy as
|
||||
|
@ -114,11 +117,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Configuring both a location and a custom loader.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) // <1>
|
||||
class CustomLoaderXmlApplicationContextTests {
|
||||
|
@ -126,6 +129,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Configuring both a location and a custom loader.
|
||||
======
|
||||
|
||||
|
||||
NOTE: `@ContextConfiguration` provides support for inheriting resource locations or
|
||||
|
|
|
@ -31,11 +31,11 @@ Java::
|
|||
// some tests that require a new Spring container
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Dirty the context before the current test class.
|
||||
+
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@DirtiesContext(classMode = BEFORE_CLASS) // <1>
|
||||
class FreshContextTests {
|
||||
|
@ -43,6 +43,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Dirty the context before the current test class.
|
||||
======
|
||||
|
||||
* After the current test class, when declared on a class with class mode set to
|
||||
`AFTER_CLASS` (i.e., the default class mode).
|
||||
|
@ -58,11 +59,11 @@ Java::
|
|||
// some tests that result in the Spring container being dirtied
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Dirty the context after the current test class.
|
||||
+
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@DirtiesContext // <1>
|
||||
class ContextDirtyingTests {
|
||||
|
@ -70,6 +71,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Dirty the context after the current test class.
|
||||
======
|
||||
|
||||
|
||||
* Before each test method in the current test class, when declared on a class with class
|
||||
|
@ -86,11 +88,11 @@ Java::
|
|||
// some tests that require a new Spring container
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Dirty the context before each test method.
|
||||
+
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1>
|
||||
class FreshContextTests {
|
||||
|
@ -98,6 +100,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Dirty the context before each test method.
|
||||
======
|
||||
|
||||
|
||||
* After each test method in the current test class, when declared on a class with class
|
||||
|
@ -114,11 +117,11 @@ Java::
|
|||
// some tests that result in the Spring container being dirtied
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Dirty the context after each test method.
|
||||
+
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1>
|
||||
class ContextDirtyingTests {
|
||||
|
@ -126,6 +129,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Dirty the context after each test method.
|
||||
======
|
||||
|
||||
|
||||
* Before the current test, when declared on a method with the method mode set to
|
||||
|
@ -143,11 +147,11 @@ Java::
|
|||
// some logic that requires a new Spring container
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Dirty the context before the current test method.
|
||||
+
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@DirtiesContext(methodMode = BEFORE_METHOD) // <1>
|
||||
@Test
|
||||
|
@ -156,6 +160,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Dirty the context before the current test method.
|
||||
======
|
||||
|
||||
* After the current test, when declared on a method with the method mode set to
|
||||
`AFTER_METHOD` (i.e., the default method mode).
|
||||
|
@ -172,11 +177,11 @@ Java::
|
|||
// some logic that results in the Spring container being dirtied
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Dirty the context after the current test method.
|
||||
+
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@DirtiesContext // <1>
|
||||
@Test
|
||||
|
@ -185,6 +190,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Dirty the context after the current test method.
|
||||
======
|
||||
|
||||
|
||||
If you use `@DirtiesContext` in a test whose context is configured as part of a context
|
||||
|
@ -220,11 +226,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Use the current-level algorithm.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextHierarchy(
|
||||
ContextConfiguration("/parent-config.xml"),
|
||||
|
@ -243,6 +249,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Use the current-level algorithm.
|
||||
======
|
||||
|
||||
|
||||
For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms, see the
|
||||
|
|
|
@ -29,13 +29,13 @@ Java::
|
|||
// tests ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Annotate a `static` method with `@DynamicPropertySource`.
|
||||
<2> Accept a `DynamicPropertyRegistry` as an argument.
|
||||
<3> Register a dynamic `server.port` property to be retrieved lazily from the server.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
class MyIntegrationTests {
|
||||
|
@ -58,6 +58,7 @@ Java::
|
|||
<1> Annotate a `static` method with `@DynamicPropertySource`.
|
||||
<2> Accept a `DynamicPropertyRegistry` as an argument.
|
||||
<3> Register a dynamic `server.port` property to be retrieved lazily from the server.
|
||||
======
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[Context Configuration with Dynamic Property Sources] for further details.
|
||||
|
||||
|
|
|
@ -27,11 +27,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Do not roll back the result.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Rollback(false) // <1>
|
||||
@Test
|
||||
|
@ -40,5 +40,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Do not roll back the result.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -17,11 +17,11 @@ Java::
|
|||
// run code that relies on the test schema and test data
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Run two scripts for this test.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Test
|
||||
@Sql("/test-schema.sql", "/test-user-data.sql") // <1>
|
||||
|
@ -30,6 +30,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Run two scripts for this test.
|
||||
======
|
||||
|
||||
See xref:testing/testcontext-framework/executing-sql.adoc#testcontext-executing-sql-declaratively[Executing SQL scripts declaratively with @Sql] for further details.
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ Java::
|
|||
// run code that relies on the test data
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Set the comment prefix and the separator in SQL scripts.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Test
|
||||
@Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) // <1>
|
||||
|
@ -32,4 +32,5 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Set the comment prefix and the separator in SQL scripts.
|
||||
======
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@ Java::
|
|||
// run code that uses the test schema and test data
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Declare a group of SQL scripts.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Test
|
||||
@SqlGroup( // <1>
|
||||
|
@ -37,6 +37,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Declare a group of SQL scripts.
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -29,11 +29,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@Sql("/test-schema.sql")
|
||||
|
@ -48,6 +48,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class.
|
||||
======
|
||||
|
||||
The following example shows how to use `@SqlMergeMode` at the method level.
|
||||
|
||||
|
@ -69,11 +70,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Set the `@Sql` merge mode to `MERGE` for a specific test method.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@Sql("/test-schema.sql")
|
||||
|
@ -88,5 +89,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Set the `@Sql` merge mode to `MERGE` for a specific test method.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -20,11 +20,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Register two `TestExecutionListener` implementations.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) // <1>
|
||||
|
@ -33,11 +33,12 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Register two `TestExecutionListener` implementations.
|
||||
======
|
||||
|
||||
|
||||
By default, `@TestExecutionListeners` provides support for inheriting listeners from
|
||||
superclasses or enclosing classes. See
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration] and the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration] and the
|
||||
{api-spring-framework}/test/context/TestExecutionListeners.html[`@TestExecutionListeners`
|
||||
javadoc] for an example and further details. If you discover that you need to switch
|
||||
back to using the default `TestExecutionListener` implementations, see the note
|
||||
|
|
|
@ -20,11 +20,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get properties from `test.properties` in the root of the classpath.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource("/test.properties") // <1>
|
||||
|
@ -33,6 +33,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Get properties from `test.properties` in the root of the classpath.
|
||||
======
|
||||
|
||||
|
||||
The following example demonstrates how to declare inlined properties:
|
||||
|
@ -49,11 +50,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Declare `timezone` and `port` properties.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1>
|
||||
|
@ -62,6 +63,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Declare `timezone` and `port` properties.
|
||||
======
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management/property-sources.adoc[Context Configuration with Test Property Sources] for examples and further details.
|
||||
|
||||
|
|
|
@ -25,11 +25,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> The `@WebAppConfiguration` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration // <1>
|
||||
|
@ -38,6 +38,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> The `@WebAppConfiguration` annotation.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
@ -59,11 +60,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specifying a classpath resource.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration("classpath:test-web-resources") // <1>
|
||||
|
@ -72,6 +73,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specifying a classpath resource.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
|
|
@ -37,15 +37,15 @@ Java::
|
|||
.andExpect(content().string("body"));
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Check response status is still unchanged
|
||||
<2> Async processing must have started
|
||||
<3> Wait and assert the async result
|
||||
<4> Manually perform an ASYNC dispatch (as there is no running container)
|
||||
<5> Verify the final response
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Test
|
||||
fun test() {
|
||||
|
@ -69,5 +69,6 @@ Java::
|
|||
<3> Wait and assert the async result
|
||||
<4> Manually perform an ASYNC dispatch (as there is no running container)
|
||||
<5> Verify the final response
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -298,7 +298,6 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of
|
||||
`AbstractPage`, but, in summary, it contains common functionality for all of our pages.
|
||||
For example, if our application has a navigational bar, global error messages, and other
|
||||
|
@ -316,8 +315,9 @@ https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-
|
|||
to override the default lookup behavior. Our example shows how to use the `@FindBy`
|
||||
annotation to look up our submit button with a `css` selector (`input[type=submit]`).
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { // <1>
|
||||
|
||||
|
@ -358,6 +358,7 @@ by the `id` or `name` of the element within the HTML page.
|
|||
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
|
||||
to override the default lookup behavior. Our example shows how to use the `@FindBy`
|
||||
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
|
||||
======
|
||||
--
|
||||
|
||||
Finally, we can verify that a new message was created successfully. The following
|
||||
|
|
|
@ -54,14 +54,13 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Annotate the test class with `@RecordApplicationEvents`.
|
||||
<2> Inject the `ApplicationEvents` instance for the current test.
|
||||
<3> Use the `ApplicationEvents` API to count how many `OrderSubmitted` events were published.
|
||||
|
||||
// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(/* ... */)
|
||||
@RecordApplicationEvents // <1>
|
||||
|
@ -86,6 +85,7 @@ Java::
|
|||
<1> Annotate the test class with `@RecordApplicationEvents`.
|
||||
<2> Inject the `ApplicationEvents` instance for the current test.
|
||||
<3> Use the `ApplicationEvents` API to count how many `OrderSubmitted` events were published.
|
||||
======
|
||||
|
||||
See the
|
||||
{api-spring-framework}/test/context/event/ApplicationEvents.html[`ApplicationEvents`
|
||||
|
|
|
@ -31,11 +31,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Injecting the `ApplicationContext`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig
|
||||
class MyTest {
|
||||
|
@ -47,6 +47,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Injecting the `ApplicationContext`.
|
||||
======
|
||||
|
||||
|
||||
Similarly, if your test is configured to load a `WebApplicationContext`, you can inject
|
||||
|
@ -67,12 +68,12 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Configuring the `WebApplicationContext`.
|
||||
<2> Injecting the `WebApplicationContext`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitWebConfig // <1>
|
||||
class MyWebAppTest {
|
||||
|
@ -84,6 +85,7 @@ Java::
|
|||
----
|
||||
<1> Configuring the `WebApplicationContext`.
|
||||
<2> Injecting the `WebApplicationContext`.
|
||||
======
|
||||
|
||||
|
||||
Dependency injection by using `@Autowired` is provided by the
|
||||
|
|
|
@ -28,11 +28,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specifying the location of Groovy configuration files.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from "/AppConfig.groovy" and
|
||||
|
@ -43,6 +43,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specifying the location of Groovy configuration files.
|
||||
======
|
||||
|
||||
|
||||
If you omit both the `locations` and `value` attributes from the `@ContextConfiguration`
|
||||
|
@ -67,11 +68,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Loading configuration from the default location.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from
|
||||
|
@ -82,6 +83,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Loading configuration from the default location.
|
||||
======
|
||||
|
||||
|
||||
.Declaring XML configuration and Groovy scripts simultaneously
|
||||
|
|
|
@ -47,12 +47,12 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Configuration file defined in the superclass.
|
||||
<2> Configuration file defined in the subclass.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from "/base-config.xml"
|
||||
|
@ -71,6 +71,7 @@ Java::
|
|||
----
|
||||
<1> Configuration file defined in the superclass.
|
||||
<2> Configuration file defined in the subclass.
|
||||
======
|
||||
|
||||
|
||||
Similarly, in the next example, which uses component classes, the `ApplicationContext`
|
||||
|
@ -97,12 +98,12 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Configuration class defined in the superclass.
|
||||
<2> Configuration class defined in the subclass.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// ApplicationContext will be loaded from BaseConfig
|
||||
@SpringJUnitConfig(BaseConfig::class) // <1>
|
||||
|
@ -118,6 +119,7 @@ Java::
|
|||
----
|
||||
<1> Configuration class defined in the superclass.
|
||||
<2> Configuration class defined in the subclass.
|
||||
======
|
||||
|
||||
|
||||
In the next example, which uses context initializers, the `ApplicationContext` for
|
||||
|
@ -146,12 +148,12 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Initializer defined in the superclass.
|
||||
<2> Initializer defined in the subclass.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// ApplicationContext will be initialized by BaseInitializer
|
||||
@SpringJUnitConfig(initializers = [BaseInitializer::class]) // <1>
|
||||
|
@ -168,5 +170,6 @@ Java::
|
|||
----
|
||||
<1> Initializer defined in the superclass.
|
||||
<2> Initializer defined in the subclass.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -29,11 +29,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specifying configuration by using a configuration class and an initializer.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from TestConfig
|
||||
|
@ -46,6 +46,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specifying configuration by using a configuration class and an initializer.
|
||||
======
|
||||
|
||||
|
||||
You can also omit the declaration of XML configuration files, Groovy scripts, or
|
||||
|
@ -68,11 +69,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specifying configuration by using only an initializer.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be initialized by EntireAppInitializer
|
||||
|
@ -83,5 +84,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specifying configuration by using only an initializer.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specifying component classes.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from AppConfig and TestConfig
|
||||
|
@ -33,6 +33,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specifying component classes.
|
||||
======
|
||||
|
||||
|
||||
[[testcontext-ctx-management-javaconfig-component-classes]]
|
||||
|
@ -100,11 +101,11 @@ Java::
|
|||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Loading configuration information from the nested `Config` class.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig <1>
|
||||
// ApplicationContext will be loaded from the nested Config class
|
||||
|
@ -131,5 +132,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Loading configuration information from the nested `Config` class.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -52,11 +52,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specifying a properties file with an absolute path.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource("/test.properties") // <1>
|
||||
|
@ -65,6 +65,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specifying a properties file with an absolute path.
|
||||
======
|
||||
|
||||
|
||||
You can configure inlined properties in the form of key-value pairs by using the
|
||||
|
@ -93,11 +94,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Setting two properties by using two variations of the key-value syntax.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1>
|
||||
|
@ -106,6 +107,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Setting two properties by using two variations of the key-value syntax.
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
|
|
@ -24,11 +24,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Setting the locations attribute to a list of XML files.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from "/app-config.xml" and
|
||||
|
@ -39,6 +39,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Setting the locations attribute to a list of XML files.
|
||||
======
|
||||
|
||||
|
||||
`@ContextConfiguration` supports an alias for the `locations` attribute through the
|
||||
|
@ -59,11 +60,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specifying XML files without using the `locations` attribute.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-config.xml") // <1>
|
||||
|
@ -72,6 +73,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Specifying XML files without using the `locations` attribute.
|
||||
======
|
||||
|
||||
|
||||
If you omit both the `locations` and the `value` attributes from the
|
||||
|
@ -96,11 +98,11 @@ Java::
|
|||
// class body...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Loading configuration from the default location.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from
|
||||
|
@ -111,5 +113,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Loading configuration from the default location.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -105,13 +105,13 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(WebConfig::class) // <1>
|
||||
class MyTests {
|
||||
|
@ -127,6 +127,7 @@ Java::
|
|||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
======
|
||||
|
||||
For Spring MVC, use the following where the Spring `ApplicationContext` is passed to
|
||||
{api-spring-framework}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-[MockMvcBuilders.webAppContextSetup]
|
||||
|
@ -158,13 +159,13 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
|
||||
|
@ -188,6 +189,7 @@ Java::
|
|||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -221,12 +221,12 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@CrossOrigin` at the class level.
|
||||
<2> Using `@CrossOrigin` at the method level.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600) // <1>
|
||||
@RestController
|
||||
|
@ -247,6 +247,7 @@ Java::
|
|||
----
|
||||
<1> Using `@CrossOrigin` at the class level.
|
||||
<2> Using `@CrossOrigin` at the method level.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
|
|
@ -67,11 +67,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create router using `route()`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val repository: PersonRepository = ...
|
||||
val handler = PersonHandler(repository)
|
||||
|
@ -103,6 +103,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Create router using Coroutines router DSL; a Reactive alternative is also available via `router { }`.
|
||||
======
|
||||
|
||||
One way to run a `RouterFunction` is to turn it into an `HttpHandler` and install it
|
||||
through one of the built-in xref:web/webflux/reactive-spring.adoc#webflux-httphandler[server adapters]:
|
||||
|
@ -436,7 +437,6 @@ public class PersonHandler {
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
|
@ -448,8 +448,9 @@ when the `Person` has been saved).
|
|||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we use `switchIfEmpty(Mono<T>)` to return a 404 Not Found response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
|
@ -479,6 +480,7 @@ Note that `PersonRepository.savePerson(Person)` is a suspending function with no
|
|||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we return a 404 Not Found response.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
@ -515,13 +517,13 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
|
@ -548,6 +550,7 @@ Java::
|
|||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
======
|
||||
|
||||
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
|
||||
a global `Validator` instance based on `LocalValidatorFactoryBean`.
|
||||
|
@ -666,7 +669,6 @@ RouterFunction<ServerResponse> route = route()
|
|||
.add(otherRoute) // <4>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
|
@ -675,8 +677,9 @@ RouterFunction<ServerResponse> route = route()
|
|||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||
|
||||
|
@ -698,6 +701,7 @@ RouterFunction<ServerResponse> route = route()
|
|||
<3> `POST /person` with no additional predicates is mapped to
|
||||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
======
|
||||
|
||||
|
||||
[[nested-routes]]
|
||||
|
@ -724,11 +728,11 @@ RouterFunction<ServerResponse> route = route()
|
|||
.POST(handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> Note that second parameter of `path` is a consumer that takes the router builder.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = coRouter { // <1>
|
||||
"/person".nest {
|
||||
|
@ -739,6 +743,7 @@ RouterFunction<ServerResponse> route = route()
|
|||
}
|
||||
----
|
||||
<1> Create router using Coroutines router DSL; a Reactive alternative is also available via `router { }`.
|
||||
======
|
||||
|
||||
Though path-based nesting is the most common, you can nest on any kind of predicate by using
|
||||
the `nest` method on the builder.
|
||||
|
@ -918,12 +923,12 @@ Java::
|
|||
.after((request, response) -> logResponse(response)) // <2>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = router {
|
||||
"/person".nest {
|
||||
|
@ -942,6 +947,7 @@ Java::
|
|||
----
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
======
|
||||
|
||||
|
||||
The `filter` method on the router builder takes a `HandlerFilterFunction`: a
|
||||
|
|
|
@ -214,13 +214,13 @@ Java::
|
|||
return WebClient.builder().clientConnector(connector).build(); // <3>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create resources independent of global ones.
|
||||
<2> Use the `ReactorClientHttpConnector` constructor with resource factory.
|
||||
<3> Plug the connector into the `WebClient.Builder`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Bean
|
||||
fun resourceFactory() = ReactorResourceFactory().apply {
|
||||
|
@ -242,6 +242,7 @@ Java::
|
|||
<1> Create resources independent of global ones.
|
||||
<2> Use the `ReactorClientHttpConnector` constructor with resource factory.
|
||||
<3> Plug the connector into the `WebClient.Builder`.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
@ -483,12 +484,12 @@ Java::
|
|||
return WebClient.builder().clientConnector(connector).build(); <2>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Use the `JettyClientHttpConnector` constructor with resource factory.
|
||||
<2> Plug the connector into the `WebClient.Builder`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Bean
|
||||
fun resourceFactory() = JettyResourceFactory()
|
||||
|
@ -506,6 +507,7 @@ Java::
|
|||
----
|
||||
<1> Use the `JettyClientHttpConnector` constructor with resource factory.
|
||||
<2> Plug the connector into the `WebClient.Builder`.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
|
|
@ -194,14 +194,14 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Access the stream of inbound messages.
|
||||
<2> Do something with each message.
|
||||
<3> Perform nested asynchronous operations that use the message content.
|
||||
<4> Return a `Mono<Void>` that completes when receiving completes.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ExampleHandler : WebSocketHandler {
|
||||
|
||||
|
@ -221,6 +221,7 @@ Java::
|
|||
<2> Do something with each message.
|
||||
<3> Perform nested asynchronous operations that use the message content.
|
||||
<4> Return a `Mono<Void>` that completes when receiving completes.
|
||||
======
|
||||
|
||||
|
||||
TIP: For nested, asynchronous operations, you may need to call `message.retain()` on underlying
|
||||
|
@ -254,13 +255,13 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Handle the inbound message stream.
|
||||
<2> Create the outbound message, producing a combined flow.
|
||||
<3> Return a `Mono<Void>` that does not complete while we continue to receive.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ExampleHandler : WebSocketHandler {
|
||||
|
||||
|
@ -282,6 +283,7 @@ Java::
|
|||
<1> Handle the inbound message stream.
|
||||
<2> Create the outbound message, producing a combined flow.
|
||||
<3> Return a `Mono<Void>` that does not complete while we continue to receive.
|
||||
======
|
||||
|
||||
|
||||
Inbound and outbound streams can be independent and be joined only for completion,
|
||||
|
@ -314,13 +316,13 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Handle inbound message stream.
|
||||
<2> Send outgoing messages.
|
||||
<3> Join the streams and return a `Mono<Void>` that completes when either stream ends.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ExampleHandler : WebSocketHandler {
|
||||
|
||||
|
@ -345,6 +347,7 @@ Java::
|
|||
<1> Handle inbound message stream.
|
||||
<2> Send outgoing messages.
|
||||
<3> Join the streams and return a `Mono<Void>` that completes when either stream ends.
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -145,13 +145,13 @@ Java::
|
|||
return "myViewName";
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Application-specific calculation.
|
||||
<2> Response has been set to 304 (NOT_MODIFIED). No further processing.
|
||||
<3> Continue with request processing.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RequestMapping
|
||||
fun myHandleMethod(exchange: ServerWebExchange, model: Model): String? {
|
||||
|
@ -169,6 +169,7 @@ Java::
|
|||
<1> Application-specific calculation.
|
||||
<2> Response has been set to 304 (NOT_MODIFIED). No further processing.
|
||||
<3> Continue with request processing.
|
||||
======
|
||||
--
|
||||
|
||||
There are three variants for checking conditional requests against `eTag` values, `lastModified`
|
||||
|
|
|
@ -24,11 +24,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Declaring an `@ExceptionHandler`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class SimpleController {
|
||||
|
@ -42,6 +42,8 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Declaring an `@ExceptionHandler`.
|
||||
======
|
||||
|
||||
|
||||
|
||||
The exception can match against a top-level exception being propagated (that is, a direct
|
||||
|
|
|
@ -41,11 +41,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@InitBinder` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class FormController {
|
||||
|
@ -61,6 +61,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using the `@InitBinder` annotation.
|
||||
======
|
||||
--
|
||||
|
||||
Alternatively, when using a `Formatter`-based setup through a shared
|
||||
|
@ -85,11 +86,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Adding a custom formatter (a `DateFormatter`, in this case).
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class FormController {
|
||||
|
@ -103,6 +104,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Adding a custom formatter (a `DateFormatter`, in this case).
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
|
|
@ -26,11 +26,11 @@ Java::
|
|||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the cookie value.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
fun handle(@CookieValue("JSESSIONID") cookie: String) { // <1>
|
||||
|
@ -38,6 +38,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Get the cookie value.
|
||||
======
|
||||
|
||||
|
||||
Type conversion is applied automatically if the target method parameter type is not
|
||||
|
|
|
@ -18,16 +18,17 @@ Java::
|
|||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@ModelAttribute Pet pet) { } // <1>
|
||||
----
|
||||
======
|
||||
<1> Bind an instance of `Pet`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@ModelAttribute pet: Pet): String { } // <1>
|
||||
----
|
||||
<1> Bind an instance of `Pet`.
|
||||
======
|
||||
|
||||
The `Pet` instance in the preceding example is resolved as follows:
|
||||
|
||||
|
@ -63,11 +64,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Adding a `BindingResult`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1>
|
||||
|
@ -78,6 +79,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Adding a `BindingResult`.
|
||||
======
|
||||
|
||||
You can automatically apply validation after data binding by adding the
|
||||
`jakarta.validation.Valid` annotation or Spring's `@Validated` annotation (see also
|
||||
|
@ -98,11 +100,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@Valid` on a model attribute argument.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1>
|
||||
|
@ -113,6 +115,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using `@Valid` on a model attribute argument.
|
||||
======
|
||||
|
||||
Spring WebFlux, unlike Spring MVC, supports reactive types in the model -- for example,
|
||||
`Mono<Account>` or `io.reactivex.Single<Account>`. You can declare a `@ModelAttribute` argument
|
||||
|
|
|
@ -95,12 +95,12 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
<2> Using `@RequestPart` to get the file.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestPart("meta-data") Part metadata, // <1>
|
||||
|
@ -110,6 +110,7 @@ Java::
|
|||
----
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
<2> Using `@RequestPart` to get the file.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
@ -128,11 +129,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestPart("meta-data") metadata: MetaData): String { // <1>
|
||||
|
@ -140,6 +141,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
======
|
||||
--
|
||||
|
||||
You can use `@RequestPart` in combination with `jakarta.validation.Valid` or Spring's
|
||||
|
@ -189,11 +191,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestBody`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { // <1>
|
||||
|
@ -201,6 +203,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using `@RequestBody`.
|
||||
======
|
||||
--
|
||||
|
||||
[[partevent]]
|
||||
|
@ -250,7 +253,6 @@ Java::
|
|||
}));
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestBody`.
|
||||
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
|
||||
followed by additional events belonging to subsequent parts.
|
||||
|
@ -262,8 +264,9 @@ file upload.
|
|||
<5> Handling the file upload.
|
||||
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = { // <1>
|
||||
|
@ -299,6 +302,7 @@ file upload.
|
|||
<4> Handling the form field.
|
||||
<5> Handling the file upload.
|
||||
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
======
|
||||
|
||||
Received part events can also be relayed to another service by using the `WebClient`.
|
||||
See xref:web/webflux-webclient/client-body.adoc#webflux-client-body-multipart[Multipart Data].
|
||||
|
|
|
@ -18,11 +18,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestAttribute`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/")
|
||||
fun handle(@RequestAttribute client: Client): String { // <1>
|
||||
|
@ -30,5 +30,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using `@RequestAttribute`.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -34,12 +34,12 @@ Java::
|
|||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the value of the `Accept-Encoding` header.
|
||||
<2> Get the value of the `Keep-Alive` header.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
fun handle(
|
||||
|
@ -50,6 +50,7 @@ Java::
|
|||
----
|
||||
<1> Get the value of the `Accept-Encoding` header.
|
||||
<2> Get the value of the `Keep-Alive` header.
|
||||
======
|
||||
|
||||
Type conversion is applied automatically if the target method parameter type is not
|
||||
`String`. See xref:web/webflux/controller/ann-methods/typeconversion.adoc[Type Conversion].
|
||||
|
|
|
@ -28,11 +28,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestParam`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.ui.set
|
||||
|
||||
|
@ -53,6 +53,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using `@RequestParam`.
|
||||
======
|
||||
|
||||
TIP: The Servlet API "`request parameter`" concept conflates query parameters, form
|
||||
data, and multiparts into one. However, in WebFlux, each is accessed individually through
|
||||
|
|
|
@ -18,11 +18,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@SessionAttribute`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/")
|
||||
fun handle(@SessionAttribute user: User): String { // <1>
|
||||
|
@ -30,6 +30,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using `@SessionAttribute`.
|
||||
======
|
||||
|
||||
For use cases that require adding or removing session attributes, consider injecting
|
||||
`WebSession` into the controller method.
|
||||
|
|
|
@ -23,11 +23,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") // <1>
|
||||
|
@ -36,6 +36,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
======
|
||||
|
||||
On the first request, when a model attribute with the name, `pet`, is added to the model,
|
||||
it is automatically promoted to and saved in the `WebSession`. It remains there until
|
||||
|
@ -65,12 +66,12 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
<2> Using a `SessionStatus` variable.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") // <1>
|
||||
|
@ -90,5 +91,6 @@ Java::
|
|||
----
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
<2> Using a `SessionStatus` variable.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -155,12 +155,12 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Class-level URI mapping.
|
||||
<2> Method-level URI mapping.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/owners/{ownerId}") // <1>
|
||||
|
@ -174,6 +174,7 @@ Java::
|
|||
----
|
||||
<1> Class-level URI mapping.
|
||||
<2> Method-level URI mapping.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
@ -352,11 +353,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Check that `myParam` equals `myValue`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) // <1>
|
||||
fun findPet(@PathVariable petId: String) {
|
||||
|
@ -364,6 +365,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Check that `myParam` equals `myValue`.
|
||||
======
|
||||
|
||||
You can also use the same with request header conditions, as the following example shows:
|
||||
|
||||
|
@ -378,11 +380,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Check that `myHeader` equals `myValue`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) // <1>
|
||||
fun findPet(@PathVariable petId: String) {
|
||||
|
@ -390,6 +392,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Check that `myHeader` equals `myValue`.
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
@ -466,14 +469,14 @@ Java::
|
|||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Inject target handlers and the handler mapping for controllers.
|
||||
<2> Prepare the request mapping metadata.
|
||||
<3> Get the handler method.
|
||||
<4> Add the registration.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
@ -493,6 +496,7 @@ Java::
|
|||
<2> Prepare the request mapping metadata.
|
||||
<3> Get the handler method.
|
||||
<4> Add the registration.
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -25,11 +25,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Scan the `org.example.web` package.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web") // <1>
|
||||
|
@ -39,6 +39,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Scan the `org.example.web` package.
|
||||
======
|
||||
|
||||
`@RestController` is a xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[composed annotation] that is
|
||||
itself meta-annotated with `@Controller` and `@ResponseBody`, indicating a controller whose
|
||||
|
|
|
@ -67,11 +67,11 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create router using `route()`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.servlet.function.router
|
||||
|
||||
|
@ -105,6 +105,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Create router using the router DSL.
|
||||
======
|
||||
|
||||
|
||||
If you register the `RouterFunction` as a bean, for instance by exposing it in a
|
||||
|
@ -409,7 +410,6 @@ public class PersonHandler {
|
|||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
|
@ -417,8 +417,9 @@ JSON.
|
|||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we return a 404 Not Found response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
|
@ -447,6 +448,7 @@ JSON.
|
|||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we return a 404 Not Found response.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
@ -485,13 +487,13 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
|
@ -518,6 +520,7 @@ Java::
|
|||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
======
|
||||
|
||||
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
|
||||
a global `Validator` instance based on `LocalValidatorFactoryBean`.
|
||||
|
@ -638,7 +641,6 @@ Java::
|
|||
.add(otherRoute) // <4>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
|
@ -647,8 +649,9 @@ Java::
|
|||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||
import org.springframework.web.servlet.function.router
|
||||
|
@ -671,6 +674,7 @@ Java::
|
|||
<3> `POST /person` with no additional predicates is mapped to
|
||||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
======
|
||||
|
||||
|
||||
[[nested-routes]]
|
||||
|
@ -698,11 +702,11 @@ RouterFunction<ServerResponse> route = route()
|
|||
.POST(handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> Note that second parameter of `path` is a consumer that takes the router builder.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.servlet.function.router
|
||||
|
||||
|
@ -715,6 +719,7 @@ RouterFunction<ServerResponse> route = route()
|
|||
}
|
||||
----
|
||||
<1> Using `nest` DSL.
|
||||
======
|
||||
|
||||
Though path-based nesting is the most common, you can nest on any kind of predicate by using
|
||||
the `nest` method on the builder.
|
||||
|
@ -883,12 +888,12 @@ Java::
|
|||
.after((request, response) -> logResponse(response)) // <2>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.servlet.function.router
|
||||
|
||||
|
@ -909,6 +914,7 @@ Java::
|
|||
----
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
======
|
||||
|
||||
|
||||
The `filter` method on the router builder takes a `HandlerFilterFunction`: a
|
||||
|
|
|
@ -154,13 +154,13 @@ Java::
|
|||
return "myViewName";
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Application-specific calculation.
|
||||
<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing.
|
||||
<3> Continue with the request processing.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RequestMapping
|
||||
fun myHandleMethod(request: WebRequest, model: Model): String? {
|
||||
|
@ -178,6 +178,7 @@ Java::
|
|||
<1> Application-specific calculation.
|
||||
<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing.
|
||||
<3> Continue with the request processing.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
|
|
|
@ -40,11 +40,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Defining an `@InitBinder` method.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class FormController {
|
||||
|
@ -60,6 +60,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Defining an `@InitBinder` method.
|
||||
======
|
||||
|
||||
Alternatively, when you use a `Formatter`-based setup through a shared
|
||||
`FormattingConversionService`, you can re-use the same approach and register
|
||||
|
@ -82,11 +83,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Defining an `@InitBinder` method on a custom formatter.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class FormController {
|
||||
|
@ -100,6 +101,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Defining an `@InitBinder` method on a custom formatter.
|
||||
======
|
||||
|
||||
[[mvc-ann-initbinder-model-design]]
|
||||
== Model Design
|
||||
|
|
|
@ -26,11 +26,11 @@ Java::
|
|||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the value of the `JSESSIONID` cookie.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
fun handle(@CookieValue("JSESSIONID") cookie: String) { // <1>
|
||||
|
@ -38,6 +38,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Get the value of the `JSESSIONID` cookie.
|
||||
======
|
||||
|
||||
If the target method parameter type is not `String`, type conversion is applied automatically.
|
||||
See xref:web/webmvc/mvc-controller/ann-methods/typeconversion.adoc[Type Conversion].
|
||||
|
|
|
@ -20,11 +20,11 @@ Java::
|
|||
// method logic...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Bind an instance of `Pet`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@ModelAttribute pet: Pet): String { // <1>
|
||||
|
@ -32,6 +32,7 @@ fun processSubmit(@ModelAttribute pet: Pet): String { // <1>
|
|||
}
|
||||
----
|
||||
<1> Bind an instance of `Pet`.
|
||||
======
|
||||
|
||||
The `Pet` instance above is sourced in one of the following ways:
|
||||
|
||||
|
@ -66,11 +67,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Bind an instance of `Account` using an explicit attribute name.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PutMapping("/accounts/{account}")
|
||||
fun save(@ModelAttribute("account") account: Account): String { // <1>
|
||||
|
@ -78,6 +79,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Bind an instance of `Account` using an explicit attribute name.
|
||||
======
|
||||
|
||||
After the model attribute instance is obtained, data binding is applied. The
|
||||
`WebDataBinder` class matches Servlet request parameter names (query parameters and form
|
||||
|
@ -104,11 +106,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Adding a `BindingResult` next to the `@ModelAttribute`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1>
|
||||
|
@ -119,6 +121,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Adding a `BindingResult` next to the `@ModelAttribute`.
|
||||
======
|
||||
|
||||
In some cases, you may want access to a model attribute without data binding. For such
|
||||
cases, you can inject the `Model` into the controller and access it directly or,
|
||||
|
@ -146,11 +149,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Setting `@ModelAttribute(binding=false)`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ModelAttribute
|
||||
fun setUpForm(): AccountForm {
|
||||
|
@ -169,6 +172,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Setting `@ModelAttribute(binding=false)`.
|
||||
======
|
||||
|
||||
You can automatically apply validation after data binding by adding the
|
||||
`jakarta.validation.Valid` annotation or Spring's `@Validated` annotation
|
||||
|
@ -189,11 +193,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Validate the `Pet` instance.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1>
|
||||
|
@ -204,6 +208,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Validate the `Pet` instance.
|
||||
======
|
||||
|
||||
Note that using `@ModelAttribute` is optional (for example, to set its attributes).
|
||||
By default, any argument that is not a simple value type (as determined by
|
||||
|
|
|
@ -18,11 +18,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@RequestAttribute` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/")
|
||||
fun handle(@RequestAttribute client: Client): String { // <1>
|
||||
|
@ -30,5 +30,6 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using the `@RequestAttribute` annotation.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -34,12 +34,12 @@ Java::
|
|||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the value of the `Accept-Encoding` header.
|
||||
<2> Get the value of the `Keep-Alive` header.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
fun handle(
|
||||
|
@ -50,6 +50,7 @@ Java::
|
|||
----
|
||||
<1> Get the value of the `Accept-Encoding` header.
|
||||
<2> Get the value of the `Keep-Alive` header.
|
||||
======
|
||||
|
||||
If the target method parameter type is not
|
||||
`String`, type conversion is automatically applied. See xref:web/webmvc/mvc-controller/ann-methods/typeconversion.adoc[Type Conversion].
|
||||
|
|
|
@ -31,11 +31,11 @@ Java::
|
|||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestParam` to bind `petId`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.ui.set
|
||||
|
||||
|
@ -57,6 +57,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using `@RequestParam` to bind `petId`.
|
||||
======
|
||||
|
||||
By default, method parameters that use this annotation are required, but you can specify that
|
||||
a method parameter is optional by setting the `@RequestParam` annotation's `required` flag to
|
||||
|
|
|
@ -19,18 +19,19 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using a `@SessionAttribute` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RequestMapping("/")
|
||||
fun handle(@SessionAttribute user: User): String { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Using a `@SessionAttribute` annotation.
|
||||
<1> Using a `@SessionAttribute` annotation.======
|
||||
======
|
||||
|
||||
For use cases that require adding or removing session attributes, consider injecting
|
||||
`org.springframework.web.context.request.WebRequest` or
|
||||
|
|
|
@ -23,11 +23,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") // <1>
|
||||
|
@ -36,6 +36,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
======
|
||||
|
||||
On the first request, when a model attribute with the name, `pet`, is added to the model,
|
||||
it is automatically promoted to and saved in the HTTP Servlet session. It remains there
|
||||
|
@ -64,12 +65,12 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Storing the `Pet` value in the Servlet session.
|
||||
<2> Clearing the `Pet` value from the Servlet session.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") // <1>
|
||||
|
@ -89,5 +90,6 @@ class EditPetForm {
|
|||
----
|
||||
<1> Storing the `Pet` value in the Servlet session.
|
||||
<2> Clearing the `Pet` value from the Servlet session.
|
||||
======
|
||||
|
||||
|
||||
|
|
|
@ -309,11 +309,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using a `consumes` attribute to narrow the mapping by the content type.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/pets", consumes = ["application/json"]) // <1>
|
||||
fun addPet(@RequestBody pet: Pet) {
|
||||
|
@ -321,6 +321,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using a `consumes` attribute to narrow the mapping by the content type.
|
||||
======
|
||||
|
||||
The `consumes` attribute also supports negation expressions -- for example, `!text/plain` means any
|
||||
content type other than `text/plain`.
|
||||
|
@ -352,11 +353,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using a `produces` attribute to narrow the mapping by the content type.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/pets/{petId}", produces = ["application/json"]) // <1>
|
||||
@ResponseBody
|
||||
|
@ -365,6 +366,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Using a `produces` attribute to narrow the mapping by the content type.
|
||||
======
|
||||
|
||||
The media type can specify a character set. Negated expressions are supported -- for example,
|
||||
`!text/plain` means any content type other than "text/plain".
|
||||
|
@ -396,11 +398,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Testing whether `myParam` equals `myValue`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) // <1>
|
||||
fun findPet(@PathVariable petId: String) {
|
||||
|
@ -408,6 +410,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Testing whether `myParam` equals `myValue`.
|
||||
======
|
||||
|
||||
You can also use the same with request header conditions, as the following example shows:
|
||||
|
||||
|
@ -422,11 +425,11 @@ Java::
|
|||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Testing whether `myHeader` equals `myValue`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) // <1>
|
||||
fun findPet(@PathVariable petId: String) {
|
||||
|
@ -434,6 +437,7 @@ Java::
|
|||
}
|
||||
----
|
||||
<1> Testing whether `myHeader` equals `myValue`.
|
||||
======
|
||||
|
||||
TIP: You can match `Content-Type` and `Accept` with the headers condition, but it is better to use
|
||||
xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-consumes[consumes] and xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-produces[produces]
|
||||
|
@ -517,14 +521,14 @@ Java::
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Inject the target handler and the handler mapping for controllers.
|
||||
<2> Prepare the request mapping meta data.
|
||||
<3> Get the handler method.
|
||||
<4> Add the registration.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
@ -541,6 +545,7 @@ Java::
|
|||
<2> Prepare the request mapping meta data.
|
||||
<3> Get the handler method.
|
||||
<4> Add the registration.
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -18,15 +18,15 @@ Java::
|
|||
|
||||
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
|
||||
----
|
||||
======
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add or replace URI components.
|
||||
<3> Request to have the URI template and URI variables encoded.
|
||||
<4> Build a `UriComponents`.
|
||||
<5> Expand variables and obtain the `URI`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uriComponents = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
|
@ -41,6 +41,7 @@ Java::
|
|||
<3> Request to have the URI template and URI variables encoded.
|
||||
<4> Build a `UriComponents`.
|
||||
<5> Expand variables and obtain the `URI`.
|
||||
======
|
||||
|
||||
The preceding example can be consolidated into one chain and shortened with `buildAndExpand`,
|
||||
as the following example shows:
|
||||
|
|
|
@ -8,10 +8,10 @@ javaPlatform {
|
|||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.14.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.6"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.7"))
|
||||
api(platform("io.netty:netty-bom:4.1.92.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.6"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.7"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.11"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
|
||||
|
@ -23,13 +23,13 @@ dependencies {
|
|||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.6")
|
||||
api("com.github.librepdf:openpdf:1.3.30")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.10.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.21.12")
|
||||
api("com.google.protobuf:protobuf-java-util:3.23.0")
|
||||
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
|
||||
api("com.h2database:h2:2.1.214")
|
||||
api("com.jayway.jsonpath:json-path:2.8.0")
|
||||
|
@ -45,11 +45,11 @@ dependencies {
|
|||
api("com.thoughtworks.xstream:xstream:1.4.20")
|
||||
api("commons-io:commons-io:2.11.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("info.picocli:picocli:4.7.1")
|
||||
api("info.picocli:picocli:4.7.3")
|
||||
api("io.micrometer:context-propagation:1.0.0")
|
||||
api("io.mockk:mockk:1.13.4")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
api("io.projectreactor.tools:blockhound:1.0.7.RELEASE")
|
||||
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
|
@ -89,9 +89,9 @@ dependencies {
|
|||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.70.0")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.17.2")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.2")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.2")
|
||||
api("org.apache.activemq:activemq-broker:5.17.4")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.4")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.4")
|
||||
api("org.apache.commons:commons-pool2:2.9.0")
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
|
|
Loading…
Reference in New Issue