From 39146f9066c97619eb2386d4d1ff75033f314860 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 20 Apr 2023 16:21:36 -0500 Subject: [PATCH] Migrate to Asciidoctor Tabs --- framework-docs/antora-playbook.yml | 2 - .../ROOT/pages/core/aop-api/advice.adoc | 90 +++++-- .../ROOT/pages/core/aop-api/advised.adoc | 20 +- .../modules/ROOT/pages/core/aop-api/pfb.adoc | 10 +- .../ROOT/pages/core/aop-api/pointcuts.adoc | 10 +- .../modules/ROOT/pages/core/aop-api/prog.adoc | 10 +- .../ROOT/pages/core/aop-api/targetsource.adoc | 20 +- .../pages/core/aop/aspectj-programmatic.adoc | 10 +- .../ROOT/pages/core/aop/ataspectj/advice.adoc | 170 ++++++++++--- .../core/aop/ataspectj/aspectj-support.adoc | 10 +- .../pages/core/aop/ataspectj/at-aspectj.adoc | 10 +- .../pages/core/aop/ataspectj/example.adoc | 26 +- .../aop/ataspectj/instantiation-models.adoc | 10 +- .../core/aop/ataspectj/introductions.adoc | 20 +- .../pages/core/aop/ataspectj/pointcuts.adoc | 26 +- .../modules/ROOT/pages/core/aop/choosing.adoc | 10 +- .../modules/ROOT/pages/core/aop/proxying.adoc | 50 +++- .../modules/ROOT/pages/core/aop/schema.adoc | 110 ++++++-- .../ROOT/pages/core/aop/using-aspectj.adoc | 90 +++++-- .../modules/ROOT/pages/core/aot.adoc | 24 +- .../ROOT/pages/core/appendix/xml-custom.adoc | 86 +++++-- .../ROOT/pages/core/appendix/xsd-schemas.adoc | 20 +- .../annotation-config/autowired-primary.adoc | 20 +- .../autowired-qualifiers.adoc | 86 +++++-- .../beans/annotation-config/autowired.adoc | 102 ++++++-- .../generics-as-qualifiers.adoc | 30 ++- ...tconstruct-and-predestroy-annotations.adoc | 10 +- .../beans/annotation-config/resource.adoc | 22 +- .../annotation-config/value-annotations.adoc | 70 ++++-- .../modules/ROOT/pages/core/beans/basics.adoc | 50 +++- .../ROOT/pages/core/beans/beanfactory.adoc | 20 +- .../pages/core/beans/classpath-scanning.adoc | 196 ++++++++++++--- .../core/beans/context-introduction.adoc | 150 ++++++++--- .../core/beans/context-load-time-weaver.adoc | 10 +- .../ROOT/pages/core/beans/definition.adoc | 30 ++- .../dependencies/factory-collaborators.adoc | 80 ++++-- .../factory-method-injection.adoc | 60 ++++- .../factory-properties-detailed.adoc | 30 ++- .../ROOT/pages/core/beans/environment.adoc | 122 +++++++-- .../pages/core/beans/factory-extension.adoc | 20 +- .../ROOT/pages/core/beans/factory-nature.adoc | 60 ++++- .../ROOT/pages/core/beans/factory-scopes.adoc | 90 +++++-- .../pages/core/beans/java/basic-concepts.adoc | 10 +- .../core/beans/java/bean-annotation.adoc | 116 +++++++-- .../java/composing-configuration-classes.adoc | 110 ++++++-- .../beans/java/configuration-annotation.adoc | 40 ++- .../beans/java/instantiating-container.adoc | 46 +++- .../core/beans/standard-annotations.adoc | 70 ++++-- .../ROOT/pages/core/databuffer-codec.adoc | 10 +- .../ROOT/pages/core/expressions/beandef.adoc | 40 ++- .../pages/core/expressions/evaluation.adoc | 70 +++++- .../core/expressions/example-classes.adoc | 30 ++- .../language-ref/array-construction.adoc | 10 +- .../language-ref/bean-references.adoc | 20 +- .../language-ref/collection-projection.adoc | 10 +- .../language-ref/collection-selection.adoc | 20 +- .../language-ref/constructors.adoc | 10 +- .../expressions/language-ref/functions.adoc | 30 ++- .../language-ref/inline-lists.adoc | 10 +- .../expressions/language-ref/inline-maps.adoc | 10 +- .../expressions/language-ref/literal.adoc | 10 +- .../expressions/language-ref/methods.adoc | 10 +- .../language-ref/operator-elvis.adoc | 20 +- .../operator-safe-navigation.adoc | 10 +- .../language-ref/operator-ternary.adoc | 20 +- .../expressions/language-ref/operators.adoc | 50 +++- .../language-ref/properties-arrays.adoc | 30 ++- .../expressions/language-ref/templating.adoc | 20 +- .../core/expressions/language-ref/types.adoc | 10 +- .../expressions/language-ref/variables.adoc | 20 +- .../modules/ROOT/pages/core/resources.adoc | 180 +++++++++++--- .../modules/ROOT/pages/core/spring-jcl.adoc | 10 +- .../pages/core/validation/beans-beans.adoc | 80 ++++-- .../pages/core/validation/beanvalidation.adoc | 90 +++++-- .../ROOT/pages/core/validation/convert.adoc | 20 +- ...uring-formatting-globaldatetimeformat.adoc | 10 +- .../ROOT/pages/core/validation/format.adoc | 40 ++- .../ROOT/pages/core/validation/validator.adoc | 30 ++- .../modules/ROOT/pages/data-access/dao.adoc | 35 ++- .../ROOT/pages/data-access/jdbc/advanced.adoc | 40 ++- .../pages/data-access/jdbc/connections.adoc | 10 +- .../ROOT/pages/data-access/jdbc/core.adoc | 235 ++++++++++++++---- .../jdbc/embedded-database-support.adoc | 30 ++- .../ROOT/pages/data-access/jdbc/object.adoc | 100 ++++++-- .../data-access/jdbc/parameter-handling.adoc | 42 +++- .../ROOT/pages/data-access/jdbc/simple.adoc | 110 ++++++-- .../ROOT/pages/data-access/orm/general.adoc | 10 +- .../ROOT/pages/data-access/orm/hibernate.adoc | 30 ++- .../ROOT/pages/data-access/orm/jpa.adoc | 20 +- .../modules/ROOT/pages/data-access/oxm.adoc | 20 +- .../modules/ROOT/pages/data-access/r2dbc.adoc | 138 +++++++--- .../transaction/declarative/annotations.adoc | 60 ++++- .../applying-more-than-just-tx-advice.adoc | 10 +- .../transaction/declarative/aspectj.adoc | 10 +- .../declarative/first-example.adoc | 50 +++- .../transaction/declarative/rolling-back.adoc | 16 +- .../pages/data-access/transaction/event.adoc | 10 +- .../data-access/transaction/programmatic.adoc | 90 +++++-- .../languages/kotlin/bean-definition-dsl.adoc | 2 - .../languages/kotlin/spring-projects-in.adoc | 2 - .../modules/ROOT/pages/rsocket.adoc | 144 ++++++++--- .../integration-junit-jupiter.adoc | 43 +++- .../annotations/integration-junit4.adoc | 30 ++- .../testing/annotations/integration-meta.adoc | 76 ++++-- .../annotation-activeprofiles.adoc | 12 +- .../annotation-aftertransaction.adoc | 6 +- .../annotation-beforetransaction.adoc | 6 +- .../integration-spring/annotation-commit.adoc | 6 +- .../annotation-contextconfiguration.adoc | 24 +- .../annotation-contexthierarchy.adoc | 20 +- .../annotation-dirtiescontext.adoc | 42 +++- .../annotation-dynamicpropertysource.adoc | 6 +- .../annotation-rollback.adoc | 6 +- .../integration-spring/annotation-sql.adoc | 6 +- .../annotation-sqlconfig.adoc | 6 +- .../annotation-sqlgroup.adoc | 6 +- .../annotation-sqlmergemode.adoc | 12 +- .../annotation-testexecutionlisteners.adoc | 6 +- .../annotation-testpropertysource.adoc | 12 +- .../annotation-webappconfiguration.adoc | 12 +- .../pages/testing/spring-mvc-test-client.adoc | 50 +++- .../async-requests.adoc | 6 +- .../server-defining-expectations.adoc | 73 ++++-- .../server-filters.adoc | 10 +- .../server-htmlunit/mah.adoc | 66 +++-- .../server-htmlunit/webdriver.adoc | 120 +++++++-- .../server-htmlunit/why.adoc | 28 ++- .../server-performing-requests.adoc | 58 ++++- .../server-setup-options.adoc | 28 ++- .../server-setup-steps.adoc | 18 +- .../vs-streaming-response.adoc | 6 +- .../application-events.adoc | 6 +- .../testcontext-framework/ctx-management.adoc | 12 +- .../dynamic-property-sources.adoc | 10 +- .../ctx-management/env-profiles.adoc | 107 ++++++-- .../ctx-management/groovy.adoc | 22 +- .../ctx-management/hierarchies.adoc | 38 ++- .../ctx-management/inheritance.adoc | 18 +- .../ctx-management/initializers.adoc | 12 +- .../ctx-management/javaconfig.adoc | 12 +- .../ctx-management/property-sources.adoc | 41 ++- .../ctx-management/web-mocks.adoc | 10 +- .../ctx-management/web.adoc | 33 ++- .../ctx-management/xml.adoc | 18 +- .../testcontext-framework/executing-sql.adoc | 59 ++++- .../testcontext-framework/fixture-di.adoc | 27 +- .../support-classes.adoc | 90 +++++-- .../testcontext-framework/tel-config.adoc | 28 ++- .../testing/testcontext-framework/tx.adoc | 57 ++++- .../web-scoped-beans.adoc | 21 +- .../ROOT/pages/testing/webtestclient.adoc | 200 +++++++++++---- .../modules/ROOT/pages/web/webflux-cors.adoc | 46 +++- .../ROOT/pages/web/webflux-functional.adoc | 175 ++++++++++--- .../modules/ROOT/pages/web/webflux-view.adoc | 40 ++- .../webflux-webclient/client-attributes.adoc | 10 +- .../web/webflux-webclient/client-body.adoc | 90 +++++-- .../web/webflux-webclient/client-builder.adoc | 131 ++++++++-- .../web/webflux-webclient/client-context.adoc | 6 +- .../webflux-webclient/client-exchange.adoc | 10 +- .../web/webflux-webclient/client-filter.adoc | 40 ++- .../webflux-webclient/client-retrieve.adoc | 40 ++- .../webflux-webclient/client-synchronous.adoc | 20 +- .../ROOT/pages/web/webflux-websocket.adoc | 68 ++++- .../ROOT/pages/web/webflux/caching.adoc | 24 +- .../ROOT/pages/web/webflux/config.adoc | 168 ++++++++++--- .../ROOT/pages/web/webflux/controller.adoc | 10 +- .../web/webflux/controller/ann-advice.adoc | 9 +- .../webflux/controller/ann-exceptions.adoc | 6 +- .../webflux/controller/ann-initbinder.adoc | 12 +- .../controller/ann-methods/cookievalue.adoc | 6 +- .../controller/ann-methods/httpentity.adoc | 10 +- .../controller/ann-methods/jackson.adoc | 9 +- .../ann-methods/matrix-variables.adoc | 40 ++- .../ann-methods/modelattrib-method-args.adoc | 28 ++- .../ann-methods/multipart-forms.adoc | 43 +++- .../controller/ann-methods/requestattrib.adoc | 6 +- .../controller/ann-methods/requestbody.adoc | 29 ++- .../controller/ann-methods/requestheader.adoc | 6 +- .../controller/ann-methods/requestparam.adoc | 6 +- .../controller/ann-methods/responsebody.adoc | 10 +- .../ann-methods/responseentity.adoc | 10 +- .../ann-methods/sessionattribute.adoc | 6 +- .../ann-methods/sessionattributes.adoc | 12 +- .../controller/ann-modelattrib-methods.adoc | 40 ++- .../controller/ann-requestmapping.adoc | 74 ++++-- .../pages/web/webflux/controller/ann.adoc | 6 +- .../pages/web/webflux/dispatcher-handler.adoc | 10 +- .../pages/web/webflux/reactive-spring.adoc | 90 +++++-- .../modules/ROOT/pages/web/webmvc-cors.adoc | 50 +++- .../ROOT/pages/web/webmvc-functional.adoc | 162 +++++++++--- .../pages/web/webmvc-view/mvc-document.adoc | 10 +- .../ROOT/pages/web/webmvc-view/mvc-feeds.adoc | 20 +- .../pages/web/webmvc-view/mvc-freemarker.adoc | 20 +- .../web/webmvc-view/mvc-groovymarkup.adoc | 10 +- .../ROOT/pages/web/webmvc-view/mvc-jsp.adoc | 30 ++- .../pages/web/webmvc-view/mvc-script.adoc | 30 ++- .../ROOT/pages/web/webmvc-view/mvc-xslt.adoc | 20 +- .../ROOT/pages/web/webmvc/mvc-ann-async.adoc | 50 +++- .../ROOT/pages/web/webmvc/mvc-caching.adoc | 26 +- .../web/webmvc/mvc-config/advanced-java.adoc | 10 +- .../web/webmvc/mvc-config/advanced-xml.adoc | 10 +- .../mvc-config/content-negotiation.adoc | 10 +- .../web/webmvc/mvc-config/conversion.adoc | 20 +- .../web/webmvc/mvc-config/customize.adoc | 10 +- .../mvc-config/default-servlet-handler.adoc | 20 +- .../pages/web/webmvc/mvc-config/enable.adoc | 10 +- .../web/webmvc/mvc-config/interceptors.adoc | 10 +- .../webmvc/mvc-config/message-converters.adoc | 10 +- .../web/webmvc/mvc-config/path-matching.adoc | 10 +- .../webmvc/mvc-config/static-resources.adoc | 20 +- .../web/webmvc/mvc-config/validation.adoc | 20 +- .../webmvc/mvc-config/view-controller.adoc | 10 +- .../web/webmvc/mvc-config/view-resolvers.adoc | 20 +- .../ROOT/pages/web/webmvc/mvc-controller.adoc | 10 +- .../web/webmvc/mvc-controller/ann-advice.adoc | 10 +- .../mvc-controller/ann-exceptionhandler.adoc | 30 ++- .../webmvc/mvc-controller/ann-initbinder.adoc | 12 +- .../ann-methods/cookievalue.adoc | 6 +- .../ann-methods/httpentity.adoc | 10 +- .../mvc-controller/ann-methods/jackson.adoc | 30 ++- .../ann-methods/matrix-variables.adoc | 40 ++- .../ann-methods/modelattrib-method-args.adoc | 30 ++- .../ann-methods/multipart-forms.adoc | 40 ++- .../ann-methods/redirecting-passing-data.adoc | 10 +- .../ann-methods/requestattrib.adoc | 6 +- .../ann-methods/requestbody.adoc | 20 +- .../ann-methods/requestheader.adoc | 6 +- .../ann-methods/requestparam.adoc | 6 +- .../ann-methods/responsebody.adoc | 10 +- .../ann-methods/responseentity.adoc | 10 +- .../ann-methods/sessionattribute.adoc | 6 +- .../ann-methods/sessionattributes.adoc | 12 +- .../ann-modelattrib-methods.adoc | 30 ++- .../mvc-controller/ann-requestmapping.adoc | 70 +++++- .../pages/web/webmvc/mvc-controller/ann.adoc | 10 +- .../ROOT/pages/web/webmvc/mvc-servlet.adoc | 10 +- .../webmvc/mvc-servlet/container-config.adoc | 40 ++- .../webmvc/mvc-servlet/context-hierarchy.adoc | 10 +- .../webmvc/mvc-servlet/exceptionhandlers.adoc | 10 +- .../pages/web/webmvc/mvc-servlet/logging.adoc | 10 +- .../web/webmvc/mvc-servlet/multipart.adoc | 10 +- .../pages/web/webmvc/mvc-uri-building.adoc | 80 ++++-- .../modules/ROOT/partials/web/web-uris.adoc | 106 ++++++-- 243 files changed, 7124 insertions(+), 1779 deletions(-) diff --git a/framework-docs/antora-playbook.yml b/framework-docs/antora-playbook.yml index 656621102b6..6e9e4308e61 100644 --- a/framework-docs/antora-playbook.yml +++ b/framework-docs/antora-playbook.yml @@ -4,8 +4,6 @@ antora: extensions: - '@antora/collector-extension' - - require: '@springio/antora-extensions/tabs-migration-extension' - unwrap_example_block: always site: title: Spring Framework Reference url: https://https://rwinch.github.io/spring-framework/ diff --git a/framework-docs/modules/ROOT/pages/core/aop-api/advice.adoc b/framework-docs/modules/ROOT/pages/core/aop-api/advice.adoc index fc026e81cba..fd9ecd219a2 100644 --- a/framework-docs/modules/ROOT/pages/core/aop-api/advice.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop-api/advice.adoc @@ -54,8 +54,11 @@ point. The following example shows a simple `MethodInterceptor` implementation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class DebugInterceptor implements MethodInterceptor { @@ -67,8 +70,10 @@ The following example shows a simple `MethodInterceptor` implementation: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class DebugInterceptor : MethodInterceptor { @@ -80,6 +85,7 @@ The following example shows a simple `MethodInterceptor` implementation: } } ---- +====== Note the call to the `proceed()` method of `MethodInvocation`. This proceeds down the interceptor chain towards the join point. Most interceptors invoke this method and @@ -129,8 +135,11 @@ wrapped in an unchecked exception by the AOP proxy. The following example shows a before advice in Spring, which counts all method invocations: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class CountingBeforeAdvice implements MethodBeforeAdvice { @@ -145,8 +154,10 @@ The following example shows a before advice in Spring, which counts all method i } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class CountingBeforeAdvice : MethodBeforeAdvice { @@ -157,6 +168,7 @@ The following example shows a before advice in Spring, which counts all method i } } ---- +====== TIP: Before advice can be used with any pointcut. @@ -181,8 +193,11 @@ arguments. The next two listing show classes that are examples of throws advice. The following advice is invoked if a `RemoteException` is thrown (including from subclasses): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class RemoteThrowsAdvice implements ThrowsAdvice { @@ -191,8 +206,10 @@ The following advice is invoked if a `RemoteException` is thrown (including from } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class RemoteThrowsAdvice : ThrowsAdvice { @@ -201,13 +218,17 @@ The following advice is invoked if a `RemoteException` is thrown (including from } } ---- +====== Unlike the preceding advice, the next example declares four arguments, so that it has access to the invoked method, method arguments, and target object. The following advice is invoked if a `ServletException` is thrown: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ServletThrowsAdviceWithArguments implements ThrowsAdvice { @@ -216,8 +237,10 @@ arguments, and target object. The following advice is invoked if a `ServletExcep } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ServletThrowsAdviceWithArguments : ThrowsAdvice { @@ -226,13 +249,17 @@ arguments, and target object. The following advice is invoked if a `ServletExcep } } ---- +====== The final example illustrates how these two methods could be used in a single class that handles both `RemoteException` and `ServletException`. Any number of throws advice methods can be combined in a single class. The following listing shows the final example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static class CombinedThrowsAdvice implements ThrowsAdvice { @@ -245,8 +272,10 @@ methods can be combined in a single class. The following listing shows the final } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class CombinedThrowsAdvice : ThrowsAdvice { @@ -259,6 +288,7 @@ methods can be combined in a single class. The following listing shows the final } } ---- +====== NOTE: If a throws-advice method throws an exception itself, it overrides the original exception (that is, it changes the exception thrown to the user). The overriding @@ -292,8 +322,11 @@ the invoked method, the method's arguments, and the target. The following after returning advice counts all successful method invocations that have not thrown exceptions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class CountingAfterReturningAdvice implements AfterReturningAdvice { @@ -309,8 +342,10 @@ not thrown exceptions: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class CountingAfterReturningAdvice : AfterReturningAdvice { @@ -322,6 +357,7 @@ not thrown exceptions: } } ---- +====== This advice does not change the execution path. If it throws an exception, it is thrown up the interceptor chain instead of the return value. @@ -380,8 +416,11 @@ introduced interfaces can be implemented by the configured `IntroductionIntercep Consider an example from the Spring test suite and suppose we want to introduce the following interface to one or more objects: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public interface Lockable { void lock(); @@ -389,8 +428,10 @@ introduce the following interface to one or more objects: boolean locked(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- interface Lockable { fun lock() @@ -398,6 +439,7 @@ introduce the following interface to one or more objects: fun locked(): Boolean } ---- +====== This illustrates a mixin. We want to be able to cast advised objects to `Lockable`, whatever their type and call lock and unlock methods. If we call the `lock()` method, we @@ -434,8 +476,11 @@ to that held in the target object. The following example shows the example `LockMixin` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable { @@ -462,8 +507,10 @@ The following example shows the example `LockMixin` class: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class LockMixin : DelegatingIntroductionInterceptor(), Lockable { @@ -490,6 +537,7 @@ The following example shows the example `LockMixin` class: } ---- +====== Often, you need not override the `invoke()` method. The `DelegatingIntroductionInterceptor` implementation (which calls the `delegate` method if @@ -504,8 +552,11 @@ interceptor (which would be defined as a prototype). In this case, there is no configuration relevant for a `LockMixin`, so we create it by using `new`. The following example shows our `LockMixinAdvisor` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class LockMixinAdvisor extends DefaultIntroductionAdvisor { @@ -514,11 +565,14 @@ The following example shows our `LockMixinAdvisor` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java) ---- +====== We can apply this advisor very simply, because it requires no configuration. (However, it is impossible to use an `IntroductionInterceptor` without an diff --git a/framework-docs/modules/ROOT/pages/core/aop-api/advised.adoc b/framework-docs/modules/ROOT/pages/core/aop-api/advised.adoc index f216c9b4d44..46932dfa85f 100644 --- a/framework-docs/modules/ROOT/pages/core/aop-api/advised.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop-api/advised.adoc @@ -6,8 +6,11 @@ However you create AOP proxies, you can manipulate them BY using the interface, no matter which other interfaces it implements. This interface includes the following methods: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Advisor[] getAdvisors(); @@ -29,8 +32,10 @@ following methods: boolean isFrozen(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun getAdvisors(): Array @@ -59,6 +64,7 @@ following methods: fun isFrozen(): Boolean ---- +====== The `getAdvisors()` method returns an `Advisor` for every advisor, interceptor, or other advice type that has been added to the factory. If you added an `Advisor`, the @@ -80,8 +86,11 @@ change. (You can obtain a new proxy from the factory to avoid this problem.) The following example shows casting an AOP proxy to the `Advised` interface and examining and manipulating its advice: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Advised advised = (Advised) myObject; Advisor[] advisors = advised.getAdvisors(); @@ -98,8 +107,10 @@ manipulating its advice: assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val advised = myObject as Advised val advisors = advised.advisors @@ -116,6 +127,7 @@ manipulating its advice: assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size) ---- +====== NOTE: It is questionable whether it is advisable (no pun intended) to modify advice on a business object in production, although there are, no doubt, legitimate usage cases. diff --git a/framework-docs/modules/ROOT/pages/core/aop-api/pfb.adoc b/framework-docs/modules/ROOT/pages/core/aop-api/pfb.adoc index 4ede450416f..6927d154273 100644 --- a/framework-docs/modules/ROOT/pages/core/aop-api/pfb.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop-api/pfb.adoc @@ -192,16 +192,22 @@ an instance of the prototype from the factory. Holding a reference is not suffic The `person` bean definition shown earlier can be used in place of a `Person` implementation, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Person person = (Person) factory.getBean("person"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val person = factory.getBean("person") as Person; ---- +====== Other beans in the same IoC context can express a strongly typed dependency on it, as with an ordinary Java object. The following example shows how to do so: diff --git a/framework-docs/modules/ROOT/pages/core/aop-api/pointcuts.adoc b/framework-docs/modules/ROOT/pages/core/aop-api/pointcuts.adoc index a99cfd000de..aa1c2726932 100644 --- a/framework-docs/modules/ROOT/pages/core/aop-api/pointcuts.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop-api/pointcuts.adoc @@ -208,8 +208,11 @@ Because static pointcuts are most useful, you should probably subclass abstract method (although you can override other methods to customize behavior). The following example shows how to subclass `StaticMethodMatcherPointcut`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class TestStaticPointcut extends StaticMethodMatcherPointcut { @@ -218,8 +221,10 @@ following example shows how to subclass `StaticMethodMatcherPointcut`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class TestStaticPointcut : StaticMethodMatcherPointcut() { @@ -228,6 +233,7 @@ following example shows how to subclass `StaticMethodMatcherPointcut`: } } ---- +====== There are also superclasses for dynamic pointcuts. You can use custom pointcuts with any advice type. diff --git a/framework-docs/modules/ROOT/pages/core/aop-api/prog.adoc b/framework-docs/modules/ROOT/pages/core/aop-api/prog.adoc index 51be4bf6995..0247e4a71c6 100644 --- a/framework-docs/modules/ROOT/pages/core/aop-api/prog.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop-api/prog.adoc @@ -8,22 +8,28 @@ The interfaces implemented by the target object are automatically proxied. The following listing shows creation of a proxy for a target object, with one interceptor and one advisor: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); factory.addAdvice(myMethodInterceptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val factory = ProxyFactory(myBusinessInterfaceImpl) factory.addAdvice(myMethodInterceptor) factory.addAdvisor(myAdvisor) val tb = factory.proxy as MyBusinessInterface ---- +====== The first step is to construct an object of type `org.springframework.aop.framework.ProxyFactory`. You can create this with a target diff --git a/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc b/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc index 91c1cf698ad..fdb41e7b4d1 100644 --- a/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc @@ -34,18 +34,24 @@ Changing the target source's target takes effect immediately. The You can change the target by using the `swap()` method on HotSwappableTargetSource, as the follow example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); Object oldTarget = swapper.swap(newTarget); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource val oldTarget = swapper.swap(newTarget) ---- +====== The following example shows the required XML definitions: @@ -142,18 +148,24 @@ the `ProxyFactoryBean` that exposes the pooled object. The cast is defined as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject"); System.out.println("Max pool size is " + conf.getMaxSize()); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val conf = beanFactory.getBean("businessObject") as PoolingConfig println("Max pool size is " + conf.maxSize) ---- +====== NOTE: Pooling stateless service objects is not usually necessary. We do not believe it should be the default choice, as most stateless objects are naturally thread safe, and instance diff --git a/framework-docs/modules/ROOT/pages/core/aop/aspectj-programmatic.adoc b/framework-docs/modules/ROOT/pages/core/aop/aspectj-programmatic.adoc index 66fbd2209b2..1a243d51c36 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/aspectj-programmatic.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/aspectj-programmatic.adoc @@ -11,8 +11,11 @@ You can use the `org.springframework.aop.aspectj.annotation.AspectJProxyFactory` to create a proxy for a target object that is advised by one or more @AspectJ aspects. The basic usage for this class is very simple, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- // create a factory that can generate a proxy for the given target object AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); @@ -28,8 +31,10 @@ The basic usage for this class is very simple, as the following example shows: // now get the proxy object... MyInterfaceType proxy = factory.getProxy(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- // create a factory that can generate a proxy for the given target object val factory = AspectJProxyFactory(targetObject) @@ -45,6 +50,7 @@ The basic usage for this class is very simple, as the following example shows: // now get the proxy object... val proxy = factory.getProxy() ---- +====== See the {api-spring-framework}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information. diff --git a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/advice.adoc b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/advice.adoc index 856e6604125..e5ea40f51fe 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/advice.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/advice.adoc @@ -13,8 +13,11 @@ You can declare before advice in an aspect by using the `@Before` annotation. The following example uses an inline pointcut expression. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @@ -28,8 +31,10 @@ The following example uses an inline pointcut expression. } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Before @@ -43,12 +48,16 @@ The following example uses an inline pointcut expression. } } ---- +====== If we use a xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[named pointcut], we can rewrite the preceding example as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @@ -62,8 +71,10 @@ as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Before @@ -77,6 +88,7 @@ as follows: } } ---- +====== [[aop-advice-after-returning]] @@ -85,8 +97,11 @@ as follows: After returning advice runs when a matched method execution returns normally. You can declare it by using the `@AfterReturning` annotation. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @@ -100,8 +115,10 @@ You can declare it by using the `@AfterReturning` annotation. } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.AfterReturning @@ -115,6 +132,7 @@ You can declare it by using the `@AfterReturning` annotation. } } ---- +====== NOTE: You can have multiple advice declarations (and other members as well), all inside the same aspect. We show only a single advice declaration in these @@ -124,8 +142,11 @@ Sometimes, you need access in the advice body to the actual value that was retur You can use the form of `@AfterReturning` that binds the return value to get that access, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @@ -141,8 +162,10 @@ access, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.AfterReturning @@ -158,6 +181,7 @@ access, as the following example shows: } } ---- +====== The name used in the `returning` attribute must correspond to the name of a parameter in the advice method. When a method execution returns, the return value is passed to @@ -176,8 +200,11 @@ After throwing advice runs when a matched method execution exits by throwing an exception. You can declare it by using the `@AfterThrowing` annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @@ -191,8 +218,10 @@ following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.AfterThrowing @@ -206,6 +235,7 @@ following example shows: } } ---- +====== Often, you want the advice to run only when exceptions of a given type are thrown, and you also often need access to the thrown exception in the advice body. You can @@ -213,8 +243,11 @@ use the `throwing` attribute to both restrict matching (if desired -- use `Throw as the exception type otherwise) and bind the thrown exception to an advice parameter. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @@ -230,8 +263,10 @@ The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.AfterThrowing @@ -247,6 +282,7 @@ The following example shows how to do so: } } ---- +====== The name used in the `throwing` attribute must correspond to the name of a parameter in the advice method. When a method execution exits by throwing an exception, the exception @@ -271,8 +307,11 @@ using the `@After` annotation. After advice must be prepared to handle both norm exception return conditions. It is typically used for releasing resources and similar purposes. The following example shows how to use after finally advice: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.After; @@ -286,8 +325,10 @@ purposes. The following example shows how to use after finally advice: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.After @@ -301,6 +342,7 @@ purposes. The following example shows how to use after finally advice: } } ---- +====== [NOTE] ==== @@ -371,8 +413,11 @@ value depending on the use case. The following example shows how to use around advice: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; @@ -390,8 +435,10 @@ The following example shows how to use around advice: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Around @@ -409,6 +456,7 @@ The following example shows how to use around advice: } } ---- +====== [[aop-ataspectj-advice-params]] == Advice Parameters @@ -448,22 +496,28 @@ Suppose you want to advise the execution of DAO operations that take an `Account object as the first parameter, and you need access to the account in the advice body. You could write the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)") public void validateAccount(Account account) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)") fun validateAccount(account: Account) { // ... } ---- +====== The `args(account,..)` part of the pointcut expression serves two purposes. First, it restricts matching to only those method executions where the method takes at least one @@ -475,8 +529,11 @@ Another way of writing this is to declare a pointcut that "provides" the `Accoun object value when it matches a join point, and then refer to the named pointcut from the advice. This would look as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)") private void accountDataAccessOperation(Account account) {} @@ -486,8 +543,10 @@ from the advice. This would look as follows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)") private fun accountDataAccessOperation(account: Account) { @@ -498,6 +557,7 @@ from the advice. This would look as follows: // ... } ---- +====== See the AspectJ programming guide for more details. @@ -508,8 +568,11 @@ set of examples shows how to match the execution of methods annotated with an The following shows the definition of the `@Auditable` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @@ -517,18 +580,24 @@ The following shows the definition of the `@Auditable` annotation: AuditCode value(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) annotation class Auditable(val value: AuditCode) ---- +====== The following shows the advice that matches the execution of `@Auditable` methods: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1> public void audit(Auditable auditable) { @@ -536,6 +605,7 @@ The following shows the advice that matches the execution of `@Auditable` method // ... } ---- +====== <1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions]. [source,kotlin,indent=0,subs="verbatim",role="secondary"] @@ -555,62 +625,80 @@ The following shows the advice that matches the execution of `@Auditable` method Spring AOP can handle generics used in class declarations and method parameters. Suppose you have a generic type like the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public interface Sample { void sampleGenericMethod(T param); void sampleGenericCollectionMethod(Collection param); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- interface Sample { fun sampleGenericMethod(param: T) fun sampleGenericCollectionMethod(param: Collection) } ---- +====== You can restrict interception of method types to certain parameter types by tying the advice parameter to the parameter type for which you want to intercept the method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") public void beforeSampleMethod(MyType param) { // Advice implementation } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") fun beforeSampleMethod(param: MyType) { // Advice implementation } ---- +====== This approach does not work for generic collections. So you cannot define a pointcut as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)") public void beforeSampleMethod(Collection param) { // Advice implementation } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)") fun beforeSampleMethod(param: Collection) { // Advice implementation } ---- +====== To make this work, we would have to inspect every element of the collection, which is not reasonable, as we also cannot decide how to treat `null` values in general. To achieve @@ -668,8 +756,11 @@ needed information. The following example shows how to use the `argNames` attribute: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Before( value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1> @@ -679,6 +770,7 @@ The following example shows how to use the `argNames` attribute: // ... 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. @@ -701,8 +793,11 @@ If the first parameter is of type `JoinPoint`, `ProceedingJoinPoint`, or `argNames` attribute. For example, if you modify the preceding advice to receive the join point object, the `argNames` attribute does not need to include it: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Before( value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1> @@ -712,6 +807,7 @@ point object, the `argNames` attribute does not need to include it: // ... 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. @@ -735,14 +831,18 @@ methods that do not collect any other join point context. In such situations, yo omit the `argNames` attribute. For example, the following advice does not need to declare the `argNames` attribute: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Before("com.xyz.Pointcuts.publicMethod()") // <1> public void audit(JoinPoint jp) { // ... use jp } ---- +====== <1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions]. [source,kotlin,indent=0,subs="verbatim",role="secondary"] @@ -764,8 +864,11 @@ arguments that works consistently across Spring AOP and AspectJ. The solution is to ensure that the advice signature binds each of the method parameters in order. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Around("execution(List find*(..)) && " + "com.xyz.CommonPointcuts.inDataAccessLayer() && " + @@ -776,6 +879,7 @@ The following example shows how to do so: 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]. [source,kotlin,indent=0,subs="verbatim",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/aspectj-support.adoc b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/aspectj-support.adoc index 458d4fb118b..6bbddeaddb8 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/aspectj-support.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/aspectj-support.adoc @@ -19,21 +19,27 @@ classpath of your application (version 1.9 or later). This library is available To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy` annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableAspectJAutoProxy public class AppConfig { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableAspectJAutoProxy class AppConfig ---- +====== [[aop-enable-aspectj-xml]] == Enabling @AspectJ Support with XML Configuration diff --git a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/at-aspectj.adoc b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/at-aspectj.adoc index c50f8aba6c3..5e2e40176e6 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/at-aspectj.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/at-aspectj.adoc @@ -19,8 +19,11 @@ that points to a bean class that is annotated with `@Aspect`: The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is annotated with `@Aspect`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"] -.Java ---- package com.xyz; @@ -30,8 +33,10 @@ annotated with `@Aspect`: public class NotVeryUsefulAspect { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"] -.Kotlin ---- package com.xyz @@ -40,6 +45,7 @@ annotated with `@Aspect`: @Aspect class NotVeryUsefulAspect ---- +====== Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any other class. They can also contain pointcut, advice, and introduction (inter-type) diff --git a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/example.adoc b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/example.adoc index d16aba8949d..082de8ba3fc 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/example.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/example.adoc @@ -16,8 +16,11 @@ aspect. Because we want to retry the operation, we need to use around advice so that we can call `proceed` multiple times. The following listing shows the basic aspect implementation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Aspect public class ConcurrentOperationExecutor implements Ordered { @@ -56,6 +59,7 @@ call `proceed` multiple times. The following listing shows the basic aspect impl } } ---- +====== <1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions]. [source,kotlin,indent=0,subs="verbatim",role="secondary"] @@ -123,28 +127,37 @@ The corresponding Spring configuration follows: To refine the aspect so that it retries only idempotent operations, we might define the following `Idempotent` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Retention(RetentionPolicy.RUNTIME) // marker annotation public @interface Idempotent { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Retention(AnnotationRetention.RUNTIME) // marker annotation annotation class Idempotent ---- +====== We can then use the annotation to annotate the implementation of service operations. The change to the aspect to retry only idempotent operations involves refining the pointcut expression so that only `@Idempotent` operations match, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Around("execution(* com.xyz..service.*.*(..)) && " + "@annotation(com.xyz.service.Idempotent)") @@ -152,8 +165,10 @@ expression so that only `@Idempotent` operations match, as follows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Around("execution(* com.xyz..service.*.*(..)) && " + "@annotation(com.xyz.service.Idempotent)") @@ -161,6 +176,7 @@ expression so that only `@Idempotent` operations match, as follows: // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/instantiation-models.adoc b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/instantiation-models.adoc index 75c985d8112..decfbc52e96 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/instantiation-models.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/instantiation-models.adoc @@ -13,8 +13,11 @@ supported. You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect` annotation. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Aspect("perthis(execution(* com.xyz..service.*.*(..)))") public class MyAspect { @@ -27,8 +30,10 @@ annotation. Consider the following example: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Aspect("perthis(execution(* com.xyz..service.*.*(..)))") class MyAspect { @@ -41,6 +46,7 @@ annotation. Consider the following example: } } ---- +====== In the preceding example, the effect of the `perthis` clause is that one aspect instance is created for each unique service object that performs a business service (each unique diff --git a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/introductions.adoc b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/introductions.adoc index d535ade9f49..e8732d3cc3d 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/introductions.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/introductions.adoc @@ -11,8 +11,11 @@ given an interface named `UsageTracked` and an implementation of that interface `DefaultUsageTracked`, the following aspect declares that all implementors of service interfaces also implement the `UsageTracked` interface (e.g. for statistics via JMX): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Aspect public class UsageTracking { @@ -27,8 +30,10 @@ interfaces also implement the `UsageTracked` interface (e.g. for statistics via } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Aspect class UsageTracking { @@ -45,6 +50,7 @@ interfaces also implement the `UsageTracked` interface (e.g. for statistics via } } ---- +====== The interface to be implemented is determined by the type of the annotated field. The `value` attribute of the `@DeclareParents` annotation is an AspectJ type pattern. Any @@ -53,15 +59,21 @@ before advice of the preceding example, service beans can be directly used as implementations of the `UsageTracked` interface. If accessing a bean programmatically, you would write the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- UsageTracked usageTracked = context.getBean("myService", UsageTracked.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- val usageTracked = context.getBean("myService", UsageTracked.class) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/pointcuts.adoc b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/pointcuts.adoc index 722405ef761..b809e1f779f 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/ataspectj/pointcuts.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/ataspectj/pointcuts.adoc @@ -15,18 +15,24 @@ An example may help make this distinction between a pointcut signature and a poi expression clear. The following example defines a pointcut named `anyOldTransfer` that matches the execution of any method named `transfer`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Pointcut("execution(* transfer(..))") // the pointcut expression private void anyOldTransfer() {} // the pointcut signature ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Pointcut("execution(* transfer(..))") // the pointcut expression private fun anyOldTransfer() {} // the pointcut signature ---- +====== The pointcut expression that forms the value of the `@Pointcut` annotation is a regular AspectJ pointcut expression. For a full discussion of AspectJ's pointcut language, see @@ -140,8 +146,11 @@ it is natural and straightforward to identify specific beans by name. You can combine pointcut expressions by using `&&,` `||` and `!`. You can also refer to pointcut expressions by name. The following example shows three pointcut expressions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz; @@ -158,6 +167,7 @@ pointcut expressions by name. The following example shows three pointcut express 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. @@ -204,8 +214,11 @@ We recommend defining a dedicated aspect that captures commonly used _named poin expressions for this purpose. Such an aspect typically resembles the following `CommonPointcuts` example (though what you name the aspect is up to you): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"] -.Java ---- package com.xyz; @@ -266,8 +279,10 @@ expressions for this purpose. Such an aspect typically resembles the following } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"] -.Kotlin ---- package com.xyz @@ -328,6 +343,7 @@ expressions for this purpose. Such an aspect typically resembles the following } ---- +====== You can refer to the pointcuts defined in such an aspect anywhere you need a pointcut expression by referencing the fully-qualified name of the `@Aspect` class combined with diff --git a/framework-docs/modules/ROOT/pages/core/aop/choosing.adoc b/framework-docs/modules/ROOT/pages/core/aop/choosing.adoc index aed62baa1b1..d5432fce394 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/choosing.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/choosing.adoc @@ -55,8 +55,11 @@ it can express than the @AspectJ style: Only the "singleton" aspect instantiatio is supported, and it is not possible to combine named pointcuts declared in XML. For example, in the @AspectJ style you can write something like the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Pointcut("execution(* get*())") public void propertyAccess() {} @@ -67,8 +70,10 @@ For example, in the @AspectJ style you can write something like the following: @Pointcut("propertyAccess() && operationReturningAnAccount()") public void accountPropertyAccess() {} ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Pointcut("execution(* get*())") fun propertyAccess() {} @@ -79,6 +84,7 @@ For example, in the @AspectJ style you can write something like the following: @Pointcut("propertyAccess() && operationReturningAnAccount()") fun accountPropertyAccess() {} ---- +====== In the XML style you can declare the first two pointcuts: diff --git a/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc b/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc index 4ec0ae91452..0187cf3a59e 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc @@ -65,8 +65,11 @@ Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object reference, as the following code snippet shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public class SimplePojo implements Pojo { @@ -80,8 +83,10 @@ code snippet shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- class SimplePojo : Pojo { @@ -95,14 +100,18 @@ code snippet shows: } } ---- +====== If you invoke a method on an object reference, the method is invoked directly on that object reference, as the following image and listing show: image::aop-proxy-plain-pojo-call.png[] +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public class Main { @@ -113,8 +122,10 @@ image::aop-proxy-plain-pojo-call.png[] } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun main() { val pojo = SimplePojo() @@ -122,14 +133,18 @@ image::aop-proxy-plain-pojo-call.png[] pojo.foo() } ---- +====== Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet: image::aop-proxy-call.png[] +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public class Main { @@ -144,8 +159,10 @@ image::aop-proxy-call.png[] } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun main() { val factory = ProxyFactory(SimplePojo()) @@ -157,6 +174,7 @@ fun main() { pojo.foo() } ---- +====== The key thing to understand here is that the client code inside the `main(..)` method of the `Main` class has a reference to the proxy. This means that method calls on that @@ -175,8 +193,11 @@ The next approach is absolutely horrendous, and we hesitate to point it out, pre because it is so horrendous. You can (painful as it is to us) totally tie the logic within your class to Spring AOP, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public class SimplePojo implements Pojo { @@ -190,8 +211,10 @@ within your class to Spring AOP, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- class SimplePojo : Pojo { @@ -205,14 +228,18 @@ within your class to Spring AOP, as the following example shows: } } ---- +====== This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public class Main { @@ -228,8 +255,10 @@ following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun main() { val factory = ProxyFactory(SimplePojo()) @@ -242,6 +271,7 @@ following example shows: pojo.foo() } ---- +====== Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP framework. diff --git a/framework-docs/modules/ROOT/pages/core/aop/schema.adoc b/framework-docs/modules/ROOT/pages/core/aop/schema.adoc index 7e702016fca..fd19d9ecc6f 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/schema.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/schema.adoc @@ -132,20 +132,26 @@ collects the `this` object as the join point context and passes it to the advice The advice must be declared to receive the collected join point context by including parameters of the matching names, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public void monitor(Object service) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun monitor(service: Any) { // ... } ---- +====== When combining pointcut sub-expressions, `+&&+` is awkward within an XML document, so you can use the `and`, `or`, and `not` keywords in place of `+&&+`, @@ -272,16 +278,22 @@ The `doAccessCheck` method must declare a parameter named `retVal`. The type of parameter constrains matching in the same way as described for `@AfterReturning`. For example, you can declare the method signature as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public void doAccessCheck(Object retVal) {... ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun doAccessCheck(retVal: Any) {... ---- +====== [[aop-schema-advice-after-throwing]] @@ -324,16 +336,22 @@ The `doRecoveryActions` method must declare a parameter named `dataAccessEx`. The type of this parameter constrains matching in the same way as described for `@AfterThrowing`. For example, the method signature may be declared as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public void doRecoveryActions(DataAccessException dataAccessEx) {... ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun doRecoveryActions(dataAccessEx: DataAccessException) {... ---- +====== [[aop-schema-advice-after-finally]] @@ -399,8 +417,11 @@ The following example shows how to declare around advice in XML: The implementation of the `doBasicProfiling` advice can be exactly the same as in the @AspectJ example (minus the annotation, of course), as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch @@ -409,8 +430,10 @@ The implementation of the `doBasicProfiling` advice can be exactly the same as i return retVal; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun doBasicProfiling(pjp: ProceedingJoinPoint): Any { // start stopwatch @@ -419,6 +442,7 @@ The implementation of the `doBasicProfiling` advice can be exactly the same as i return pjp.proceed() } ---- +====== [[aop-schema-params]] @@ -447,8 +471,11 @@ The `arg-names` attribute accepts a comma-delimited list of parameter names. The following slightly more involved example of the XSD-based approach shows some around advice used in conjunction with a number of strongly typed parameters: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz.service; @@ -464,8 +491,10 @@ some around advice used in conjunction with a number of strongly typed parameter } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package com.xyz.service @@ -481,14 +510,18 @@ some around advice used in conjunction with a number of strongly typed parameter } } ---- +====== Next up is the aspect. Notice the fact that the `profile(..)` method accepts a number of strongly-typed parameters, the first of which happens to be the join point used to proceed with the method call. The presence of this parameter is an indication that the `profile(..)` is to be used as `around` advice, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz; @@ -509,8 +542,10 @@ proceed with the method call. The presence of this parameter is an indication th } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package com.xyz @@ -531,6 +566,7 @@ proceed with the method call. The presence of this parameter is an indication th } } ---- +====== Finally, the following example XML configuration effects the execution of the preceding advice for a particular join point: @@ -570,8 +606,11 @@ preceding advice for a particular join point: Consider the following driver script: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public class Boot { @@ -582,8 +621,10 @@ Consider the following driver script: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun main() { val ctx = ClassPathXmlApplicationContext("beans.xml") @@ -591,6 +632,7 @@ Consider the following driver script: person.getPerson("Pengo", 12) } ---- +====== With such a `Boot` class, we would get output similar to the following on standard output: @@ -668,20 +710,26 @@ through JMX for example.) The class that backs the `usageTracking` bean would then contain the following method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- fun recordUsage(usageTracked: UsageTracked) { usageTracked.incrementUseCount() } ---- +====== The interface to be implemented is determined by the `implement-interface` attribute. The value of the `types-matching` attribute is an AspectJ type pattern. Any bean of a @@ -690,16 +738,22 @@ advice of the preceding example, service beans can be directly used as implement the `UsageTracked` interface. To access a bean programmatically, you could write the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- UsageTracked usageTracked = context.getBean("myService", UsageTracked.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- val usageTracked = context.getBean("myService", UsageTracked.class) ---- +====== @@ -771,8 +825,11 @@ Because we want to retry the operation, we need to use around advice so that we call `proceed` multiple times. The following listing shows the basic aspect implementation (which is a regular Java class that uses the schema support): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- public class ConcurrentOperationExecutor implements Ordered { @@ -809,8 +866,10 @@ call `proceed` multiple times. The following listing shows the basic aspect impl } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- class ConcurrentOperationExecutor : Ordered { @@ -847,6 +906,7 @@ call `proceed` multiple times. The following listing shows the basic aspect impl } } ---- +====== 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 @@ -889,21 +949,27 @@ this is not the case, we can refine the aspect so that it retries only genuinely idempotent operations, by introducing an `Idempotent` annotation and using the annotation to annotate the implementation of service operations, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Retention(RetentionPolicy.RUNTIME) // marker annotation public @interface Idempotent { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Retention(AnnotationRetention.RUNTIME) // marker annotation annotation class Idempotent ---- +====== The change to the aspect to retry only idempotent operations involves refining the diff --git a/framework-docs/modules/ROOT/pages/core/aop/using-aspectj.adoc b/framework-docs/modules/ROOT/pages/core/aop/using-aspectj.adoc index ced4a2afe25..10fdac6dcac 100644 --- a/framework-docs/modules/ROOT/pages/core/aop/using-aspectj.adoc +++ b/framework-docs/modules/ROOT/pages/core/aop/using-aspectj.adoc @@ -32,8 +32,11 @@ The `@Configurable` annotation marks a class as being eligible for Spring-driven configuration. In the simplest case, you can use purely it as a marker annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz.domain; @@ -44,8 +47,10 @@ following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package com.xyz.domain @@ -56,6 +61,7 @@ following example shows: // ... } ---- +====== When used as a marker interface in this way, Spring configures new instances of the annotated type (`Account`, in this case) by using a bean definition (typically @@ -74,8 +80,11 @@ is to omit the `id` attribute, as the following example shows: If you want to explicitly specify the name of the prototype bean definition to use, you can do so directly in the annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz.domain; @@ -86,8 +95,10 @@ can do so directly in the annotation, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package com.xyz.domain @@ -98,6 +109,7 @@ can do so directly in the annotation, as the following example shows: // ... } ---- +====== Spring now looks for a bean definition named `account` and uses that as the definition to configure new `Account` instances. @@ -137,16 +149,22 @@ dependencies to be injected before the constructor bodies run and thus be available for use in the body of the constructors, you need to define this on the `@Configurable` declaration, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configurable(preConstruction = true) ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configurable(preConstruction = true) ---- +====== You can find more information about the language semantics of the various pointcut types in AspectJ @@ -164,22 +182,28 @@ a reference to the bean factory that is to be used to configure new objects). If use Java-based configuration, you can add `@EnableSpringConfigured` to any `@Configuration` class, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableSpringConfigured public class AppConfig { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableSpringConfigured class AppConfig { } ---- +====== If you prefer XML based configuration, the Spring xref:core/appendix/xsd-schemas.adoc#context[`context` namespace] @@ -417,8 +441,11 @@ use @AspectJ with xref:core/beans/java.adoc[Java configuration]. Specifically, y The following example shows the profiling aspect, which is not fancy. It is a time-based profiler that uses the @AspectJ-style of aspect declaration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz; @@ -448,8 +475,10 @@ It is a time-based profiler that uses the @AspectJ-style of aspect declaration: public void methodsToBeProfiled(){} } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package com.xyz @@ -480,6 +509,7 @@ It is a time-based profiler that uses the @AspectJ-style of aspect declaration: } } ---- +====== We also need to create an `META-INF/aop.xml` file, to inform the AspectJ weaver that we want to weave our `ProfilingAspect` into our classes. This file convention, namely @@ -537,8 +567,11 @@ Now that all the required artifacts (the aspect, the `META-INF/aop.xml` file, and the Spring configuration) are in place, we can create the following driver class with a `main(..)` method to demonstrate the LTW in action: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz; @@ -557,8 +590,10 @@ driver class with a `main(..)` method to demonstrate the LTW in action: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package com.xyz @@ -573,6 +608,7 @@ driver class with a `main(..)` method to demonstrate the LTW in action: service.calculateEntitlement() } ---- +====== We have one last thing to do. The introduction to this section did say that one could switch on LTW selectively on a per-`ClassLoader` basis with Spring, and this is true. @@ -612,8 +648,11 @@ Since this LTW is effected by using full-blown AspectJ, we are not limited only Spring beans. The following slight variation on the `Main` program yields the same result: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"] -.Java ---- package com.xyz; @@ -632,8 +671,10 @@ result: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package com.xyz @@ -648,6 +689,7 @@ result: service.calculateEntitlement() } ---- +====== Notice how, in the preceding program, we bootstrap the Spring container and then create a new instance of the `StubEntitlementCalculationService` totally outside @@ -721,22 +763,28 @@ enough because the LTW support uses `BeanFactoryPostProcessors`.) To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver`, which typically is done by using the `@EnableLoadTimeWeaving` annotation, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableLoadTimeWeaving public class AppConfig { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableLoadTimeWeaving class AppConfig { } ---- +====== Alternatively, if you prefer XML-based configuration, use the `` element. Note that the element is defined in the @@ -804,8 +852,11 @@ To specify a specific `LoadTimeWeaver` with Java configuration, implement the `LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method. The following example specifies a `ReflectiveLoadTimeWeaver`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableLoadTimeWeaving @@ -817,8 +868,10 @@ The following example specifies a `ReflectiveLoadTimeWeaver`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableLoadTimeWeaving @@ -829,6 +882,7 @@ The following example specifies a `ReflectiveLoadTimeWeaver`: } } ---- +====== If you use XML-based configuration, you can specify the fully qualified class name as the value of the `weaver-class` attribute on the `` diff --git a/framework-docs/modules/ROOT/pages/core/aot.adoc b/framework-docs/modules/ROOT/pages/core/aot.adoc index 5a569ca85fa..32e2b32b48c 100644 --- a/framework-docs/modules/ROOT/pages/core/aot.adoc +++ b/framework-docs/modules/ROOT/pages/core/aot.adoc @@ -124,8 +124,11 @@ This is the default behavior, since tuning the generated code for a bean definit Taking our previous example, let's assume that `DataSourceConfiguration` is as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration(proxyBeanMethods = false) public class DataSourceConfiguration { @@ -137,12 +140,16 @@ Taking our previous example, let's assume that `DataSourceConfiguration` is as f } ---- +====== Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates. The AOT engine will convert the configuration class above to code similar to the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,role="primary"] -.Java ---- /** * Bean definitions for {@link DataSourceConfiguration} @@ -177,6 +184,7 @@ The AOT engine will convert the configuration class above to code similar to the } } ---- +====== NOTE: The exact code generated may differ depending on the exact nature of your bean definitions. @@ -197,11 +205,15 @@ Consequently, if the application needs to load a resource, it must be referenced The {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime. The following example makes sure that `config/app.properties` can be loaded from the classpath at runtime within a native image: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- runtimeHints.resources().registerPattern("config/app.properties"); ---- +====== A number of contracts are handled automatically during AOT processing. For instance, the return type of a `@Controller` method is inspected, and relevant reflection hints are added if Spring detects that the type should be serialized (typically to JSON). @@ -248,8 +260,11 @@ A typical use case is the use of DTOs that the container cannot infer, such as u `@RegisterReflectionForBinding` can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required. The following example registers `Account` for serialization. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class OrderService { @@ -261,6 +276,7 @@ The following example registers `Account` for serialization. } ---- +====== [[aot.hints.testing]] === Testing Runtime Hints diff --git a/framework-docs/modules/ROOT/pages/core/appendix/xml-custom.adoc b/framework-docs/modules/ROOT/pages/core/appendix/xml-custom.adoc index 691bfe63ae3..116fad99520 100644 --- a/framework-docs/modules/ROOT/pages/core/appendix/xml-custom.adoc +++ b/framework-docs/modules/ROOT/pages/core/appendix/xml-custom.adoc @@ -141,8 +141,11 @@ element results in a single `SimpleDateFormat` bean definition). Spring features number of convenience classes that support this scenario. In the following example, we use the `NamespaceHandlerSupport` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package org.springframework.samples.xml; @@ -155,8 +158,10 @@ use the `NamespaceHandlerSupport` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package org.springframework.samples.xml @@ -169,6 +174,7 @@ use the `NamespaceHandlerSupport` class: } } ---- +====== You may notice that there is not actually a whole lot of parsing logic in this class. Indeed, the `NamespaceHandlerSupport` class has a built-in notion of @@ -192,8 +198,11 @@ responsible for parsing one distinct top-level XML element defined in the schema the parser, we' have access to the XML element (and thus to its subelements, too) so that we can parse our custom XML content, as you can see in the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package org.springframework.samples.xml; @@ -224,6 +233,7 @@ we can parse our custom XML content, as you can see in the following example: } ---- +====== <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 @@ -401,8 +411,11 @@ setter method for the `components` property. This makes it hard (or rather impos to configure a bean definition for the `Component` class by using setter injection. The following listing shows the `Component` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo; @@ -432,8 +445,10 @@ The following listing shows the `Component` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo @@ -454,13 +469,17 @@ The following listing shows the `Component` class: } } ---- +====== The typical solution to this issue is to create a custom `FactoryBean` that exposes a setter property for the `components` property. The following listing shows such a custom `FactoryBean`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo; @@ -499,8 +518,10 @@ setter property for the `components` property. The following listing shows such } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo @@ -538,6 +559,7 @@ setter property for the `components` property. The following listing shows such } } ---- +====== This works nicely, but it exposes a lot of Spring plumbing to the end user. What we are going to do is write a custom extension that hides away all of this Spring plumbing. @@ -571,8 +593,11 @@ listing shows: Again following xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-introduction[the process described earlier], we then create a custom `NamespaceHandler`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo; @@ -585,8 +610,10 @@ we then create a custom `NamespaceHandler`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo @@ -599,13 +626,17 @@ we then create a custom `NamespaceHandler`: } } ---- +====== Next up is the custom `BeanDefinitionParser`. Remember that we are creating a `BeanDefinition` that describes a `ComponentFactoryBean`. The following listing shows our custom `BeanDefinitionParser` implementation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo; @@ -653,8 +684,10 @@ listing shows our custom `BeanDefinitionParser` implementation: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo @@ -702,6 +735,7 @@ listing shows our custom `BeanDefinitionParser` implementation: } } ---- +====== Finally, the various artifacts need to be registered with the Spring XML infrastructure, by modifying the `META-INF/spring.handlers` and `META-INF/spring.schemas` files, as follows: @@ -748,8 +782,11 @@ the named JCache for us. We can also modify the existing `BeanDefinition` for th `'checkingAccountService'` so that it has a dependency on this new JCache-initializing `BeanDefinition`. The following listing shows our `JCacheInitializer`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo; @@ -766,8 +803,10 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo @@ -778,6 +817,7 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni } } ---- +====== Now we can move onto the custom extension. First, we need to author the XSD schema that describes the custom attribute, as follows: @@ -798,8 +838,11 @@ the XSD schema that describes the custom attribute, as follows: Next, we need to create the associated `NamespaceHandler`, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo; @@ -814,8 +857,10 @@ Next, we need to create the associated `NamespaceHandler`, as follows: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo @@ -830,13 +875,17 @@ Next, we need to create the associated `NamespaceHandler`, as follows: } ---- +====== Next, we need to create the parser. Note that, in this case, because we are going to parse an XML attribute, we write a `BeanDefinitionDecorator` rather than a `BeanDefinitionParser`. The following listing shows our `BeanDefinitionDecorator` implementation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo; @@ -889,8 +938,10 @@ The following listing shows our `BeanDefinitionDecorator` implementation: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo @@ -939,6 +990,7 @@ The following listing shows our `BeanDefinitionDecorator` implementation: } } ---- +====== Finally, we need to register the various artifacts with the Spring XML infrastructure by modifying the `META-INF/spring.handlers` and `META-INF/spring.schemas` files, as follows: diff --git a/framework-docs/modules/ROOT/pages/core/appendix/xsd-schemas.adoc b/framework-docs/modules/ROOT/pages/core/appendix/xsd-schemas.adoc index 0eef8fbfc0d..28b79843a25 100644 --- a/framework-docs/modules/ROOT/pages/core/appendix/xsd-schemas.adoc +++ b/framework-docs/modules/ROOT/pages/core/appendix/xsd-schemas.adoc @@ -117,8 +117,11 @@ easy to do in Spring. You do not actually have to do anything or know anything a the Spring internals (or even about classes such as the `FieldRetrievingFactoryBean`). The following example enumeration shows how easy injecting an enum value is: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package jakarta.persistence; @@ -128,8 +131,10 @@ The following example enumeration shows how easy injecting an enum value is: EXTENDED } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package jakarta.persistence @@ -139,11 +144,15 @@ The following example enumeration shows how easy injecting an enum value is: EXTENDED } ---- +====== Now consider the following setter of type `PersistenceContextType` and the corresponding bean definition: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package example; @@ -156,8 +165,10 @@ Now consider the following setter of type `PersistenceContextType` and the corre } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package example @@ -166,6 +177,7 @@ Now consider the following setter of type `PersistenceContextType` and the corre lateinit var persistenceContextType: PersistenceContextType } ---- +====== [source,xml,indent=0,subs="verbatim,quotes"] ---- diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-primary.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-primary.adoc index 7843d9835d6..dcaa7bce623 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-primary.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-primary.adoc @@ -11,8 +11,11 @@ autowired value. Consider the following configuration that defines `firstMovieCatalog` as the primary `MovieCatalog`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class MovieConfiguration { @@ -27,8 +30,10 @@ primary `MovieCatalog`: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class MovieConfiguration { @@ -43,12 +48,16 @@ primary `MovieCatalog`: // ... } ---- +====== With the preceding configuration, the following `MovieRecommender` is autowired with the `firstMovieCatalog`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -58,8 +67,10 @@ With the preceding configuration, the following `MovieRecommender` is autowired // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -69,6 +80,7 @@ class MovieRecommender { // ... } ---- +====== The corresponding bean definitions follow: diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc index a63b5e8bbae..9069fdad9da 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc @@ -9,8 +9,11 @@ chosen for each argument. In the simplest case, this can be a plain descriptive shown in the following example: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -21,8 +24,10 @@ shown in the following example: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -33,14 +38,18 @@ shown in the following example: // ... } ---- +====== -- You can also specify the `@Qualifier` annotation on individual constructor arguments or method parameters, as shown in the following example: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -58,8 +67,10 @@ method parameters, as shown in the following example: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -77,6 +88,7 @@ method parameters, as shown in the following example: // ... } ---- +====== -- The following example shows corresponding bean definitions. @@ -194,8 +206,11 @@ You can create your own custom qualifier annotations. To do so, define an annota provide the `@Qualifier` annotation within your definition, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @@ -205,22 +220,28 @@ provide the `@Qualifier` annotation within your definition, as the following exa String value(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) @Qualifier annotation class Genre(val value: String) ---- +====== -- Then you can provide the custom qualifier on autowired fields and parameters, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -238,8 +259,10 @@ following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -257,6 +280,7 @@ following example shows: // ... } ---- +====== -- Next, you can provide the information for the candidate bean definitions. You can add @@ -306,8 +330,11 @@ catalog that can be searched when no Internet connection is available. First, de the simple annotation, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @@ -315,22 +342,28 @@ the simple annotation, as the following example shows: public @interface Offline { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) @Qualifier annotation class Offline ---- +====== -- Then add the annotation to the field or property to be autowired, as shown in the following example: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -341,6 +374,7 @@ following example: // ... } ---- +====== <1> This line adds the `@Offline` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -379,8 +413,11 @@ all such attribute values to be considered an autowire candidate. As an example, consider the following annotation definition: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @@ -392,41 +429,53 @@ consider the following annotation definition: Format format(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) @Qualifier annotation class MovieQualifier(val genre: String, val format: Format) ---- +====== -- In this case `Format` is an enum, defined as follows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public enum Format { VHS, DVD, BLURAY } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- enum class Format { VHS, DVD, BLURAY } ---- +====== -- The fields to be autowired are annotated with the custom qualifier and include values for both attributes: `genre` and `format`, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -449,8 +498,10 @@ for both attributes: `genre` and `format`, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -473,6 +524,7 @@ for both attributes: `genre` and `format`, as the following example shows: // ... } ---- +====== -- Finally, the bean definitions should contain matching qualifier values. This example diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc index 2e5f36016b6..595675cc295 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc @@ -9,8 +9,11 @@ examples included in this section. See xref:core/beans/standard-annotations.adoc You can apply the `@Autowired` annotation to constructors, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -24,12 +27,15 @@ You can apply the `@Autowired` annotation to constructors, as the following exam // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender @Autowired constructor( private val customerPreferenceDao: CustomerPreferenceDao) ---- +====== [NOTE] ==== @@ -44,8 +50,11 @@ xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-cons You can also apply the `@Autowired` annotation to _traditional_ setter methods, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -59,8 +68,10 @@ as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleMovieLister { @@ -71,12 +82,16 @@ as the following example shows: } ---- +====== You can also apply the annotation to methods with arbitrary names and multiple arguments, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -94,8 +109,10 @@ arguments, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -113,12 +130,16 @@ arguments, as the following example shows: // ... } ---- +====== You can apply `@Autowired` to fields as well and even mix it with constructors, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -135,8 +156,10 @@ following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender @Autowired constructor( private val customerPreferenceDao: CustomerPreferenceDao) { @@ -147,6 +170,7 @@ following example shows: // ... } ---- +====== [TIP] ==== @@ -166,8 +190,11 @@ You can also instruct Spring to provide all beans of a particular type from the `ApplicationContext` by adding the `@Autowired` annotation to a field or method that expects an array of that type, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -177,8 +204,10 @@ expects an array of that type, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -188,11 +217,15 @@ expects an array of that type, as the following example shows: // ... } ---- +====== The same applies for typed collections, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -206,8 +239,10 @@ The same applies for typed collections, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -217,6 +252,7 @@ The same applies for typed collections, as the following example shows: // ... } ---- +====== [[beans-factory-ordered]] [TIP] @@ -241,8 +277,11 @@ Even typed `Map` instances can be autowired as long as the expected key type is The map values contain all beans of the expected type, and the keys contain the corresponding bean names, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -256,8 +295,10 @@ corresponding bean names, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -267,6 +308,7 @@ corresponding bean names, as the following example shows: // ... } ---- +====== By default, autowiring fails when no matching candidate beans are available for a given injection point. In the case of a declared array, collection, or map, at least one @@ -277,8 +319,11 @@ dependencies. You can change this behavior as demonstrated in the following exam enabling the framework to skip a non-satisfiable injection point through marking it as non-required (i.e., by setting the `required` attribute in `@Autowired` to `false`): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -292,8 +337,10 @@ non-required (i.e., by setting the `required` attribute in `@Autowired` to `fals // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleMovieLister { @@ -303,6 +350,7 @@ non-required (i.e., by setting the `required` attribute in `@Autowired` to `fals // ... } ---- +====== [NOTE] ==== @@ -317,8 +365,8 @@ that can be optionally overridden via dependency injection. ==== -[[beans-autowired-annotation-constructor-resolution]] +[[beans-autowired-annotation-constructor-resolution]] Injected constructor and factory method arguments are a special case since the `required` attribute in `@Autowired` has a somewhat different meaning due to Spring's constructor resolution algorithm that may potentially deal with multiple constructors. Constructor @@ -364,8 +412,11 @@ As of Spring Framework 5.0, you can also use a `@Nullable` annotation (of any ki in any package -- for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety support: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -375,8 +426,10 @@ Kotlin built-in null-safety support: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleMovieLister { @@ -386,6 +439,7 @@ Kotlin built-in null-safety support: // ... } ---- +====== You can also use `@Autowired` for interfaces that are well-known resolvable dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`, @@ -394,8 +448,11 @@ interfaces, such as `ConfigurableApplicationContext` or `ResourcePatternResolver automatically resolved, with no special setup necessary. The following example autowires an `ApplicationContext` object: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -408,8 +465,10 @@ an `ApplicationContext` object: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender { @@ -419,6 +478,7 @@ class MovieRecommender { // ... } ---- +====== [NOTE] ==== diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/generics-as-qualifiers.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/generics-as-qualifiers.adoc index 0f081e2d3be..f4dac3a0461 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/generics-as-qualifiers.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/generics-as-qualifiers.adoc @@ -5,8 +5,11 @@ In addition to the `@Qualifier` annotation, you can use Java generic types as an implicit form of qualification. For example, suppose you have the following configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class MyConfiguration { @@ -22,8 +25,10 @@ configuration: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class MyConfiguration { @@ -35,13 +40,17 @@ configuration: fun integerStore() = IntegerStore() } ---- +====== Assuming that the preceding beans implement a generic interface, (that is, `Store` and `Store`), you can `@Autowire` the `Store` interface and the generic is used as a qualifier, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Autowired private Store s1; // qualifier, injects the stringStore bean @@ -49,8 +58,10 @@ used as a qualifier, as the following example shows: @Autowired private Store s2; // qualifier, injects the integerStore bean ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Autowired private lateinit var s1: Store // qualifier, injects the stringStore bean @@ -58,26 +69,33 @@ used as a qualifier, as the following example shows: @Autowired private lateinit var s2: Store // qualifier, injects the integerStore bean ---- +====== Generic qualifiers also apply when autowiring lists, `Map` instances and arrays. The following example autowires a generic `List`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Inject all Store beans as long as they have an generic // Store beans will not appear in this list @Autowired private List> s; ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Inject all Store beans as long as they have an generic // Store beans will not appear in this list @Autowired private lateinit var s: List> ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/postconstruct-and-predestroy-annotations.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/postconstruct-and-predestroy-annotations.adoc index 391158c84ab..4c9a1bdcbf3 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/postconstruct-and-predestroy-annotations.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/postconstruct-and-predestroy-annotations.adoc @@ -13,8 +13,11 @@ as the corresponding Spring lifecycle interface method or explicitly declared ca method. In the following example, the cache is pre-populated upon initialization and cleared upon destruction: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class CachingMovieLister { @@ -29,8 +32,10 @@ cleared upon destruction: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class CachingMovieLister { @@ -45,6 +50,7 @@ cleared upon destruction: } } ---- +====== For details about the effects of combining various lifecycle mechanisms, see xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-combined-effects[Combining Lifecycle Mechanisms]. diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/resource.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/resource.adoc index 1a3f02af6cf..a9622299440 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/resource.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/resource.adoc @@ -11,8 +11,11 @@ the bean name to be injected. In other words, it follows by-name semantics, as demonstrated in the following example: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -24,6 +27,7 @@ as demonstrated in the following example: } } ---- +====== <1> This line injects a `@Resource`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -45,8 +49,11 @@ it takes the bean property name. The following example is going to have the bean named `movieFinder` injected into its setter method: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -58,8 +65,10 @@ named `movieFinder` injected into its setter method: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleMovieLister { @@ -68,6 +77,7 @@ named `movieFinder` injected into its setter method: } ---- +====== -- NOTE: The name provided with the annotation is resolved as a bean name by the @@ -88,8 +98,11 @@ named "customerPreferenceDao" and then falls back to a primary type match for th `CustomerPreferenceDao`: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -105,6 +118,7 @@ named "customerPreferenceDao" and then falls back to a primary type match for th // ... } ---- +====== <1> The `context` field is injected based on the known resolvable dependency type: `ApplicationContext`. diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc index 006d8da85e3..967e04af4e7 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc @@ -3,8 +3,11 @@ `@Value` is typically used to inject externalized properties: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MovieRecommender { @@ -16,29 +19,38 @@ } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MovieRecommender(@Value("\${catalog.name}") private val catalog: String) ---- +====== With the following configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @PropertySource("classpath:application.properties") public class AppConfig { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @PropertySource("classpath:application.properties") class AppConfig ---- +====== And the following `application.properties` file: @@ -55,8 +67,11 @@ will be injected as the value. If you want to maintain strict control over nonex values, you should declare a `PropertySourcesPlaceholderConfigurer` bean, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -67,8 +82,10 @@ example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -77,6 +94,7 @@ example shows: fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer() } ---- +====== NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig, the `@Bean` method must be `static`. @@ -95,8 +113,11 @@ automatically converted to `String` array without extra effort. It is possible to provide a default value as following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MovieRecommender { @@ -108,20 +129,26 @@ It is possible to provide a default value as following: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String) ---- +====== A Spring `BeanPostProcessor` uses a `ConversionService` behind the scenes to handle the process for converting the `String` value in `@Value` to the target type. If you want to provide conversion support for your own custom type, you can provide your own `ConversionService` bean instance as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -134,8 +161,10 @@ provide conversion support for your own custom type, you can provide your own } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -148,12 +177,16 @@ provide conversion support for your own custom type, you can provide your own } } ---- +====== When `@Value` contains a xref:core/expressions.adoc[`SpEL` expression] the value will be dynamically computed at runtime as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MovieRecommender { @@ -165,18 +198,24 @@ computed at runtime as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MovieRecommender( @Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String) ---- +====== SpEL also enables the use of more complex data structures: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MovieRecommender { @@ -189,12 +228,15 @@ SpEL also enables the use of more complex data structures: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MovieRecommender( @Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/beans/basics.adoc b/framework-docs/modules/ROOT/pages/core/beans/basics.adoc index c38218c32cf..b3deb285d76 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/basics.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/basics.adoc @@ -119,16 +119,22 @@ supplied to an `ApplicationContext` constructor are resource strings that let the container load configuration metadata from a variety of external resources, such as the local file system, the Java `CLASSPATH`, and so on. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ---- val context = ClassPathXmlApplicationContext("services.xml", "daos.xml") ---- +====== [NOTE] ==== @@ -295,8 +301,11 @@ a registry of different beans and their dependencies. By using the method The `ApplicationContext` lets you read bean definitions and access them, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); @@ -307,7 +316,9 @@ example shows: // use configured instance List userList = service.getUsernameList(); ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ---- import org.springframework.beans.factory.getBean @@ -321,58 +332,77 @@ example shows: // use configured instance var userList = service.getUsernameList() ---- +====== With Groovy configuration, bootstrapping looks very similar. It has a different context implementation class which is Groovy-aware (but also understands XML bean definitions). The following example shows Groovy configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy"); ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ---- val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy") ---- +====== The most flexible variant is `GenericApplicationContext` in combination with reader delegates -- for example, with `XmlBeanDefinitionReader` for XML files, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- GenericApplicationContext context = new GenericApplicationContext(); new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml"); context.refresh(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val context = GenericApplicationContext() XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml") context.refresh() ---- +====== You can also use the `GroovyBeanDefinitionReader` for Groovy files, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- GenericApplicationContext context = new GenericApplicationContext(); new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy"); context.refresh(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val context = GenericApplicationContext() GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy") context.refresh() ---- +====== You can mix and match such reader delegates on the same `ApplicationContext`, reading bean definitions from diverse configuration sources. diff --git a/framework-docs/modules/ROOT/pages/core/beans/beanfactory.adoc b/framework-docs/modules/ROOT/pages/core/beans/beanfactory.adoc index e88c5d196c7..32837957748 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/beanfactory.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/beanfactory.adoc @@ -86,8 +86,11 @@ The following table lists features provided by the `BeanFactory` and To explicitly register a bean post-processor with a `DefaultListableBeanFactory`, you need to programmatically call `addBeanPostProcessor`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // populate the factory with bean definitions @@ -98,8 +101,10 @@ you need to programmatically call `addBeanPostProcessor`, as the following examp // now start using the factory ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val factory = DefaultListableBeanFactory() // populate the factory with bean definitions @@ -110,12 +115,16 @@ you need to programmatically call `addBeanPostProcessor`, as the following examp // now start using the factory ---- +====== To apply a `BeanFactoryPostProcessor` to a plain `DefaultListableBeanFactory`, you need to call its `postProcessBeanFactory` method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); @@ -128,8 +137,10 @@ you need to call its `postProcessBeanFactory` method, as the following example s // now actually do the replacement cfg.postProcessBeanFactory(factory); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val factory = DefaultListableBeanFactory() val reader = XmlBeanDefinitionReader(factory) @@ -142,6 +153,7 @@ you need to call its `postProcessBeanFactory` method, as the following example s // now actually do the replacement cfg.postProcessBeanFactory(factory) ---- +====== In both cases, the explicit registration steps are inconvenient, which is why the various `ApplicationContext` variants are preferred over a plain diff --git a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc index e595b82d608..e3f79239223 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc @@ -55,8 +55,11 @@ own code. A meta-annotation is an annotation that can be applied to another anno For example, the `@Service` annotation mentioned xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[earlier] is meta-annotated with `@Component`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -67,6 +70,7 @@ is meta-annotated with `@Component`, as the following example shows: // ... } ---- +====== <1> The `@Component` causes `@Service` to be treated in the same way as `@Component`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -94,8 +98,11 @@ want to only expose a subset of the meta-annotation's attributes. For example, S customization of the `proxyMode`. The following listing shows the definition of the `SessionScope` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -112,8 +119,10 @@ customization of the `proxyMode`. The following listing shows the definition of } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @@ -124,11 +133,15 @@ customization of the `proxyMode`. The following listing shows the definition of val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS ) ---- +====== You can then use `@SessionScope` without declaring the `proxyMode` as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Service @SessionScope @@ -136,8 +149,10 @@ You can then use `@SessionScope` without declaring the `proxyMode` as follows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Service @SessionScope @@ -145,11 +160,15 @@ You can then use `@SessionScope` without declaring the `proxyMode` as follows: // ... } ---- +====== You can also override the value for the `proxyMode`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Service @SessionScope(proxyMode = ScopedProxyMode.INTERFACES) @@ -157,8 +176,10 @@ You can also override the value for the `proxyMode`, as the following example sh // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Service @SessionScope(proxyMode = ScopedProxyMode.INTERFACES) @@ -166,6 +187,7 @@ You can also override the value for the `proxyMode`, as the following example sh // ... } ---- +====== For further details, see the https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model] @@ -180,8 +202,11 @@ Spring can automatically detect stereotyped classes and register corresponding `BeanDefinition` instances with the `ApplicationContext`. For example, the following two classes are eligible for such autodetection: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Service public class SimpleMovieLister { @@ -193,29 +218,38 @@ are eligible for such autodetection: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Service class SimpleMovieLister(private val movieFinder: MovieFinder) ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository public class JpaMovieFinder implements MovieFinder { // implementation elided for clarity } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Repository class JpaMovieFinder : MovieFinder { // implementation elided for clarity } ---- +====== To autodetect these classes and register the corresponding beans, you need to add @@ -223,8 +257,11 @@ To autodetect these classes and register the corresponding beans, you need to ad is a common parent package for the two classes. (Alternatively, you can specify a comma- or semicolon- or space-separated list that includes the parent package of each class.) +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan(basePackages = "org.example") @@ -232,8 +269,10 @@ comma- or semicolon- or space-separated list that includes the parent package of // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ComponentScan(basePackages = ["org.example"]) @@ -241,6 +280,7 @@ comma- or semicolon- or space-separated list that includes the parent package of // ... } ---- +====== NOTE: For brevity, the preceding example could have used the `value` attribute of the annotation (that is, `@ComponentScan("org.example")`). @@ -335,8 +375,11 @@ The following table describes the filtering options: The following example shows the configuration ignoring all `@Repository` annotations and using "`stub`" repositories instead: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan(basePackages = "org.example", @@ -346,8 +389,10 @@ and using "`stub`" repositories instead: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ComponentScan(basePackages = ["org.example"], @@ -357,6 +402,7 @@ and using "`stub`" repositories instead: // ... } ---- +====== The following listing shows the equivalent XML: @@ -387,8 +433,11 @@ Spring components can also contribute bean definition metadata to the container. this with the same `@Bean` annotation used to define bean metadata within `@Configuration` annotated classes. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class FactoryMethodComponent { @@ -404,8 +453,10 @@ annotated classes. The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class FactoryMethodComponent { @@ -419,6 +470,7 @@ annotated classes. The following example shows how to do so: } } ---- +====== The preceding class is a Spring component that has application-specific code in its `doWork()` method. However, it also contributes a bean definition that has a factory @@ -436,8 +488,11 @@ with optional dependencies, we recommend `ObjectProvider` instead. Autowired fields and methods are supported, as previously discussed, with additional support for autowiring of `@Bean` methods. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class FactoryMethodComponent { @@ -473,8 +528,10 @@ support for autowiring of `@Bean` methods. The following example shows how to do } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class FactoryMethodComponent { @@ -504,6 +561,7 @@ support for autowiring of `@Bean` methods. The following example shows how to do fun requestScopedInstance() = TestBean("requestScopedInstance", 3) } ---- +====== The example autowires the `String` method parameter `country` to the value of the `age` property on another bean named `privateInstance`. A Spring Expression Language element @@ -522,8 +580,11 @@ injection point that triggered the creation of a new bean instance in the given You can use the provided injection point metadata with semantic care in such scenarios. The following example shows how to use `InjectionPoint`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class FactoryMethodComponent { @@ -534,8 +595,10 @@ The following example shows how to use `InjectionPoint`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class FactoryMethodComponent { @@ -546,6 +609,7 @@ The following example shows how to use `InjectionPoint`: TestBean("prototypeInstance for ${injectionPoint.member}") } ---- +====== The `@Bean` methods in a regular Spring component are processed differently than their counterparts inside a Spring `@Configuration` class. The difference is that `@Component` @@ -609,39 +673,51 @@ If such an annotation contains no name `value` or for any other detected compone the uncapitalized non-qualified class name. For example, if the following component classes were detected, the names would be `myMovieLister` and `movieFinderImpl`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Service("myMovieLister") public class SimpleMovieLister { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Service("myMovieLister") class SimpleMovieLister { // ... } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository public class MovieFinderImpl implements MovieFinder { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Repository class MovieFinderImpl : MovieFinder { // ... } ---- +====== If you do not want to rely on the default bean-naming strategy, you can provide a custom bean-naming strategy. First, implement the @@ -657,8 +733,11 @@ fully qualified class name for the generated bean name. As of Spring Framework 5 `FullyQualifiedAnnotationBeanNameGenerator` located in package `org.springframework.context.annotation` can be used for such purposes. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) @@ -666,8 +745,10 @@ fully qualified class name for the generated bean name. As of Spring Framework 5 // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class) @@ -675,6 +756,7 @@ fully qualified class name for the generated bean name. As of Spring Framework 5 // ... } ---- +====== [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -698,8 +780,11 @@ autodetected components is `singleton`. However, sometimes you need a different that can be specified by the `@Scope` annotation. You can provide the name of the scope within the annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Scope("prototype") @Repository @@ -707,8 +792,10 @@ scope within the annotation, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Scope("prototype") @Repository @@ -716,6 +803,7 @@ scope within the annotation, as the following example shows: // ... } ---- +====== NOTE: `@Scope` annotations are only introspected on the concrete bean class (for annotated components) or the factory method (for `@Bean` methods). In contrast to XML bean @@ -735,8 +823,11 @@ interface. Be sure to include a default no-arg constructor. Then you can provide fully qualified class name when configuring the scanner, as the following example of both an annotation and a bean definition shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) @@ -744,8 +835,10 @@ an annotation and a bean definition shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class) @@ -753,6 +846,7 @@ an annotation and a bean definition shows: // ... } ---- +====== [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -767,8 +861,11 @@ For this purpose, a scoped-proxy attribute is available on the component-scan element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example, the following configuration results in standard JDK dynamic proxies: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES) @@ -776,8 +873,10 @@ the following configuration results in standard JDK dynamic proxies: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES) @@ -785,6 +884,7 @@ the following configuration results in standard JDK dynamic proxies: // ... } ---- +====== [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -808,8 +908,11 @@ auto-detection of components, you can provide the qualifier metadata with type-l annotations on the candidate class. The following three examples demonstrate this technique: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component @Qualifier("Action") @@ -817,16 +920,22 @@ technique: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component @Qualifier("Action") class ActionMovieCatalog : MovieCatalog ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component @Genre("Action") @@ -834,8 +943,10 @@ technique: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component @Genre("Action") @@ -843,9 +954,13 @@ technique: // ... } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component @Offline @@ -853,8 +968,10 @@ technique: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component @Offline @@ -862,6 +979,7 @@ class CachingMovieCatalog : MovieCatalog { // ... } ---- +====== NOTE: As with most annotation-based alternatives, keep in mind that the annotation metadata is bound to the class definition itself, while the use of XML allows for multiple beans diff --git a/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc b/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc index 4caec82ef44..c77bc8a9235 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc @@ -98,8 +98,11 @@ The next example shows a program to run the `MessageSource` functionality. Remember that all `ApplicationContext` implementations are also `MessageSource` implementations and so can be cast to the `MessageSource` interface. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); @@ -107,8 +110,10 @@ implementations and so can be cast to the `MessageSource` interface. System.out.println(message); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun main() { val resources = ClassPathXmlApplicationContext("beans.xml") @@ -116,6 +121,7 @@ implementations and so can be cast to the `MessageSource` interface. println(message) } ---- +====== The resulting output from the above program is as follows: @@ -151,8 +157,11 @@ converted into `String` objects and inserted into placeholders in the lookup mes ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class Example { @@ -169,8 +178,10 @@ converted into `String` objects and inserted into placeholders in the lookup mes } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Example { @@ -183,6 +194,7 @@ converted into `String` objects and inserted into placeholders in the lookup mes } } ---- +====== The resulting output from the invocation of the `execute()` method is as follows: @@ -208,8 +220,11 @@ resolved is specified manually: argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required. ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); @@ -218,8 +233,10 @@ argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required. System.out.println(message); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun main() { val resources = ClassPathXmlApplicationContext("beans.xml") @@ -228,6 +245,7 @@ argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required. println(message) } ---- +====== The resulting output from the running of the above program is as follows: @@ -322,8 +340,11 @@ The following table describes the standard events that Spring provides: You can also create and publish your own custom events. The following example shows a simple class that extends Spring's `ApplicationEvent` base class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class BlockedListEvent extends ApplicationEvent { @@ -339,21 +360,27 @@ simple class that extends Spring's `ApplicationEvent` base class: // accessor and other methods... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class BlockedListEvent(source: Any, val address: String, val content: String) : ApplicationEvent(source) ---- +====== To publish a custom `ApplicationEvent`, call the `publishEvent()` method on an `ApplicationEventPublisher`. Typically, this is done by creating a class that implements `ApplicationEventPublisherAware` and registering it as a Spring bean. The following example shows such a class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class EmailService implements ApplicationEventPublisherAware { @@ -377,8 +404,10 @@ example shows such a class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class EmailService : ApplicationEventPublisherAware { @@ -402,6 +431,7 @@ example shows such a class: } } ---- +====== At configuration time, the Spring container detects that `EmailService` implements `ApplicationEventPublisherAware` and automatically calls @@ -413,8 +443,11 @@ To receive the custom `ApplicationEvent`, you can create a class that implements `ApplicationListener` and register it as a Spring bean. The following example shows such a class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class BlockedListNotifier implements ApplicationListener { @@ -429,8 +462,10 @@ shows such a class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class BlockedListNotifier : ApplicationListener { @@ -441,6 +476,7 @@ shows such a class: } } ---- +====== Notice that `ApplicationListener` is generically parameterized with the type of your custom event (`BlockedListEvent` in the preceding example). This means that the @@ -497,8 +533,11 @@ architectures that build upon the well-known Spring programming model. You can register an event listener on any method of a managed bean by using the `@EventListener` annotation. The `BlockedListNotifier` can be rewritten as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class BlockedListNotifier { @@ -514,8 +553,10 @@ You can register an event listener on any method of a managed bean by using the } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class BlockedListNotifier { @@ -527,6 +568,7 @@ You can register an event listener on any method of a managed bean by using the } } ---- +====== The method signature once again declares the event type to which it listens, but, this time, with a flexible name and without implementing a specific listener interface. @@ -537,22 +579,28 @@ If your method should listen to several events or if you want to define it with parameter at all, the event types can also be specified on the annotation itself. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) public void handleContextStart() { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class) fun handleContextStart() { // ... } ---- +====== It is also possible to add additional runtime filtering by using the `condition` attribute of the annotation that defines a xref:core/expressions.adoc[`SpEL` expression], which should match @@ -561,22 +609,28 @@ to actually invoke the method for a particular event. The following example shows how our notifier can be rewritten to be invoked only if the `content` attribute of the event is equal to `my-event`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @EventListener(condition = "#blEvent.content == 'my-event'") public void processBlockedListEvent(BlockedListEvent blEvent) { // notify appropriate parties via notificationAddress... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @EventListener(condition = "#blEvent.content == 'my-event'") fun processBlockedListEvent(blEvent: BlockedListEvent) { // notify appropriate parties via notificationAddress... } ---- +====== Each `SpEL` expression evaluates against a dedicated context. The following table lists the items made available to the context so that you can use them for conditional event processing: @@ -611,8 +665,11 @@ signature actually refers to an arbitrary object that was published. If you need to publish an event as the result of processing another event, you can change the method signature to return the event that should be published, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @EventListener public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) { @@ -620,8 +677,10 @@ method signature to return the event that should be published, as the following // then publish a ListUpdateEvent... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @EventListener fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent { @@ -629,6 +688,7 @@ method signature to return the event that should be published, as the following // then publish a ListUpdateEvent... } ---- +====== NOTE: This feature is not supported for xref:core/beans/context-introduction.adoc#context-functionality-events-async[asynchronous listeners]. @@ -645,8 +705,11 @@ If you want a particular listener to process events asynchronously, you can reus xref:integration/scheduling.adoc#scheduling-annotation-support-async[regular `@Async` support]. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @EventListener @Async @@ -654,8 +717,10 @@ The following example shows how to do so: // BlockedListEvent is processed in a separate thread } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @EventListener @Async @@ -663,6 +728,7 @@ The following example shows how to do so: // BlockedListEvent is processed in a separate thread } ---- +====== Be aware of the following limitations when using asynchronous events: @@ -682,8 +748,11 @@ Be aware of the following limitations when using asynchronous events: If you need one listener to be invoked before another one, you can add the `@Order` annotation to the method declaration, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @EventListener @Order(42) @@ -691,8 +760,10 @@ annotation to the method declaration, as the following example shows: // notify appropriate parties via notificationAddress... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @EventListener @Order(42) @@ -700,6 +771,7 @@ annotation to the method declaration, as the following example shows: // notify appropriate parties via notificationAddress... } ---- +====== [[context-functionality-events-generics]] @@ -710,22 +782,28 @@ You can also use generics to further define the structure of your event. Conside can create the following listener definition to receive only `EntityCreatedEvent` for a `Person`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @EventListener public void onPersonCreated(EntityCreatedEvent event) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @EventListener fun onPersonCreated(event: EntityCreatedEvent) { // ... } ---- +====== Due to type erasure, this works only if the event that is fired resolves the generic parameters on which the event listener filters (that is, something like @@ -736,8 +814,11 @@ structure (as should be the case for the event in the preceding example). In suc you can implement `ResolvableTypeProvider` to guide the framework beyond what the runtime environment provides. The following event shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class EntityCreatedEvent extends ApplicationEvent implements ResolvableTypeProvider { @@ -751,8 +832,10 @@ environment provides. The following event shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class EntityCreatedEvent(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider { @@ -761,6 +844,7 @@ environment provides. The following event shows how to do so: } } ---- +====== TIP: This works not only for `ApplicationEvent` but any arbitrary object that you send as an event. @@ -819,8 +903,11 @@ The `AbstractApplicationContext` (and its subclasses) is instrumented with an Here is an example of instrumentation in the `AnnotationConfigApplicationContext`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // create a startup step and start recording StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan"); @@ -831,8 +918,10 @@ Here is an example of instrumentation in the `AnnotationConfigApplicationContext // end the current step scanPackages.end(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // create a startup step and start recording val scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan") @@ -843,6 +932,7 @@ Here is an example of instrumentation in the `AnnotationConfigApplicationContext // end the current step scanPackages.end() ---- +====== The application context is already instrumented with multiple steps. Once recorded, these startup steps can be collected, displayed and analyzed with specific tools. diff --git a/framework-docs/modules/ROOT/pages/core/beans/context-load-time-weaver.adoc b/framework-docs/modules/ROOT/pages/core/beans/context-load-time-weaver.adoc index c7280792b3f..73579b414de 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/context-load-time-weaver.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/context-load-time-weaver.adoc @@ -7,21 +7,27 @@ loaded into the Java virtual machine (JVM). To enable load-time weaving, you can add the `@EnableLoadTimeWeaving` to one of your `@Configuration` classes, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableLoadTimeWeaving public class AppConfig { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableLoadTimeWeaving class AppConfig ---- +====== Alternatively, for XML configuration, you can use the `context:load-time-weaver` element: diff --git a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc index 48efa61acae..97164897c99 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc @@ -260,8 +260,11 @@ specify a factory method: The following example shows a class that would work with the preceding bean definition: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ClientService { private static ClientService clientService = new ClientService(); @@ -272,8 +275,10 @@ The following example shows a class that would work with the preceding bean defi } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ClientService private constructor() { companion object { @@ -283,6 +288,7 @@ The following example shows a class that would work with the preceding bean defi } } ---- +====== For details about the mechanism for supplying (optional) arguments to the factory method and setting object instance properties after the object is returned from the factory, @@ -316,8 +322,11 @@ how to configure such a bean: The following example shows the corresponding class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class DefaultServiceLocator { @@ -328,8 +337,10 @@ The following example shows the corresponding class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class DefaultServiceLocator { companion object { @@ -340,6 +351,7 @@ The following example shows the corresponding class: } } ---- +====== One factory class can also hold more than one factory method, as the following example shows: @@ -360,8 +372,11 @@ One factory class can also hold more than one factory method, as the following e The following example shows the corresponding class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class DefaultServiceLocator { @@ -378,8 +393,10 @@ The following example shows the corresponding class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class DefaultServiceLocator { companion object { @@ -396,6 +413,7 @@ The following example shows the corresponding class: } } ---- +====== This approach shows that the factory bean itself can be managed and configured through dependency injection (DI). See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail] diff --git a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc index becfead14bc..25bbaff2b63 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc @@ -30,8 +30,11 @@ treats arguments to a constructor and to a `static` factory method similarly. Th following example shows a class that can only be dependency-injected with constructor injection: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -46,14 +49,17 @@ injection: // business logic that actually uses the injected MovieFinder is omitted... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // a constructor so that the Spring container can inject a MovieFinder class SimpleMovieLister(private val movieFinder: MovieFinder) { // business logic that actually uses the injected MovieFinder is omitted... } ---- +====== Notice that there is nothing special about this class. It is a POJO that has no dependencies on container specific interfaces, base classes, or annotations. @@ -67,8 +73,11 @@ order in which the constructor arguments are defined in a bean definition is the in which those arguments are supplied to the appropriate constructor when the bean is being instantiated. Consider the following class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package x.y; @@ -79,13 +88,16 @@ being instantiated. Consider the following class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package x.y class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree) ---- +====== Assuming that the `ThingTwo` and `ThingThree` classes are not related by inheritance, no potential ambiguity exists. Thus, the following configuration works fine, and you do not @@ -111,8 +123,11 @@ case with the preceding example). When a simple type is used, such as `true`, Spring cannot determine the type of the value, and so cannot match by type without help. Consider the following class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package examples; @@ -130,8 +145,10 @@ by type without help. Consider the following class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package examples @@ -140,6 +157,7 @@ by type without help. Consider the following class: private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything ) ---- +====== .[[beans-factory-ctor-arguments-type]]Constructor argument type matching -- @@ -195,8 +213,11 @@ https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.h JDK annotation to explicitly name your constructor arguments. The sample class would then have to look as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package examples; @@ -211,8 +232,10 @@ then have to look as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package examples @@ -220,6 +243,7 @@ then have to look as follows: @ConstructorProperties("years", "ultimateAnswer") constructor(val years: Int, val ultimateAnswer: String) ---- +====== -- @@ -234,8 +258,11 @@ The following example shows a class that can only be dependency-injected by usin setter injection. This class is conventional Java. It is a POJO that has no dependencies on container specific interfaces, base classes, or annotations. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -250,8 +277,10 @@ on container specific interfaces, base classes, or annotations. // business logic that actually uses the injected MovieFinder is omitted... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleMovieLister { @@ -261,6 +290,7 @@ class SimpleMovieLister { // business logic that actually uses the injected MovieFinder is omitted... } ---- +====== The `ApplicationContext` supports constructor-based and setter-based DI for the beans it @@ -403,8 +433,11 @@ part of a Spring XML configuration file specifies some bean definitions as follo The following example shows the corresponding `ExampleBean` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ExampleBean { @@ -427,8 +460,10 @@ The following example shows the corresponding `ExampleBean` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ExampleBean { lateinit var beanOne: AnotherBean @@ -436,6 +471,7 @@ class ExampleBean { var i: Int = 0 } ---- +====== In the preceding example, setters are declared to match against the properties specified in the XML file. The following example uses constructor-based DI: @@ -460,8 +496,11 @@ in the XML file. The following example uses constructor-based DI: The following example shows the corresponding `ExampleBean` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ExampleBean { @@ -479,14 +518,17 @@ The following example shows the corresponding `ExampleBean` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ExampleBean( private val beanOne: AnotherBean, private val beanTwo: YetAnotherBean, private val i: Int) ---- +====== The constructor arguments specified in the bean definition are used as arguments to the constructor of the `ExampleBean`. @@ -508,8 +550,11 @@ told to call a `static` factory method to return an instance of the object: The following example shows the corresponding `ExampleBean` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ExampleBean { @@ -530,8 +575,10 @@ The following example shows the corresponding `ExampleBean` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ExampleBean private constructor() { companion object { @@ -547,6 +594,7 @@ The following example shows the corresponding `ExampleBean` class: } } ---- +====== Arguments to the `static` factory method are supplied by `` elements, exactly the same as if a constructor had actually been used. The type of the class being diff --git a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-method-injection.adoc b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-method-injection.adoc index 18742fe6e1b..108202c40ac 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-method-injection.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-method-injection.adoc @@ -17,8 +17,11 @@ and by xref:core/beans/basics.adoc#beans-factory-client[making a `getBean("B")` typically new) bean B instance every time bean A needs it. The following example shows this approach: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"] -.Java ---- package fiona.apple; @@ -54,8 +57,10 @@ shows this approach: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"] -.Kotlin ---- package fiona.apple @@ -86,6 +91,7 @@ shows this approach: } } ---- +====== The preceding is not desirable, because the business code is aware of and coupled to the Spring Framework. Method Injection, a somewhat advanced feature of the Spring IoC @@ -127,8 +133,11 @@ Spring container dynamically overrides the implementation of the `createCommand( method. The `CommandManager` class does not have any Spring dependencies, as the reworked example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"] -.Java ---- package fiona.apple; @@ -148,8 +157,10 @@ the reworked example shows: protected abstract Command createCommand(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"] -.Kotlin ---- package fiona.apple @@ -169,6 +180,7 @@ the reworked example shows: protected abstract fun createCommand(): Command } ---- +====== In the client class that contains the method to be injected (the `CommandManager` in this case), the method to be injected requires a signature of the following form: @@ -204,8 +216,11 @@ bean is returned each time. Alternatively, within the annotation-based component model, you can declare a lookup method through the `@Lookup` annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public abstract class CommandManager { @@ -219,8 +234,10 @@ method through the `@Lookup` annotation, as the following example shows: protected abstract Command createCommand(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- abstract class CommandManager { @@ -234,12 +251,16 @@ method through the `@Lookup` annotation, as the following example shows: protected abstract fun createCommand(): Command } ---- +====== Or, more idiomatically, you can rely on the target bean getting resolved against the declared return type of the lookup method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public abstract class CommandManager { @@ -253,8 +274,10 @@ declared return type of the lookup method: protected abstract Command createCommand(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- abstract class CommandManager { @@ -268,6 +291,7 @@ declared return type of the lookup method: protected abstract fun createCommand(): Command } ---- +====== Note that you should typically declare such annotated lookup methods with a concrete stub implementation, in order for them to be compatible with Spring's component @@ -296,8 +320,11 @@ With XML-based configuration metadata, you can use the `replaced-method` element replace an existing method implementation with another, for a deployed bean. Consider the following class, which has a method called `computeValue` that we want to override: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyValueCalculator { @@ -308,8 +335,10 @@ the following class, which has a method called `computeValue` that we want to ov // some other methods... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyValueCalculator { @@ -320,12 +349,16 @@ the following class, which has a method called `computeValue` that we want to ov // some other methods... } ---- +====== A class that implements the `org.springframework.beans.factory.support.MethodReplacer` interface provides the new method definition, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- /** * meant to be used to override the existing computeValue(String) @@ -341,8 +374,10 @@ interface provides the new method definition, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- /** * meant to be used to override the existing computeValue(String) @@ -358,6 +393,7 @@ interface provides the new method definition, as the following example shows: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc index 25b41d07a76..ae7874fa329 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc @@ -350,8 +350,11 @@ type-conversion support such that the elements of your strongly-typed `Collectio instances are converted to the appropriate type prior to being added to the `Collection`. The following Java class and bean definition show how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SomeClass { @@ -362,13 +365,16 @@ The following Java class and bean definition show how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SomeClass { lateinit var accounts: Map } ---- +====== [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -408,16 +414,22 @@ following XML-based configuration metadata snippet sets the `email` property to The preceding example is equivalent to the following Java code: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- exampleBean.setEmail(""); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- exampleBean.email = "" ---- +====== The `` element handles `null` values. The following listing shows an example: @@ -433,16 +445,22 @@ The `` element handles `null` values. The following listing shows an exam The preceding configuration is equivalent to the following Java code: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- exampleBean.setEmail(null); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- exampleBean.email = null ---- +====== [[beans-p-namespace]] diff --git a/framework-docs/modules/ROOT/pages/core/beans/environment.adoc b/framework-docs/modules/ROOT/pages/core/beans/environment.adoc index 137e0c32b4e..441fdb49b56 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/environment.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/environment.adoc @@ -39,8 +39,11 @@ B deployments. Consider the first use case in a practical application that requires a `DataSource`. In a test environment, the configuration might resemble the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Bean public DataSource dataSource() { @@ -51,8 +54,10 @@ Consider the first use case in a practical application that requires a .build(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Bean fun dataSource(): DataSource { @@ -63,14 +68,18 @@ Consider the first use case in a practical application that requires a .build() } ---- +====== Now consider how this application can be deployed into a QA or production environment, assuming that the datasource for the application is registered with the production application server's JNDI directory. Our `dataSource` bean now looks like the following listing: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Bean(destroyMethod = "") public DataSource dataSource() throws Exception { @@ -78,8 +87,10 @@ now looks like the following listing: return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Bean(destroyMethod = "") fun dataSource(): DataSource { @@ -87,6 +98,7 @@ now looks like the following listing: return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource } ---- +====== The problem is how to switch between using these two variations based on the current environment. Over time, Spring users have devised a number of ways to @@ -112,8 +124,11 @@ when one or more specified profiles are active. Using our preceding example, we can rewrite the `dataSource` configuration as follows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @Profile("development") @@ -129,8 +144,10 @@ can rewrite the `dataSource` configuration as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @Profile("development") @@ -146,11 +163,15 @@ can rewrite the `dataSource` configuration as follows: } } ---- +====== -- -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @Profile("production") @@ -163,6 +184,7 @@ can rewrite the `dataSource` configuration as follows: } } ---- +====== <1> `@Bean(destroyMethod = "")` disables default destroy method inference. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -206,8 +228,11 @@ of creating a custom composed annotation. The following example defines a custom `@Profile("production")`: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -215,14 +240,17 @@ of creating a custom composed annotation. The following example defines a custom public @interface Production { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @Profile("production") annotation class Production ---- +====== -- TIP: If a `@Configuration` class is marked with `@Profile`, all of the `@Bean` methods and @@ -239,8 +267,11 @@ of a configuration class (for example, for alternative variants of a particular the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -263,6 +294,7 @@ the following example shows: } } ---- +====== <1> The `standaloneDataSource` method is available only in the `development` profile. <2> The `jndiDataSource` method is available only in the `production` profile. @@ -416,16 +448,21 @@ Activating a profile can be done in several ways, but the most straightforward i it programmatically against the `Environment` API which is available through an `ApplicationContext`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("development"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = AnnotationConfigApplicationContext().apply { environment.setActiveProfiles("development") @@ -433,6 +470,7 @@ it programmatically against the `Environment` API which is available through an refresh() } ---- +====== In addition, you can also declaratively activate profiles through the `spring.profiles.active` property, which may be specified through system environment @@ -447,16 +485,22 @@ profiles at once. Programmatically, you can provide multiple profile names to th `setActiveProfiles()` method, which accepts `String...` varargs. The following example activates multiple profiles: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ctx.getEnvironment().setActiveProfiles("profile1", "profile2"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- ctx.getEnvironment().setActiveProfiles("profile1", "profile2") ---- +====== Declaratively, `spring.profiles.active` may accept a comma-separated list of profile names, as the following example shows: @@ -473,8 +517,11 @@ as the following example shows: The default profile represents the profile that is enabled by default. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @Profile("default") @@ -489,8 +536,10 @@ following example: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @Profile("default") @@ -505,6 +554,7 @@ following example: } } ---- +====== If no profile is active, the `dataSource` is created. You can see this as a way to provide a default definition for one or more beans. If any @@ -521,22 +571,28 @@ the `Environment` or, declaratively, by using the `spring.profiles.default` prop Spring's `Environment` abstraction provides search operations over a configurable hierarchy of property sources. Consider the following listing: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new GenericApplicationContext(); Environment env = ctx.getEnvironment(); boolean containsMyProperty = env.containsProperty("my-property"); System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = GenericApplicationContext() val env = ctx.environment val containsMyProperty = env.containsProperty("my-property") println("Does my environment contain the 'my-property' property? $containsMyProperty") ---- +====== In the preceding snippet, we see a high-level way of asking Spring whether the `my-property` property is defined for the current environment. To answer this question, the `Environment` object performs @@ -580,20 +636,26 @@ of properties that you want to integrate into this search. To do so, implement and instantiate your own `PropertySource` and add it to the set of `PropertySources` for the current `Environment`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource()); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = GenericApplicationContext() val sources = ctx.environment.propertySources sources.addFirst(MyPropertySource()) ---- +====== In the preceding code, `MyPropertySource` has been added with highest precedence in the search. If it contains a `my-property` property, the property is detected and returned, in favor of @@ -615,8 +677,11 @@ Given a file called `app.properties` that contains the key-value pair `testbean. the following `@Configuration` class uses `@PropertySource` in such a way that a call to `testBean.getName()` returns `myTestBean`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @PropertySource("classpath:/com/myco/app.properties") @@ -633,8 +698,10 @@ a call to `testBean.getName()` returns `myTestBean`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @PropertySource("classpath:/com/myco/app.properties") @@ -649,13 +716,17 @@ a call to `testBean.getName()` returns `myTestBean`: } } ---- +====== Any `${...}` placeholders present in a `@PropertySource` resource location are resolved against the set of property sources already registered against the environment, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties") @@ -672,8 +743,10 @@ environment, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties") @@ -688,6 +761,7 @@ environment, as the following example shows: } } ---- +====== Assuming that `my.placeholder` is present in one of the property sources already registered (for example, system properties or environment variables), the placeholder is diff --git a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc index df156b2372f..6882cdb7add 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc @@ -120,8 +120,11 @@ it is created by the container and prints the resulting string to the system con The following listing shows the custom `BeanPostProcessor` implementation class definition: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package scripting; @@ -140,8 +143,10 @@ The following listing shows the custom `BeanPostProcessor` implementation class } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package scripting @@ -160,6 +165,7 @@ The following listing shows the custom `BeanPostProcessor` implementation class } } ---- +====== The following `beans` element uses the `InstantiationTracingBeanPostProcessor`: @@ -196,8 +202,11 @@ xref:languages/dynamic.adoc[Dynamic Language Support].) The following Java application runs the preceding code and configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -213,8 +222,10 @@ The following Java application runs the preceding code and configuration: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -224,6 +235,7 @@ The following Java application runs the preceding code and configuration: println(messenger) } ---- +====== The output of the preceding application resembles the following: diff --git a/framework-docs/modules/ROOT/pages/core/beans/factory-nature.adoc b/framework-docs/modules/ROOT/pages/core/beans/factory-nature.adoc index b13f08e7952..634ee04486f 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/factory-nature.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/factory-nature.adoc @@ -67,8 +67,11 @@ no-argument signature. With Java configuration, you can use the `initMethod` att ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ExampleBean { @@ -77,8 +80,10 @@ no-argument signature. With Java configuration, you can use the `initMethod` att } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ExampleBean { @@ -87,6 +92,7 @@ no-argument signature. With Java configuration, you can use the `initMethod` att } } ---- +====== The preceding example has almost exactly the same effect as the following example (which consists of two listings): @@ -96,8 +102,11 @@ The preceding example has almost exactly the same effect as the following exampl ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class AnotherExampleBean implements InitializingBean { @@ -107,8 +116,10 @@ The preceding example has almost exactly the same effect as the following exampl } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class AnotherExampleBean : InitializingBean { @@ -117,6 +128,7 @@ The preceding example has almost exactly the same effect as the following exampl } } ---- +====== However, the first of the two preceding examples does not couple the code to Spring. @@ -146,8 +158,11 @@ xref:core/beans/java/bean-annotation.adoc#beans-java-lifecycle-callbacks[Receivi ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ExampleBean { @@ -156,8 +171,10 @@ xref:core/beans/java/bean-annotation.adoc#beans-java-lifecycle-callbacks[Receivi } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ExampleBean { @@ -166,6 +183,7 @@ xref:core/beans/java/bean-annotation.adoc#beans-java-lifecycle-callbacks[Receivi } } ---- +====== The preceding definition has almost exactly the same effect as the following definition: @@ -174,8 +192,11 @@ The preceding definition has almost exactly the same effect as the following def ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class AnotherExampleBean implements DisposableBean { @@ -185,8 +206,10 @@ The preceding definition has almost exactly the same effect as the following def } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class AnotherExampleBean : DisposableBean { @@ -195,6 +218,7 @@ The preceding definition has almost exactly the same effect as the following def } } ---- +====== However, the first of the two preceding definitions does not couple the code to Spring. @@ -229,8 +253,11 @@ Suppose that your initialization callback methods are named `init()` and your de callback methods are named `destroy()`. Your class then resembles the class in the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class DefaultBlogService implements BlogService { @@ -248,8 +275,10 @@ following example: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class DefaultBlogService : BlogService { @@ -263,6 +292,7 @@ following example: } } ---- +====== You could then use that class in a bean resembling the following: @@ -478,8 +508,11 @@ and implement these destroy callbacks correctly. To register a shutdown hook, call the `registerShutdownHook()` method that is declared on the `ConfigurableApplicationContext` interface, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -498,8 +531,10 @@ declared on the `ConfigurableApplicationContext` interface, as the following exa } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.context.support.ClassPathXmlApplicationContext @@ -514,6 +549,7 @@ declared on the `ConfigurableApplicationContext` interface, as the following exa // main method exits, hook is called prior to the app shutting down... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/beans/factory-scopes.adoc b/framework-docs/modules/ROOT/pages/core/beans/factory-scopes.adoc index 06eb5eba3f5..6a835bebb2f 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/factory-scopes.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/factory-scopes.adoc @@ -247,8 +247,11 @@ When using annotation-driven components or Java configuration, the `@RequestScop can be used to assign a component to the `request` scope. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RequestScope @Component @@ -256,8 +259,10 @@ to do so: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RequestScope @Component @@ -265,6 +270,7 @@ to do so: // ... } ---- +====== @@ -291,8 +297,11 @@ HTTP `Session` is eventually discarded, the bean that is scoped to that particul When using annotation-driven components or Java configuration, you can use the `@SessionScope` annotation to assign a component to the `session` scope. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SessionScope @Component @@ -300,8 +309,10 @@ When using annotation-driven components or Java configuration, you can use the // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SessionScope @Component @@ -309,6 +320,7 @@ When using annotation-driven components or Java configuration, you can use the // ... } ---- +====== @@ -335,8 +347,11 @@ When using annotation-driven components or Java configuration, you can use the `@ApplicationScope` annotation to assign a component to the `application` scope. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ApplicationScope @Component @@ -344,8 +359,10 @@ following example shows how to do so: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ApplicationScope @Component @@ -353,6 +370,7 @@ following example shows how to do so: // ... } ---- +====== @@ -551,62 +569,86 @@ does not exist, the method returns a new instance of the bean, after having boun the session for future reference). The following method returns the object from the underlying scope: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Object get(String name, ObjectFactory objectFactory) ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun get(name: String, objectFactory: ObjectFactory<*>): Any ---- +====== The session scope implementation, for example, removes the session-scoped bean from the underlying session. The object should be returned, but you can return `null` if the object with the specified name is not found. The following method removes the object from the underlying scope: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Object remove(String name) ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun remove(name: String): Any ---- +====== The following method registers a callback that the scope should invoke when it is destroyed or when the specified object in the scope is destroyed: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- void registerDestructionCallback(String name, Runnable destructionCallback) ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun registerDestructionCallback(name: String, destructionCallback: Runnable) ---- +====== See the {api-spring-framework}/beans/factory/config/Scope.html#registerDestructionCallback[javadoc] or a Spring scope implementation for more information on destruction callbacks. The following method obtains the conversation identifier for the underlying scope: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String getConversationId() ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun getConversationId(): String ---- +====== This identifier is different for each scope. For a session scoped implementation, this identifier can be the session identifier. @@ -620,16 +662,22 @@ After you write and test one or more custom `Scope` implementations, you need to the Spring container aware of your new scopes. The following method is the central method to register a new `Scope` with the Spring container: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- void registerScope(String scopeName, Scope scope); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun registerScope(scopeName: String, scope: Scope) ---- +====== This method is declared on the `ConfigurableBeanFactory` interface, which is available through the `BeanFactory` property on most of the concrete `ApplicationContext` @@ -647,18 +695,24 @@ NOTE: The next example uses `SimpleThreadScope`, which is included with Spring b registered by default. The instructions would be the same for your own custom `Scope` implementations. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val threadScope = SimpleThreadScope() beanFactory.registerScope("thread", threadScope) ---- +====== You can then create bean definitions that adhere to the scoping rules of your custom `Scope`, as follows: diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/basic-concepts.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/basic-concepts.adoc index 70236637f5a..f90a2752b7e 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/java/basic-concepts.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/java/basic-concepts.adoc @@ -15,8 +15,11 @@ source of bean definitions. Furthermore, `@Configuration` classes let inter-bean dependencies be defined by calling other `@Bean` methods in the same class. The simplest possible `@Configuration` class reads as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -27,8 +30,10 @@ The simplest possible `@Configuration` class reads as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -39,6 +44,7 @@ The simplest possible `@Configuration` class reads as follows: } } ---- +====== The preceding `AppConfig` class is equivalent to the following Spring `` XML: diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc index e39ad32d471..52089db3c58 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc @@ -21,8 +21,11 @@ method to register a bean definition within an `ApplicationContext` of the type specified as the method's return value. By default, the bean name is the same as the method name. The following example shows a `@Bean` method declaration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -33,8 +36,10 @@ the method name. The following example shows a `@Bean` method declaration: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -43,6 +48,7 @@ the method name. The following example shows a `@Bean` method declaration: fun transferService() = TransferServiceImpl() } ---- +====== The preceding configuration is exactly equivalent to the following Spring XML: @@ -65,8 +71,11 @@ transferService -> com.acme.TransferServiceImpl You can also use default methods to define beans. This allows composition of bean configurations by implementing interfaces with bean definitions on default methods. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public interface BaseConfig { @@ -81,12 +90,16 @@ configurations by implementing interfaces with bean definitions on default metho } ---- +====== You can also declare your `@Bean` method with an interface (or base class) return type, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -97,8 +110,10 @@ return type, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -109,6 +124,7 @@ return type, as the following example shows: } } ---- +====== However, this limits the visibility for advance type prediction to the specified interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`) @@ -133,8 +149,11 @@ dependencies required to build that bean. For instance, if our `TransferService` requires an `AccountRepository`, we can materialize that dependency with a method parameter, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -145,8 +164,10 @@ parameter, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -157,6 +178,7 @@ parameter, as the following example shows: } } ---- +====== The resolution mechanism is pretty much identical to constructor-based dependency @@ -184,8 +206,11 @@ The `@Bean` annotation supports specifying arbitrary initialization and destruct callback methods, much like Spring XML's `init-method` and `destroy-method` attributes on the `bean` element, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class BeanOne { @@ -215,8 +240,10 @@ on the `bean` element, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class BeanOne { @@ -242,6 +269,7 @@ class AppConfig { fun beanTwo() = BeanTwo() } ---- +====== [NOTE] ===== @@ -258,22 +286,28 @@ for a `DataSource`, as it is known to be problematic on Jakarta EE application s The following example shows how to prevent an automatic destruction callback for a `DataSource`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Bean(destroyMethod = "") public DataSource dataSource() throws NamingException { return (DataSource) jndiTemplate.lookup("MyDS"); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Bean(destroyMethod = "") fun dataSource(): DataSource { return jndiTemplate.lookup("MyDS") as DataSource } ---- +====== Also, with `@Bean` methods, you typically use programmatic JNDI lookups, either by using Spring's `JndiTemplate` or `JndiLocatorDelegate` helpers or straight JNDI @@ -286,8 +320,11 @@ intend to refer to the provided resource here). In the case of `BeanOne` from the example above the preceding note, it would be equally valid to call the `init()` method directly during construction, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -302,8 +339,10 @@ method directly during construction, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -316,6 +355,7 @@ method directly during construction, as the following example shows: // ... } ---- +====== TIP: When you work directly in Java, you can do anything you like with your objects and do not always need to rely on the container lifecycle. @@ -336,8 +376,11 @@ xref:core/beans/factory-scopes.adoc[Bean Scopes] section. The default scope is `singleton`, but you can override this with the `@Scope` annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class MyConfiguration { @@ -349,8 +392,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class MyConfiguration { @@ -362,6 +407,7 @@ as the following example shows: } } ---- +====== [[beans-java-scoped-proxy]] === `@Scope` and `scoped-proxy` @@ -379,8 +425,11 @@ If you port the scoped proxy example from the XML reference documentation (see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[scoped proxies]) to our `@Bean` using Java, it resembles the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // an HTTP Session-scoped bean exposed as a proxy @Bean @@ -397,8 +446,10 @@ it resembles the following: return service; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // an HTTP Session-scoped bean exposed as a proxy @Bean @@ -413,6 +464,7 @@ it resembles the following: } } ---- +====== [[beans-java-customizing-bean-naming]] == Customizing Bean Naming @@ -421,8 +473,11 @@ By default, configuration classes use a `@Bean` method's name as the name of the resulting bean. This functionality can be overridden, however, with the `name` attribute, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -433,8 +488,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -443,6 +500,7 @@ as the following example shows: fun thing() = Thing() } ---- +====== [[beans-java-bean-aliasing]] @@ -453,8 +511,11 @@ multiple names, otherwise known as bean aliasing. The `name` attribute of the `@ annotation accepts a String array for this purpose. The following example shows how to set a number of aliases for a bean: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -465,8 +526,10 @@ a number of aliases for a bean: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -477,6 +540,7 @@ a number of aliases for a bean: } } ---- +====== [[beans-java-bean-description]] @@ -489,8 +553,11 @@ To add a description to a `@Bean`, you can use the {api-spring-framework}/context/annotation/Description.html[`@Description`] annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -502,8 +569,10 @@ annotation, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -513,6 +582,7 @@ annotation, as the following example shows: fun thing() = Thing() } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc index 13b342fcab3..bb919b2af7e 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc @@ -12,8 +12,11 @@ Much as the `` element is used within Spring XML files to aid in modula configurations, the `@Import` annotation allows for loading `@Bean` definitions from another configuration class, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class ConfigA { @@ -34,8 +37,10 @@ another configuration class, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class ConfigA { @@ -52,13 +57,17 @@ another configuration class, as the following example shows: fun b() = B() } ---- +====== Now, rather than needing to specify both `ConfigA.class` and `ConfigB.class` when instantiating the context, only `ConfigB` needs to be supplied explicitly, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); @@ -68,8 +77,10 @@ following example shows: B b = ctx.getBean(B.class); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -81,6 +92,7 @@ following example shows: val b = ctx.getBean() } ---- +====== This approach simplifies container instantiation, as only one class needs to be dealt with, rather than requiring you to remember a potentially large number of @@ -106,8 +118,11 @@ a `@Bean` method can have an arbitrary number of parameters that describe the be dependencies. Consider the following more real-world scenario with several `@Configuration` classes, each depending on beans declared in the others: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class ServiceConfig { @@ -144,8 +159,10 @@ classes, each depending on beans declared in the others: transferService.transfer(100.00, "A123", "C456"); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -185,6 +202,7 @@ classes, each depending on beans declared in the others: transferService.transfer(100.00, "A123", "C456") } ---- +====== There is another way to achieve the same result. Remember that `@Configuration` classes are @@ -207,8 +225,11 @@ work on the configuration class itself, since it is possible to create it as a b The following example shows how one bean can be autowired to another bean: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class ServiceConfig { @@ -254,8 +275,10 @@ The following example shows how one bean can be autowired to another bean: transferService.transfer(100.00, "A123", "C456"); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -297,6 +320,7 @@ The following example shows how one bean can be autowired to another bean: transferService.transfer(100.00, "A123", "C456") } ---- +====== TIP: Constructor injection in `@Configuration` classes is only supported as of Spring Framework 4.3. Note also that there is no need to specify `@Autowired` if the target @@ -318,8 +342,11 @@ In cases where this ambiguity is not acceptable and you wish to have direct navi from within your IDE from one `@Configuration` class to another, consider autowiring the configuration classes themselves. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class ServiceConfig { @@ -334,8 +361,10 @@ configuration classes themselves. The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class ServiceConfig { @@ -350,14 +379,18 @@ class ServiceConfig { } } ---- +====== In the preceding situation, where `AccountRepository` is defined is completely explicit. However, `ServiceConfig` is now tightly coupled to `RepositoryConfig`. That is the tradeoff. This tight coupling can be somewhat mitigated by using interface-based or abstract class-based `@Configuration` classes. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class ServiceConfig { @@ -404,8 +437,10 @@ abstract class-based `@Configuration` classes. Consider the following example: transferService.transfer(100.00, "A123", "C456"); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -454,6 +489,7 @@ abstract class-based `@Configuration` classes. Consider the following example: transferService.transfer(100.00, "A123", "C456") } ---- +====== Now `ServiceConfig` is loosely coupled with respect to the concrete `DefaultRepositoryConfig`, and built-in IDE tooling is still useful: You can easily @@ -487,8 +523,11 @@ Implementations of the `Condition` interface provide a `matches(...)` method that returns `true` or `false`. For example, the following listing shows the actual `Condition` implementation used for `@Profile`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { @@ -505,8 +544,10 @@ method that returns `true` or `false`. For example, the following listing shows return true; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean { // Read the @Profile annotation attributes @@ -522,6 +563,7 @@ method that returns `true` or `false`. For example, the following listing shows return true } ---- +====== See the {api-spring-framework}/context/annotation/Conditional.html[`@Conditional`] javadoc for more detail. @@ -558,8 +600,11 @@ properly. The following example shows an ordinary configuration class in Java: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -578,8 +623,10 @@ The following example shows an ordinary configuration class in Java: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -596,6 +643,7 @@ The following example shows an ordinary configuration class in Java: fun transferService() = TransferService(accountRepository()) } ---- +====== The following example shows part of a sample `system-test-config.xml` file: @@ -625,8 +673,11 @@ jdbc.username=sa jdbc.password= ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); @@ -634,8 +685,10 @@ jdbc.password= // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun main() { val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml") @@ -643,6 +696,7 @@ jdbc.password= // ... } ---- +====== NOTE: In `system-test-config.xml` file, the `AppConfig` `` does not declare an `id` @@ -691,8 +745,11 @@ that defines a bean, a properties file, and the `main` class) shows how to use the `@ImportResource` annotation to achieve "`Java-centric`" configuration that uses XML as needed: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ImportResource("classpath:/com/acme/properties-config.xml") @@ -713,8 +770,10 @@ as needed: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ImportResource("classpath:/com/acme/properties-config.xml") @@ -735,6 +794,7 @@ as needed: } } ---- +====== [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -752,8 +812,11 @@ jdbc.username=sa jdbc.password= ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); @@ -761,8 +824,10 @@ jdbc.password= // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -772,6 +837,7 @@ jdbc.password= // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/configuration-annotation.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/configuration-annotation.adoc index ccf861e9215..d265db7e758 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/java/configuration-annotation.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/java/configuration-annotation.adoc @@ -13,8 +13,11 @@ inter-bean dependencies. See xref:core/beans/java/basic-concepts.adoc[Basic Conc When beans have dependencies on one another, expressing that dependency is as simple as having one bean method call another, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -30,8 +33,10 @@ as having one bean method call another, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -43,6 +48,7 @@ as having one bean method call another, as the following example shows: fun beanTwo() = BeanTwo() } ---- +====== In the preceding example, `beanOne` receives a reference to `beanTwo` through constructor injection. @@ -62,8 +68,11 @@ singleton-scoped bean has a dependency on a prototype-scoped bean. Using Java fo type of configuration provides a natural means for implementing this pattern. The following example shows how to use lookup method injection: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public abstract class CommandManager { public Object process(Object commandState) { @@ -78,8 +87,10 @@ following example shows how to use lookup method injection: protected abstract Command createCommand(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- abstract class CommandManager { fun process(commandState: Any): Any { @@ -94,13 +105,17 @@ following example shows how to use lookup method injection: protected abstract fun createCommand(): Command } ---- +====== By using Java configuration, you can create a subclass of `CommandManager` where the abstract `createCommand()` method is overridden in such a way that it looks up a new (prototype) command object. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Bean @Scope("prototype") @@ -121,8 +136,10 @@ the abstract `createCommand()` method is overridden in such a way that it looks } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Bean @Scope("prototype") @@ -143,6 +160,7 @@ the abstract `createCommand()` method is overridden in such a way that it looks } } ---- +====== [[beans-java-further-information-java-config]] @@ -150,8 +168,11 @@ the abstract `createCommand()` method is overridden in such a way that it looks Consider the following example, which shows a `@Bean` annotated method being called twice: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -176,8 +197,10 @@ Consider the following example, which shows a `@Bean` annotated method being cal } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -202,6 +225,7 @@ Consider the following example, which shows a `@Bean` annotated method being cal } } ---- +====== `clientDao()` has been called once in `clientService1()` and once in `clientService2()`. Since this method creates a new instance of `ClientDaoImpl` and returns it, you would diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/instantiating-container.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/instantiating-container.adoc index 1a082498c4c..e9c98ea126a 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/java/instantiating-container.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/java/instantiating-container.adoc @@ -23,8 +23,11 @@ In much the same way that Spring XML files are used as input when instantiating instantiating an `AnnotationConfigApplicationContext`. This allows for completely XML-free usage of the Spring container, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); @@ -32,8 +35,10 @@ XML-free usage of the Spring container, as the following example shows: myService.doStuff(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -43,13 +48,17 @@ XML-free usage of the Spring container, as the following example shows: myService.doStuff() } ---- +====== As mentioned earlier, `AnnotationConfigApplicationContext` is not limited to working only with `@Configuration` classes. Any `@Component` or JSR-330 annotated class may be supplied as input to the constructor, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class); @@ -57,8 +66,10 @@ as input to the constructor, as the following example shows: myService.doStuff(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -68,6 +79,7 @@ as input to the constructor, as the following example shows: myService.doStuff() } ---- +====== The preceding example assumes that `MyServiceImpl`, `Dependency1`, and `Dependency2` use Spring dependency injection annotations such as `@Autowired`. @@ -81,8 +93,11 @@ and then configure it by using the `register()` method. This approach is particu when programmatically building an `AnnotationConfigApplicationContext`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -93,8 +108,10 @@ example shows how to do so: myService.doStuff(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -107,6 +124,7 @@ example shows how to do so: myService.doStuff() } ---- +====== [[beans-java-instantiating-container-scan]] @@ -114,8 +132,11 @@ example shows how to do so: To enable component scanning, you can annotate your `@Configuration` class as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan(basePackages = "com.acme") // <1> @@ -123,6 +144,7 @@ To enable component scanning, you can annotate your `@Configuration` class as fo // ... } ---- +====== <1> This annotation enables component scanning. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -156,8 +178,11 @@ definitions within the container. `AnnotationConfigApplicationContext` exposes t `scan(String...)` method to allow for the same component-scanning functionality, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -166,8 +191,10 @@ following example shows: MyService myService = ctx.getBean(MyService.class); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun main() { val ctx = AnnotationConfigApplicationContext() @@ -176,6 +203,7 @@ following example shows: val myService = ctx.getBean() } ---- +====== NOTE: Remember that `@Configuration` classes are xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotated] with `@Component`, so they are candidates for component-scanning. In the preceding example, diff --git a/framework-docs/modules/ROOT/pages/core/beans/standard-annotations.adoc b/framework-docs/modules/ROOT/pages/core/beans/standard-annotations.adoc index 77f58b4a694..d9929bea3d0 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/standard-annotations.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/standard-annotations.adoc @@ -29,8 +29,11 @@ You can add the following dependency to your file pom.xml: Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import jakarta.inject.Inject; @@ -49,8 +52,10 @@ Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import jakarta.inject.Inject @@ -66,6 +71,7 @@ Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows: } } ---- +====== As with `@Autowired`, you can use `@Inject` at the field level, method level and constructor-argument level. Furthermore, you may declare your injection point as a @@ -73,8 +79,11 @@ and constructor-argument level. Furthermore, you may declare your injection poin other beans through a `Provider.get()` call. The following example offers a variant of the preceding example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import jakarta.inject.Inject; import jakarta.inject.Provider; @@ -94,8 +103,10 @@ preceding example: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import jakarta.inject.Inject @@ -111,12 +122,16 @@ preceding example: } } ---- +====== If you would like to use a qualified name for the dependency that should be injected, you should use the `@Named` annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import jakarta.inject.Inject; import jakarta.inject.Named; @@ -133,8 +148,10 @@ you should use the `@Named` annotation, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import jakarta.inject.Inject import jakarta.inject.Named @@ -151,6 +168,7 @@ you should use the `@Named` annotation, as the following example shows: // ... } ---- +====== As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or `@Nullable`. This is even more applicable here, since `@Inject` does not have @@ -168,8 +186,11 @@ a `required` attribute. The following pair of examples show how to use `@Inject` } ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -179,8 +200,10 @@ a `required` attribute. The following pair of examples show how to use `@Inject` } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleMovieLister { @@ -188,6 +211,7 @@ a `required` attribute. The following pair of examples show how to use `@Inject` var movieFinder: MovieFinder? = null } ---- +====== @@ -197,8 +221,11 @@ a `required` attribute. The following pair of examples show how to use `@Inject` Instead of `@Component`, you can use `@jakarta.inject.Named` or `jakarta.annotation.ManagedBean`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import jakarta.inject.Inject; import jakarta.inject.Named; @@ -216,8 +243,10 @@ as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import jakarta.inject.Inject import jakarta.inject.Named @@ -231,12 +260,16 @@ as the following example shows: // ... } ---- +====== It is very common to use `@Component` without specifying a name for the component. `@Named` can be used in a similar fashion, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import jakarta.inject.Inject; import jakarta.inject.Named; @@ -254,8 +287,10 @@ It is very common to use `@Component` without specifying a name for the componen // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import jakarta.inject.Inject import jakarta.inject.Named @@ -269,12 +304,16 @@ It is very common to use `@Component` without specifying a name for the componen // ... } ---- +====== When you use `@Named` or `@ManagedBean`, you can use component scanning in the exact same way as when you use Spring annotations, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan(basePackages = "org.example") @@ -282,8 +321,10 @@ exact same way as when you use Spring annotations, as the following example show // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ComponentScan(basePackages = ["org.example"]) @@ -291,6 +332,7 @@ exact same way as when you use Spring annotations, as the following example show // ... } ---- +====== NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `@ManagedBean` annotations are not composable. You should use Spring's stereotype model for building diff --git a/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc b/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc index 6991a5f30f6..a66ade956c2 100644 --- a/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc +++ b/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc @@ -140,8 +140,11 @@ An `Encoder` allocates data buffers that others must read (and release). So an ` doesn't have much to do. However an `Encoder` must take care to release a data buffer if a serialization error occurs while populating the buffer with data. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DataBuffer buffer = factory.allocateBuffer(); boolean release = true; @@ -156,8 +159,10 @@ a serialization error occurs while populating the buffer with data. For example: } return buffer; ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val buffer = factory.allocateBuffer() var release = true @@ -171,6 +176,7 @@ a serialization error occurs while populating the buffer with data. For example: } return buffer ---- +====== The consumer of an `Encoder` is responsible for releasing the data buffers it receives. In a WebFlux application, the output of the `Encoder` is used to write to the HTTP server diff --git a/framework-docs/modules/ROOT/pages/core/expressions/beandef.adoc b/framework-docs/modules/ROOT/pages/core/expressions/beandef.adoc index be689fb1576..7617ab15ef7 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/beandef.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/beandef.adoc @@ -67,8 +67,11 @@ and method or constructor parameters. The following example sets the default value of a field: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class FieldValueTestBean { @@ -84,8 +87,10 @@ The following example sets the default value of a field: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class FieldValueTestBean { @@ -93,11 +98,15 @@ The following example sets the default value of a field: var defaultLocale: String? = null } ---- +====== The following example shows the equivalent but on a property setter method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class PropertyValueTestBean { @@ -113,8 +122,10 @@ The following example shows the equivalent but on a property setter method: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class PropertyValueTestBean { @@ -122,12 +133,16 @@ The following example shows the equivalent but on a property setter method: var defaultLocale: String? = null } ---- +====== Autowired methods and constructors can also use the `@Value` annotation, as the following examples show: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleMovieLister { @@ -144,8 +159,10 @@ examples show: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleMovieLister { @@ -162,9 +179,13 @@ examples show: // ... } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MovieRecommender { @@ -181,14 +202,17 @@ examples show: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao, @Value("#{systemProperties['user.country']}") private val defaultLocale: String) { // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc index 096714936b9..79b3f260b91 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc @@ -8,13 +8,17 @@ xref:core/expressions/language-ref.adoc[Language Reference]. The following code introduces the SpEL API to evaluate the literal string expression, `Hello World`. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'"); // <1> String message = (String) exp.getValue(); ---- +====== <1> The value of the message variable is `'Hello World'`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -42,13 +46,17 @@ and calling constructors. In the following example of method invocation, we call the `concat` method on the string literal: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('!')"); // <1> String message = (String) exp.getValue(); ---- +====== <1> The value of `message` is now 'Hello World!'. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -62,8 +70,11 @@ In the following example of method invocation, we call the `concat` method on th The following example of calling a JavaBean property calls the `String` property `Bytes`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); @@ -71,6 +82,7 @@ The following example of calling a JavaBean property calls the `String` property Expression exp = parser.parseExpression("'Hello World'.bytes"); // <1> byte[] bytes = (byte[]) exp.getValue(); ---- +====== <1> This line converts the literal to a byte array. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -90,8 +102,11 @@ Public fields may also be accessed. The following example shows how to use dot notation to get the length of a literal: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); @@ -99,6 +114,7 @@ The following example shows how to use dot notation to get the length of a liter 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. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -115,13 +131,17 @@ The following example shows how to use dot notation to get the length of a liter The String's constructor can be called instead of using a string literal, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); 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. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -144,8 +164,11 @@ against a specific object instance (called the root object). The following examp how to retrieve the `name` property from an instance of the `Inventor` class or create a boolean condition: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Create and set a calendar GregorianCalendar c = new GregorianCalendar(); @@ -164,8 +187,10 @@ create a boolean condition: boolean result = exp.getValue(tesla, Boolean.class); // result == true ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Create and set a calendar val c = GregorianCalendar() @@ -184,6 +209,7 @@ create a boolean condition: val result = exp.getValue(tesla, Boolean::class.java) // result == true ---- +====== @@ -232,8 +258,11 @@ to set a `List` property. The type of the property is actually `List`. recognizes that the elements of the list need to be converted to `Boolean` before being placed in it. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class Simple { public List booleanList = new ArrayList<>(); @@ -251,8 +280,10 @@ being placed in it. The following example shows how to do so: // b is false Boolean b = simple.booleanList.get(0); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Simple { var booleanList: MutableList = ArrayList() @@ -270,6 +301,7 @@ being placed in it. The following example shows how to do so: // b is false val b = simple.booleanList[0] ---- +====== [[expressions-parser-configuration]] @@ -290,8 +322,11 @@ or custom converter that knows how to set the value, `null` will remain in the a list at the specified index. The following example demonstrates how to automatically grow the list: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class Demo { public List list; @@ -313,8 +348,10 @@ the list: // demo.list will now be a real collection of 4 entries // Each entry is a new empty String ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Demo { var list: List? = null @@ -336,6 +373,7 @@ the list: // demo.list will now be a real collection of 4 entries // Each entry is a new empty String ---- +====== @@ -404,8 +442,11 @@ since part of the expression may be running twice. After selecting a mode, use the `SpelParserConfiguration` to configure the parser. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, this.getClass().getClassLoader()); @@ -418,8 +459,10 @@ following example shows how to do so: Object payload = expr.getValue(message); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, this.javaClass.classLoader) @@ -432,6 +475,7 @@ following example shows how to do so: val payload = expr.getValue(message) ---- +====== When you specify the compiler mode, you can also specify a classloader (passing null is allowed). Compiled expressions are defined in a child classloader created under any that is supplied. diff --git a/framework-docs/modules/ROOT/pages/core/expressions/example-classes.adoc b/framework-docs/modules/ROOT/pages/core/expressions/example-classes.adoc index 475029a08fc..e94e3b8c091 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/example-classes.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/example-classes.adoc @@ -3,8 +3,11 @@ This section lists the classes used in the examples throughout this chapter. +[tabs] +====== +Inventor.Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Inventor.Java ---- package org.spring.samples.spel.inventor; @@ -76,8 +79,10 @@ This section lists the classes used in the examples throughout this chapter. } } ---- + +Inventor.kt:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Inventor.kt ---- package org.spring.samples.spel.inventor @@ -88,9 +93,13 @@ This section lists the classes used in the examples throughout this chapter. var birthdate: Date = GregorianCalendar().time, var placeOfBirth: PlaceOfBirth? = null) ---- +====== +[tabs] +====== +PlaceOfBirth.java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.PlaceOfBirth.java ---- package org.spring.samples.spel.inventor; @@ -125,16 +134,22 @@ This section lists the classes used in the examples throughout this chapter. } } ---- + +PlaceOfBirth.kt:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.PlaceOfBirth.kt ---- package org.spring.samples.spel.inventor class PlaceOfBirth(var city: String, var country: String? = null) { ---- +====== +[tabs] +====== +Society.java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Society.java ---- package org.spring.samples.spel.inventor; @@ -176,8 +191,10 @@ This section lists the classes used in the examples throughout this chapter. } } ---- + +Society.kt:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Society.kt ---- package org.spring.samples.spel.inventor @@ -203,3 +220,4 @@ This section lists the classes used in the examples throughout this chapter. } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/array-construction.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/array-construction.adoc index aa1e52bfe17..01e181ae301 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/array-construction.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/array-construction.adoc @@ -4,8 +4,11 @@ You can build arrays by using the familiar Java syntax, optionally supplying an initializer to have the array populated at construction time. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); @@ -15,8 +18,10 @@ to have the array populated at construction time. The following example shows ho // Multi dimensional array int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray @@ -26,6 +31,7 @@ to have the array populated at construction time. The following example shows ho // Multi dimensional array val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array ---- +====== You cannot currently supply an initializer when you construct a multi-dimensional array. diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/bean-references.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/bean-references.adoc index 4ea79cd6319..82e68876b1f 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/bean-references.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/bean-references.adoc @@ -5,8 +5,11 @@ If the evaluation context has been configured with a bean resolver, you can look up beans from an expression by using the `@` symbol. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -15,8 +18,10 @@ to do so: // This will end up calling resolve(context,"something") on MyBeanResolver during evaluation Object bean = parser.parseExpression("@something").getValue(context); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() val context = StandardEvaluationContext() @@ -25,12 +30,16 @@ to do so: // This will end up calling resolve(context,"something") on MyBeanResolver during evaluation val bean = parser.parseExpression("@something").getValue(context) ---- +====== To access a factory bean itself, you should instead prefix the bean name with an `&` symbol. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -39,8 +48,10 @@ The following example shows how to do so: // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation Object bean = parser.parseExpression("&foo").getValue(context); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() val context = StandardEvaluationContext() @@ -49,5 +60,6 @@ The following example shows how to do so: // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation val bean = parser.parseExpression("&foo").getValue(context) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc index bf2f229d772..83f492766dd 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc @@ -7,18 +7,24 @@ suppose we have a list of inventors but want the list of cities where they were Effectively, we want to evaluate 'placeOfBirth.city' for every entry in the inventor list. The following example uses projection to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // returns ['Smiljan', 'Idvor' ] List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // returns ['Smiljan', 'Idvor' ] val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*> ---- +====== Projection is supported for arrays and anything that implements `java.lang.Iterable` or `java.util.Map`. When using a map to drive projection, the projection expression is diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-selection.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-selection.adoc index 90ade2b7c28..3f87541a81c 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-selection.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-selection.adoc @@ -8,18 +8,24 @@ Selection uses a syntax of `.?[selectionExpression]`. It filters the collection returns a new collection that contains a subset of the original elements. For example, selection lets us easily get a list of Serbian inventors, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- List list = (List) parser.parseExpression( "members.?[nationality == 'Serbian']").getValue(societyContext); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val list = parser.parseExpression( "members.?[nationality == 'Serbian']").getValue(societyContext) as List ---- +====== Selection is supported for arrays and anything that implements `java.lang.Iterable` or `java.util.Map`. For a list or array, the selection criteria is evaluated against each @@ -30,16 +36,22 @@ accessible as properties for use in the selection. The following expression returns a new map that consists of those elements of the original map where the entry's value is less than 27: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Map newMap = parser.parseExpression("map.?[value<27]").getValue(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val newMap = parser.parseExpression("map.?[value<27]").getValue() ---- +====== In addition to returning all the selected elements, you can retrieve only the first or the last element. To obtain the first element matching the selection, the syntax is diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/constructors.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/constructors.adoc index c99e0aada50..a35513c9c1e 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/constructors.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/constructors.adoc @@ -6,8 +6,11 @@ qualified class name for all types except those located in the `java.lang` packa (`Integer`, `Float`, `String`, and so on). The following example shows how to use the `new` operator to invoke constructors: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Inventor einstein = p.parseExpression( "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')") @@ -18,8 +21,10 @@ qualified class name for all types except those located in the `java.lang` packa "Members.add(new org.spring.samples.spel.inventor.Inventor( 'Albert Einstein', 'German'))").getValue(societyContext); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val einstein = p.parseExpression( "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')") @@ -30,6 +35,7 @@ qualified class name for all types except those located in the `java.lang` packa "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))") .getValue(societyContext) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/functions.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/functions.adoc index db24cf801fa..014d6e8b8c4 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/functions.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/functions.adoc @@ -5,27 +5,36 @@ You can extend SpEL by registering user-defined functions that can be called wit expression string. The function is registered through the `EvaluationContext`. The following example shows how to register a user-defined function: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Method method = ...; EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("myFunction", method); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val method: Method = ... val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() context.setVariable("myFunction", method) ---- +====== For example, consider the following utility method that reverses a string: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public abstract class StringUtils { @@ -38,8 +47,10 @@ For example, consider the following utility method that reverses a string: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun reverseString(input: String): String { val backwards = StringBuilder(input.length) @@ -49,11 +60,15 @@ For example, consider the following utility method that reverses a string: return backwards.toString() } ---- +====== You can then register and use the preceding method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); @@ -64,8 +79,10 @@ You can then register and use the preceding method, as the following example sho String helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() @@ -75,6 +92,7 @@ You can then register and use the preceding method, as the following example sho val helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String::class.java) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-lists.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-lists.adoc index 0d23b7695c1..463d54d8095 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-lists.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-lists.adoc @@ -3,22 +3,28 @@ You can directly express lists in an expression by using `{}` notation. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // evaluates to a Java list containing the four numbers List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // evaluates to a Java list containing the four numbers val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*> val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*> ---- +====== `{}` by itself means an empty list. For performance reasons, if the list is itself entirely composed of fixed literals, a constant list is created to represent the diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-maps.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-maps.adoc index f20b2698193..2b972329cd8 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-maps.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/inline-maps.adoc @@ -4,22 +4,28 @@ You can also directly express maps in an expression by using `{key:value}` notation. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // evaluates to a Java map containing the two entries Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context); Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- // evaluates to a Java map containing the two entries val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, *> val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<*, *> ---- +====== `{:}` by itself means an empty map. For performance reasons, if the map is itself composed of fixed literals or other nested constant structures (lists or maps), a diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc index 077f53aa073..c133af0f367 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc @@ -23,8 +23,11 @@ isolation like this but, rather, as part of a more complex expression -- for exa using a literal on one side of a logical comparison operator or as an argument to a method. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); @@ -43,8 +46,10 @@ method. Object nullValue = parser.parseExpression("null").getValue(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() @@ -63,6 +68,7 @@ method. val nullValue = parser.parseExpression("null").value ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/methods.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/methods.adoc index 149d6a12577..46c91b83625 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/methods.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/methods.adoc @@ -5,8 +5,11 @@ You can invoke methods by using typical Java programming syntax. You can also in on literals. Variable arguments are also supported. The following examples show how to invoke methods: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // string literal, evaluates to "bc" String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class); @@ -15,8 +18,10 @@ invoke methods: boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue( societyContext, Boolean.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // string literal, evaluates to "bc" val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java) @@ -25,5 +30,6 @@ invoke methods: val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue( societyContext, Boolean::class.java) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-elvis.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-elvis.adoc index 199f131612e..8f7e69a0b6c 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-elvis.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-elvis.adoc @@ -15,27 +15,36 @@ following example shows: Instead, you can use the Elvis operator (named for the resemblance to Elvis' hair style). The following example shows how to use the Elvis operator: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class); System.out.println(name); // 'Unknown' ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java) println(name) // 'Unknown' ---- +====== The following listing shows a more complex example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); @@ -48,8 +57,10 @@ The following listing shows a more complex example: name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Elvis Presley ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() @@ -62,6 +73,7 @@ The following listing shows a more complex example: name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java) println(name) // Elvis Presley ---- +====== [NOTE] ===== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc index 1398af00d0a..0691cf2de87 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc @@ -8,8 +8,11 @@ it is not null before accessing methods or properties of the object. To avoid th safe navigation operator returns null instead of throwing an exception. The following example shows how to use the safe navigation operator: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); @@ -24,8 +27,10 @@ example shows how to use the safe navigation operator: city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class); System.out.println(city); // null - does not throw NullPointerException!!! ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() @@ -40,6 +45,7 @@ example shows how to use the safe navigation operator: city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java) println(city) // null - does not throw NullPointerException!!! ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-ternary.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-ternary.adoc index e843a1f582f..0a834d195fd 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-ternary.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-ternary.adoc @@ -4,24 +4,33 @@ You can use the ternary operator for performing if-then-else conditional logic inside the expression. The following listing shows a minimal example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String falseString = parser.parseExpression( "false ? 'trueExp' : 'falseExp'").getValue(String.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val falseString = parser.parseExpression( "false ? 'trueExp' : 'falseExp'").getValue(String::class.java) ---- +====== In this case, the boolean `false` results in returning the string value `'falseExp'`. A more realistic example follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- parser.parseExpression("name").setValue(societyContext, "IEEE"); societyContext.setVariable("queryName", "Nikola Tesla"); @@ -33,8 +42,10 @@ realistic example follows: .getValue(societyContext, String.class); // queryResultString = "Nikola Tesla is a member of the IEEE Society" ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- parser.parseExpression("name").setValue(societyContext, "IEEE") societyContext.setVariable("queryName", "Nikola Tesla") @@ -45,6 +56,7 @@ realistic example follows: .getValue(societyContext, String::class.java) // queryResultString = "Nikola Tesla is a member of the IEEE Society" ---- +====== See the next section on the Elvis operator for an even shorter syntax for the ternary operator. diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc index 240073f7daa..7d9d6ece8e4 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc @@ -17,8 +17,11 @@ and greater than or equal) are supported by using standard operator notation. These operators work on `Number` types as well as types implementing `Comparable`. The following listing shows a few examples of operators: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // evaluates to true boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); @@ -32,8 +35,10 @@ The following listing shows a few examples of operators: // uses CustomValue:::compareTo boolean trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // evaluates to true val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java) @@ -47,6 +52,7 @@ The following listing shows a few examples of operators: // uses CustomValue:::compareTo val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java); ---- +====== [NOTE] ==== @@ -62,8 +68,11 @@ in favor of comparisons against zero (for example, `X > 0` or `X < 0`). In addition to the standard relational operators, SpEL supports the `instanceof` and regular expression-based `matches` operator. The following listing shows examples of both: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // evaluates to false boolean falseValue = parser.parseExpression( @@ -77,8 +86,10 @@ expression-based `matches` operator. The following listing shows examples of bot boolean falseValue = parser.parseExpression( "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // evaluates to false val falseValue = parser.parseExpression( @@ -92,6 +103,7 @@ expression-based `matches` operator. The following listing shows examples of bot val falseValue = parser.parseExpression( "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java) ---- +====== CAUTION: Be careful with primitive types, as they are immediately boxed up to their wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while @@ -125,8 +137,11 @@ SpEL supports the following logical operators: The following example shows how to use the logical operators: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // -- AND -- @@ -155,8 +170,10 @@ The following example shows how to use the logical operators: String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // -- AND -- @@ -185,6 +202,7 @@ The following example shows how to use the logical operators: val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')" val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java) ---- +====== [[expressions-operators-mathematical]] @@ -196,8 +214,11 @@ You can also use the modulus (`%`) and exponential power (`^`) operators on numb Standard operator precedence is enforced. The following example shows the mathematical operators in use: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Addition int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 @@ -228,8 +249,10 @@ operators in use: // Operator precedence int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Addition val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2 @@ -260,6 +283,7 @@ operators in use: // Operator precedence val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21 ---- +====== [[expressions-assignment]] @@ -269,8 +293,11 @@ To set a property, use the assignment operator (`=`). This is typically done wit call to `setValue` but can also be done inside a call to `getValue`. The following listing shows both ways to use the assignment operator: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Inventor inventor = new Inventor(); EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); @@ -281,8 +308,10 @@ listing shows both ways to use the assignment operator: String aleks = parser.parseExpression( "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val inventor = Inventor() val context = SimpleEvaluationContext.forReadWriteDataBinding().build() @@ -293,5 +322,6 @@ listing shows both ways to use the assignment operator: val aleks = parser.parseExpression( "name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc index bc717138aef..3066b54dec7 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc @@ -7,22 +7,28 @@ populated with data listed in the xref:core/expressions/example-classes.adoc[Cla section. To navigate "down" the object graph and get Tesla's year of birth and Pupin's city of birth, we use the following expressions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // evaluates to 1856 int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context); String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // evaluates to 1856 val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String ---- +====== [NOTE] ==== @@ -36,8 +42,11 @@ method invocations -- for example, `getPlaceOfBirth().getCity()` instead of The contents of arrays and lists are obtained by using square bracket notation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); @@ -59,8 +68,10 @@ following example shows: String invention = parser.parseExpression("members[0].inventions[6]").getValue( context, ieee, String.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parser = SpelExpressionParser() val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() @@ -82,13 +93,17 @@ following example shows: val invention = parser.parseExpression("members[0].inventions[6]").getValue( context, ieee, String::class.java) ---- +====== The contents of maps are obtained by specifying the literal key value within the brackets. In the following example, because keys for the `officers` map are strings, we can specify string literals: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Officer's Dictionary @@ -103,8 +118,10 @@ string literals: parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue( societyContext, "Croatia"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Officer's Dictionary @@ -119,6 +136,7 @@ string literals: parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue( societyContext, "Croatia") ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/templating.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/templating.adoc index 6c521a8f0dc..2b4a02d24f4 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/templating.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/templating.adoc @@ -6,8 +6,11 @@ Each evaluation block is delimited with prefix and suffix characters that you ca define. A common choice is to use `#{ }` as the delimiters, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String randomPhrase = parser.parseExpression( "random number is #{T(java.lang.Math).random()}", @@ -15,8 +18,10 @@ shows: // evaluates to "random number is 0.7038186818312008" ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val randomPhrase = parser.parseExpression( "random number is #{T(java.lang.Math).random()}", @@ -24,6 +29,7 @@ shows: // evaluates to "random number is 0.7038186818312008" ---- +====== The string is evaluated by concatenating the literal text `'random number is '` with the result of evaluating the expression inside the `#{ }` delimiter (in this case, the result @@ -32,8 +38,11 @@ is of the type `ParserContext`. The `ParserContext` interface is used to influen the expression is parsed in order to support the expression templating functionality. The definition of `TemplateParserContext` follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class TemplateParserContext implements ParserContext { @@ -50,8 +59,10 @@ The definition of `TemplateParserContext` follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class TemplateParserContext : ParserContext { @@ -68,5 +79,6 @@ The definition of `TemplateParserContext` follows: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/types.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/types.adoc index b74d96c606e..df4d9cc3e0c 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/types.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/types.adoc @@ -9,8 +9,11 @@ type). Static methods are invoked by using this operator as well. The package do not need to be fully qualified, but all other type references must be. The following example shows how to use the `T` operator: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); @@ -20,8 +23,10 @@ following example shows how to use the `T` operator: "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") .getValue(Boolean.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java) @@ -31,6 +36,7 @@ following example shows how to use the `T` operator: "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") .getValue(Boolean::class.java) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/variables.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/variables.adoc index c3e88e62408..6f7ac6e971e 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/variables.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/variables.adoc @@ -17,8 +17,11 @@ characters. The following example shows how to use variables. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); @@ -28,8 +31,10 @@ The following example shows how to use variables. parser.parseExpression("name = #newName").getValue(context, tesla); System.out.println(tesla.getName()) // "Mike Tesla" ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val tesla = Inventor("Nikola Tesla", "Serbian") @@ -39,6 +44,7 @@ The following example shows how to use variables. parser.parseExpression("name = #newName").getValue(context, tesla) println(tesla.name) // "Mike Tesla" ---- +====== [[expressions-this-root]] @@ -50,8 +56,11 @@ defined and refers to the root context object. Although `#this` may vary as comp an expression are evaluated, `#root` always refers to the root. The following examples show how to use the `#this` and `#root` variables: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // create an array of integers List primes = new ArrayList<>(); @@ -67,8 +76,10 @@ show how to use the `#this` and `#root` variables: List primesGreaterThanTen = (List) parser.parseExpression( "#primes.?[#this>10]").getValue(context); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // create an array of integers val primes = ArrayList() @@ -84,6 +95,7 @@ show how to use the `#this` and `#root` variables: val primesGreaterThanTen = parser.parseExpression( "#primes.?[#this>10]").getValue(context) as List ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/resources.adoc b/framework-docs/modules/ROOT/pages/core/resources.adoc index d645fcca280..3bfc74aeb33 100644 --- a/framework-docs/modules/ROOT/pages/core/resources.adoc +++ b/framework-docs/modules/ROOT/pages/core/resources.adoc @@ -276,16 +276,22 @@ specified doesn't have a specific prefix, you get back a `Resource` type that is appropriate to that particular application context. For example, assume the following snippet of code was run against a `ClassPathXmlApplicationContext` instance: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Resource template = ctx.getResource("some/resource/path/myTemplate.txt"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val template = ctx.getResource("some/resource/path/myTemplate.txt") ---- +====== Against a `ClassPathXmlApplicationContext`, that code returns a `ClassPathResource`. If the same method were run against a `FileSystemXmlApplicationContext` instance, it would @@ -299,41 +305,59 @@ On the other hand, you may also force `ClassPathResource` to be used, regardless application context type, by specifying the special `classpath:` prefix, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt") ---- +====== Similarly, you can force a `UrlResource` to be used by specifying any of the standard `java.net.URL` prefixes. The following examples use the `file` and `https` prefixes: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val template = ctx.getResource("file:///some/resource/path/myTemplate.txt") ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt") ---- +====== The following table summarizes the strategy for converting `String` objects to `Resource` objects: @@ -477,8 +501,11 @@ register and use a special JavaBeans `PropertyEditor`, which can convert `String to `Resource` objects. For example, the following `MyBean` class has a `template` property of type `Resource`. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- package example; @@ -493,11 +520,14 @@ property of type `Resource`. // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyBean(var template: Resource) ---- +====== In an XML configuration file, the `template` property can be configured with a simple string for that resource, as the following example shows: @@ -537,8 +567,11 @@ retrieve the value of the template path as a string, and a special `PropertyEdit convert the string to a `Resource` object to be injected into the `MyBean` constructor. The following example demonstrates how to achieve this. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MyBean { @@ -552,12 +585,15 @@ The following example demonstrates how to achieve this. // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MyBean(@Value("\${template.path}") private val template: Resource) ---- +====== If we want to support multiple templates discovered under the same path in multiple locations in the classpath -- for example, in multiple jars in the classpath -- we can @@ -566,8 +602,11 @@ use the special `classpath*:` prefix and wildcarding to define a `templates.path Spring will convert the template path pattern into an array of `Resource` objects that can be injected into the `MyBean` constructor. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MyBean { @@ -581,12 +620,15 @@ can be injected into the `MyBean` constructor. // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MyBean(@Value("\${templates.path}") private val templates: Resource[]) ---- +====== @@ -611,31 +653,43 @@ that path and used to load the bean definitions depends on and is appropriate to specific application context. For example, consider the following example, which creates a `ClassPathXmlApplicationContext`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = ClassPathXmlApplicationContext("conf/appContext.xml") ---- +====== The bean definitions are loaded from the classpath, because a `ClassPathResource` is used. However, consider the following example, which creates a `FileSystemXmlApplicationContext`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = FileSystemXmlApplicationContext("conf/appContext.xml") ---- +====== Now the bean definitions are loaded from a filesystem location (in this case, relative to the current working directory). @@ -644,17 +698,23 @@ Note that the use of the special `classpath` prefix or a standard URL prefix on location path overrides the default type of `Resource` created to load the bean definitions. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml") ---- +====== Using `FileSystemXmlApplicationContext` loads the bean definitions from the classpath. However, it is still a `FileSystemXmlApplicationContext`. If it is subsequently used as a @@ -685,17 +745,23 @@ The following example shows how a `ClassPathXmlApplicationContext` instance comp the beans defined in files named `services.xml` and `repositories.xml` (which are on the classpath) can be instantiated: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {"services.xml", "repositories.xml"}, MessengerService.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java) ---- +====== See the {api-spring-framework}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`] javadoc for details on the various constructors. @@ -773,17 +839,23 @@ coming from jars be thoroughly tested in your specific environment before you re When constructing an XML-based application context, a location string may use the special `classpath*:` prefix, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml") ---- +====== This special prefix specifies that all classpath resources that match the given name must be obtained (internally, this essentially happens through a call to @@ -879,87 +951,123 @@ For backwards compatibility (historical) reasons however, this changes when the to treat all location paths as relative, whether they start with a leading slash or not. In practice, this means the following examples are equivalent: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/context.xml"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = FileSystemXmlApplicationContext("conf/context.xml") ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("/conf/context.xml"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx = FileSystemXmlApplicationContext("/conf/context.xml") ---- +====== The following examples are also equivalent (even though it would make sense for them to be different, as one case is relative and the other absolute): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- FileSystemXmlApplicationContext ctx = ...; ctx.getResource("some/resource/path/myTemplate.txt"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx: FileSystemXmlApplicationContext = ... ctx.getResource("some/resource/path/myTemplate.txt") ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- FileSystemXmlApplicationContext ctx = ...; ctx.getResource("/some/resource/path/myTemplate.txt"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ctx: FileSystemXmlApplicationContext = ... ctx.getResource("/some/resource/path/myTemplate.txt") ---- +====== In practice, if you need true absolute filesystem paths, you should avoid using absolute paths with `FileSystemResource` or `FileSystemXmlApplicationContext` and force the use of a `UrlResource` by using the `file:` URL prefix. The following examples show how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // actual context type doesn't matter, the Resource will always be UrlResource ctx.getResource("file:///some/resource/path/myTemplate.txt"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // actual context type doesn't matter, the Resource will always be UrlResource ctx.getResource("file:///some/resource/path/myTemplate.txt") ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // force this FileSystemXmlApplicationContext to load its definition via a UrlResource ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // force this FileSystemXmlApplicationContext to load its definition via a UrlResource val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml") ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/spring-jcl.adoc b/framework-docs/modules/ROOT/pages/core/spring-jcl.adoc index fee802ed183..768af0c3434 100644 --- a/framework-docs/modules/ROOT/pages/core/spring-jcl.adoc +++ b/framework-docs/modules/ROOT/pages/core/spring-jcl.adoc @@ -23,19 +23,25 @@ For logging needs within application code, prefer direct use of Log4j 2.x, SLF4J A `Log` implementation may be retrieved via `org.apache.commons.logging.LogFactory` as in the following example. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyBean { private final Log log = LogFactory.getLog(getClass()); // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyBean { private val log = LogFactory.getLog(javaClass) // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc b/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc index c22f3c6ff63..db58aaa11e3 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc @@ -62,8 +62,11 @@ xref:core/validation/beans-beans.adoc#beans-beans-conversion[section on `Propert The following two example classes use the `BeanWrapper` to get and set properties: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class Company { @@ -87,17 +90,23 @@ properties: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Company { var name: String? = null var managingDirector: Employee? = null } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class Employee { @@ -122,20 +131,26 @@ properties: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Employee { var name: String? = null var salary: Float? = null } ---- +====== The following code snippets show some examples of how to retrieve and manipulate some of the properties of instantiated ``Company``s and ``Employee``s: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- BeanWrapper company = new BeanWrapperImpl(new Company()); // setting the company name.. @@ -152,8 +167,10 @@ the properties of instantiated ``Company``s and ``Employee``s: // retrieving the salary of the managingDirector through the company Float salary = (Float) company.getPropertyValue("managingDirector.salary"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val company = BeanWrapperImpl(Company()) // setting the company name.. @@ -170,6 +187,7 @@ the properties of instantiated ``Company``s and ``Employee``s: // retrieving the salary of the managingDirector through the company val salary = company.getPropertyValue("managingDirector.salary") as Float? ---- +====== @@ -308,8 +326,11 @@ com The following Java source code for the referenced `SomethingBeanInfo` class associates a `CustomNumberEditor` with the `age` property of the `Something` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SomethingBeanInfo extends SimpleBeanInfo { @@ -330,8 +351,10 @@ associates a `CustomNumberEditor` with the `age` property of the `Something` cla } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SomethingBeanInfo : SimpleBeanInfo() { @@ -351,6 +374,7 @@ associates a `CustomNumberEditor` with the `age` property of the `Something` cla } } ---- +====== [[beans-beans-conversion-customeditor-registration]] @@ -390,8 +414,11 @@ support for additional `PropertyEditor` instances to an `ApplicationContext`. Consider the following example, which defines a user class called `ExoticType` and another class called `DependsOnExoticType`, which needs `ExoticType` set as a property: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package example; @@ -413,8 +440,10 @@ another class called `DependsOnExoticType`, which needs `ExoticType` set as a pr } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package example @@ -425,6 +454,7 @@ another class called `DependsOnExoticType`, which needs `ExoticType` set as a pr var type: ExoticType? = null } ---- +====== When things are properly set up, we want to be able to assign the type property as a string, which a `PropertyEditor` converts into an actual @@ -439,8 +469,11 @@ string, which a `PropertyEditor` converts into an actual The `PropertyEditor` implementation could look similar to the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package example; @@ -454,8 +487,10 @@ The `PropertyEditor` implementation could look similar to the following: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package example @@ -469,6 +504,7 @@ The `PropertyEditor` implementation could look similar to the following: } } ---- +====== Finally, the following example shows how to use `CustomEditorConfigurer` to register the new `PropertyEditor` with the `ApplicationContext`, which will then be able to use it as needed: @@ -504,8 +540,11 @@ instances for each bean creation attempt. The following example shows how to create your own `PropertyEditorRegistrar` implementation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package com.foo.editors.spring; @@ -520,8 +559,10 @@ The following example shows how to create your own `PropertyEditorRegistrar` imp } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package com.foo.editors.spring @@ -539,6 +580,7 @@ The following example shows how to create your own `PropertyEditorRegistrar` imp } } ---- +====== See also the `org.springframework.beans.support.ResourceEditorRegistrar` for an example `PropertyEditorRegistrar` implementation. Notice how in its implementation of the @@ -566,8 +608,11 @@ using xref:web/webmvc.adoc#mvc[Spring's MVC web framework], using a `PropertyEdi conjunction with data-binding web controllers can be very convenient. The following example uses a `PropertyEditorRegistrar` in the implementation of an `@InitBinder` method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class RegisterUserController { @@ -586,8 +631,10 @@ example uses a `PropertyEditorRegistrar` in the implementation of an `@InitBinde // other methods related to registering a User } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class RegisterUserController( @@ -601,6 +648,7 @@ example uses a `PropertyEditorRegistrar` in the implementation of an `@InitBinde // other methods related to registering a User } ---- +====== This style of `PropertyEditor` registration can lead to concise code (the implementation of the `@InitBinder` method is only one line long) and lets common `PropertyEditor` diff --git a/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc b/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc index 46b410824ff..3837d50a355 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc @@ -16,27 +16,36 @@ built-in constraints, and you can also define your own custom constraints. Consider the following example, which shows a simple `PersonForm` model with two properties: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class PersonForm { private String name; private int age; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class PersonForm( private val name: String, private val age: Int ) ---- +====== Bean Validation lets you declare constraints as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class PersonForm { @@ -48,8 +57,10 @@ Bean Validation lets you declare constraints as the following example shows: private int age; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class PersonForm( @get:NotNull @get:Size(max=64) @@ -58,6 +69,7 @@ Bean Validation lets you declare constraints as the following example shows: private val age: Int ) ---- +====== A Bean Validation validator then validates instances of this class based on the declared constraints. See https://beanvalidation.org/[Bean Validation] for general information about @@ -78,8 +90,11 @@ is needed in your application. You can use the `LocalValidatorFactoryBean` to configure a default Validator as a Spring bean, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; @@ -92,12 +107,15 @@ bean, as the following example shows: } } ---- + +XML:: ++ [source,xml,indent=0,subs="verbatim,quotes",role="secondary"] -.XML ---- ---- +====== The basic configuration in the preceding example triggers bean validation to initialize by using its default bootstrap mechanism. A Bean Validation provider, such as the Hibernate @@ -115,8 +133,11 @@ validation logic. You can inject a reference to `jakarta.validation.Validator` if you prefer to work with the Bean Validation API directly, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import jakarta.validation.Validator; @@ -127,20 +148,26 @@ Validation API directly, as the following example shows: private Validator validator; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import jakarta.validation.Validator; @Service class MyService(@Autowired private val validator: Validator) ---- +====== You can inject a reference to `org.springframework.validation.Validator` if your bean requires the Spring Validation API, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.validation.Validator; @@ -151,14 +178,17 @@ requires the Spring Validation API, as the following example shows: private Validator validator; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.validation.Validator @Service class MyService(@Autowired private val validator: Validator) ---- +====== [[validation-beanvalidation-spring-constraints]] @@ -182,8 +212,11 @@ that uses Spring to create `ConstraintValidator` instances. This lets your custo The following example shows a custom `@Constraint` declaration followed by an associated `ConstraintValidator` implementation that uses Spring for dependency injection: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @@ -191,17 +224,23 @@ The following example shows a custom `@Constraint` declaration followed by an as public @interface MyConstraint { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) @Constraint(validatedBy = MyConstraintValidator::class) annotation class MyConstraint ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import jakarta.validation.ConstraintValidator; @@ -213,8 +252,10 @@ The following example shows a custom `@Constraint` declaration followed by an as // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import jakarta.validation.ConstraintValidator @@ -223,6 +264,7 @@ The following example shows a custom `@Constraint` declaration followed by an as // ... } ---- +====== As the preceding example shows, a `ConstraintValidator` implementation can have its dependencies @@ -236,8 +278,11 @@ You can integrate the method validation feature supported by Bean Validation 1.1 a custom extension, also by Hibernate Validator 4.3) into a Spring context through a `MethodValidationPostProcessor` bean definition: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; @@ -251,11 +296,14 @@ a custom extension, also by Hibernate Validator 4.3) into a Spring context throu } ---- + +XML:: ++ [source,xml,indent=0,subs="verbatim,quotes",role="secondary"] -.XML ---- ---- +====== To be eligible for Spring-driven method validation, all target classes need to be annotated with Spring's `@Validated` annotation, which can optionally also declare the validation @@ -296,8 +344,11 @@ automatically added to the binder's `BindingResult`. The following example shows how to use a `DataBinder` programmatically to invoke validation logic after binding to a target object: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Foo target = new Foo(); DataBinder binder = new DataBinder(target); @@ -312,8 +363,10 @@ logic after binding to a target object: // get BindingResult that includes any validation errors BindingResult results = binder.getBindingResult(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val target = Foo() val binder = DataBinder(target) @@ -328,6 +381,7 @@ logic after binding to a target object: // get BindingResult that includes any validation errors val results = binder.bindingResult ---- +====== You can also configure a `DataBinder` with multiple `Validator` instances through `dataBinder.addValidators` and `dataBinder.replaceValidators`. This is useful when diff --git a/framework-docs/modules/ROOT/pages/core/validation/convert.adoc b/framework-docs/modules/ROOT/pages/core/validation/convert.adoc index 3d33722dcea..4d6c6391ab4 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/convert.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/convert.adoc @@ -259,8 +259,11 @@ xref:core/validation/format.adoc#format-FormatterRegistry-SPI[The `FormatterRegi To work with a `ConversionService` instance programmatically, you can inject a reference to it like you would for any other bean. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Service public class MyService { @@ -274,8 +277,10 @@ it like you would for any other bean. The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Service class MyService(private val conversionService: ConversionService) { @@ -285,6 +290,7 @@ it like you would for any other bean. The following example shows how to do so: } } ---- +====== For most use cases, you can use the `convert` method that specifies the `targetType`, but it does not work with more complex types, such as a collection of a parameterized element. @@ -294,8 +300,11 @@ you need to provide a formal definition of the source and target types. Fortunately, `TypeDescriptor` provides various options to make doing so straightforward, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DefaultConversionService cs = new DefaultConversionService(); @@ -304,8 +313,10 @@ as the following example shows: TypeDescriptor.forObject(input), // List type descriptor TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class))); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val cs = DefaultConversionService() @@ -314,6 +325,7 @@ as the following example shows: TypeDescriptor.forObject(input), // List type descriptor TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java))) ---- +====== Note that `DefaultConversionService` automatically registers converters that are appropriate for most environments. This includes collection converters, scalar diff --git a/framework-docs/modules/ROOT/pages/core/validation/format-configuring-formatting-globaldatetimeformat.adoc b/framework-docs/modules/ROOT/pages/core/validation/format-configuring-formatting-globaldatetimeformat.adoc index 053d98d73c1..82fc655f131 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/format-configuring-formatting-globaldatetimeformat.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/format-configuring-formatting-globaldatetimeformat.adoc @@ -13,8 +13,11 @@ formatters manually with the help of: For example, the following Java configuration registers a global `yyyyMMdd` format: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class AppConfig { @@ -44,8 +47,10 @@ For example, the following Java configuration registers a global `yyyyMMdd` form } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class AppConfig { @@ -71,6 +76,7 @@ For example, the following Java configuration registers a global `yyyyMMdd` form } } ---- +====== If you prefer XML-based configuration, you can use a `FormattingConversionServiceFactoryBean`. The following example shows how to do so: diff --git a/framework-docs/modules/ROOT/pages/core/validation/format.adoc b/framework-docs/modules/ROOT/pages/core/validation/format.adoc index c344b2c7313..920e2a44d97 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/format.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/format.adoc @@ -78,8 +78,11 @@ a `java.text.DateFormat`. The following `DateFormatter` is an example `Formatter` implementation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package org.springframework.format.datetime; @@ -112,8 +115,10 @@ The following `DateFormatter` is an example `Formatter` implementation: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- class DateFormatter(private val pattern: String) : Formatter { @@ -131,6 +136,7 @@ The following `DateFormatter` is an example `Formatter` implementation: } } ---- +====== The Spring team welcomes community-driven `Formatter` contributions. See https://github.com/spring-projects/spring-framework/issues[GitHub Issues] to contribute. @@ -169,8 +175,11 @@ formatting logic -- for example `org.springframework.format.annotation.DateTime The following example `AnnotationFormatterFactory` implementation binds the `@NumberFormat` annotation to a formatter to let a number style or pattern be specified: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public final class NumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory { @@ -204,8 +213,10 @@ annotation to a formatter to let a number style or pattern be specified: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory { @@ -235,12 +246,16 @@ annotation to a formatter to let a number style or pattern be specified: } } ---- +====== To trigger formatting, you can annotate fields with `@NumberFormat`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyModel { @@ -248,13 +263,16 @@ example shows: private BigDecimal decimal; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyModel( @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal ) ---- +====== [[format-annotations-api]] @@ -268,8 +286,11 @@ package. You can use `@NumberFormat` to format `Number` fields such as `Double` The following example uses `@DateTimeFormat` to format a `java.util.Date` as an ISO Date (yyyy-MM-dd): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyModel { @@ -277,13 +298,16 @@ The following example uses `@DateTimeFormat` to format a `java.util.Date` as an private Date date; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyModel( @DateTimeFormat(iso=ISO.DATE) private val date: Date ) ---- +====== [[format-FormatterRegistry-SPI]] diff --git a/framework-docs/modules/ROOT/pages/core/validation/validator.adoc b/framework-docs/modules/ROOT/pages/core/validation/validator.adoc index aa78fd755ce..f5140480e8e 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/validator.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/validator.adoc @@ -7,8 +7,11 @@ validators can report validation failures to the `Errors` object. Consider the following example of a small data object: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class Person { @@ -18,11 +21,14 @@ Consider the following example of a small data object: // the usual getters and setters... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Person(val name: String, val age: Int) ---- +====== The next example provides validation behavior for the `Person` class by implementing the following two methods of the `org.springframework.validation.Validator` interface: @@ -35,8 +41,11 @@ Implementing a `Validator` is fairly straightforward, especially when you know o `ValidationUtils` helper class that the Spring Framework also provides. The following example implements `Validator` for `Person` instances: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class PersonValidator implements Validator { @@ -58,8 +67,10 @@ example implements `Validator` for `Person` instances: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class PersonValidator : Validator { @@ -81,6 +92,7 @@ example implements `Validator` for `Person` instances: } } ---- +====== The `static` `rejectIfEmpty(..)` method on the `ValidationUtils` class is used to reject the `name` property if it is `null` or the empty string. Have a look at the @@ -98,8 +110,11 @@ within the `AddressValidator` class without resorting to copy-and-paste, you can dependency-inject or instantiate an `AddressValidator` within your `CustomerValidator`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class CustomerValidator implements Validator { @@ -137,8 +152,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class CustomerValidator(private val addressValidator: Validator) : Validator { @@ -171,6 +188,7 @@ as the following example shows: } } ---- +====== Validation errors are reported to the `Errors` object passed to the validator. In the case of Spring Web MVC, you can use the `` tag to inspect the error messages, but diff --git a/framework-docs/modules/ROOT/pages/data-access/dao.adoc b/framework-docs/modules/ROOT/pages/data-access/dao.adoc index 6745abbb0bf..2af1663add7 100644 --- a/framework-docs/modules/ROOT/pages/data-access/dao.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/dao.adoc @@ -52,14 +52,18 @@ lets the component scanning support find and configure your DAOs and repositorie without having to provide XML configuration entries for them. The following example shows how to use the `@Repository` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository // <1> public class SomeMovieFinder implements MovieFinder { // ... } ---- +====== <1> The `@Repository` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -80,8 +84,11 @@ needs access to a JDBC `DataSource`, and a JPA-based repository needs access to injected by using one of the `@Autowired`, `@Inject`, `@Resource` or `@PersistenceContext` annotations. The following example works for a JPA repository: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository public class JpaMovieFinder implements MovieFinder { @@ -93,8 +100,9 @@ annotations. The following example works for a JPA repository: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Repository class JpaMovieFinder : MovieFinder { @@ -105,13 +113,17 @@ annotations. The following example works for a JPA repository: // ... } ---- +====== If you use the classic Hibernate APIs, you can inject `SessionFactory`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository public class HibernateMovieFinder implements MovieFinder { @@ -126,22 +138,28 @@ example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Repository class HibernateMovieFinder(private val sessionFactory: SessionFactory) : MovieFinder { // ... } ---- +====== The last example we show here is for typical JDBC support. You could have the `DataSource` injected into an initialization method or a constructor, where you would create a `JdbcTemplate` and other data access support classes (such as `SimpleJdbcCall` and others) by using this `DataSource`. The following example autowires a `DataSource`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository public class JdbcMovieFinder implements MovieFinder { @@ -156,8 +174,10 @@ this `DataSource`. The following example autowires a `DataSource`: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Repository class JdbcMovieFinder(dataSource: DataSource) : MovieFinder { @@ -167,6 +187,7 @@ this `DataSource`. The following example autowires a `DataSource`: // ... } ---- +====== NOTE: See the specific coverage of each persistence technology for details on how to configure the application context to take advantage of these annotations. diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc index 203952f64a8..ce43becb612 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc @@ -17,8 +17,11 @@ the prepared statement. This method is called the number of times that you specified in the `getBatchSize` call. The following example updates the `t_actor` table based on entries in a list, and the entire list is used as the batch: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -47,8 +50,10 @@ based on entries in a list, and the entire list is used as the batch: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -71,6 +76,7 @@ based on entries in a list, and the entire list is used as the batch: // ... additional methods } ---- +====== If you process a stream of updates or reading from a file, you might have a preferred batch size, but the last batch might not have that number of entries. In this @@ -94,8 +100,11 @@ in an array of bean-style objects (with getter methods corresponding to paramete The following example shows a batch update using named parameters: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -114,8 +123,10 @@ The following example shows a batch update using named parameters: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -130,6 +141,7 @@ The following example shows a batch update using named parameters: // ... additional methods } ---- +====== For an SQL statement that uses the classic `?` placeholders, you pass in a list containing an object array with the update values. This object array must have one entry @@ -139,8 +151,11 @@ defined in the SQL statement. The following example is the same as the preceding example, except that it uses classic JDBC `?` placeholders: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -165,8 +180,10 @@ JDBC `?` placeholders: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -184,6 +201,7 @@ JDBC `?` placeholders: // ... additional methods } ---- +====== All of the batch update methods that we described earlier return an `int` array containing the number of affected rows for each batch entry. This count is reported by @@ -223,8 +241,11 @@ update calls into batches of the size specified. The following example shows a batch update that uses a batch size of 100: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -250,8 +271,10 @@ The following example shows a batch update that uses a batch size of 100: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -270,6 +293,7 @@ The following example shows a batch update that uses a batch size of 100: // ... additional methods } ---- +====== The batch update method for this call returns an array of `int` arrays that contains an array entry for each batch with an array of the number of affected rows for each update. diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc index fb787e7fa65..e8e60ac527f 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc @@ -48,8 +48,11 @@ To configure a `DriverManagerDataSource`: The following example shows how to configure a `DriverManagerDataSource` in Java: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); @@ -57,8 +60,10 @@ The following example shows how to configure a `DriverManagerDataSource` in Java dataSource.setUsername("sa"); dataSource.setPassword(""); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val dataSource = DriverManagerDataSource().apply { setDriverClassName("org.hsqldb.jdbcDriver") @@ -67,6 +72,7 @@ The following example shows how to configure a `DriverManagerDataSource` in Java password = "" } ---- +====== The following example shows the corresponding XML configuration: diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc index 73c5bda829c..54a1032c22a 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc @@ -57,54 +57,75 @@ See the attendant {api-spring-framework}/jdbc/core/JdbcTemplate.html[javadoc] fo The following query gets the number of rows in a relation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val rowCount = jdbcTemplate.queryForObject("select count(*) from t_actor")!! ---- +====== The following query uses a bind variable: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject( "select count(*) from t_actor where first_name = ?", Integer.class, "Joe"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val countOfActorsNamedJoe = jdbcTemplate.queryForObject( "select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!! ---- +====== The following query looks for a `String`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String lastName = this.jdbcTemplate.queryForObject( "select last_name from t_actor where id = ?", String.class, 1212L); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val lastName = this.jdbcTemplate.queryForObject( "select last_name from t_actor where id = ?", arrayOf(1212L))!! ---- +====== The following query finds and populates a single domain object: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Actor actor = jdbcTemplate.queryForObject( "select first_name, last_name from t_actor where id = ?", @@ -116,8 +137,10 @@ The following query finds and populates a single domain object: }, 1212L); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val actor = jdbcTemplate.queryForObject( "select first_name, last_name from t_actor where id = ?", @@ -125,11 +148,15 @@ The following query finds and populates a single domain object: Actor(rs.getString("first_name"), rs.getString("last_name")) } ---- +====== The following query finds and populates a list of domain objects: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- List actors = this.jdbcTemplate.query( "select first_name, last_name from t_actor", @@ -140,20 +167,26 @@ The following query finds and populates a list of domain objects: return actor; }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ -> Actor(rs.getString("first_name"), rs.getString("last_name")) ---- +====== If the last two snippets of code actually existed in the same application, it would make sense to remove the duplication present in the two `RowMapper` lambda expressions and extract them out into a single field that could then be referenced by DAO methods as needed. For example, it may be better to write the preceding code snippet as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- private final RowMapper actorRowMapper = (resultSet, rowNum) -> { Actor actor = new Actor(); @@ -166,8 +199,10 @@ For example, it may be better to write the preceding code snippet as follows: return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val actorMapper = RowMapper { rs: ResultSet, rowNum: Int -> Actor(rs.getString("first_name"), rs.getString("last_name")) @@ -177,6 +212,7 @@ For example, it may be better to write the preceding code snippet as follows: return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper) } ---- +====== [[jdbc-JdbcTemplate-examples-update]] === Updating (`INSERT`, `UPDATE`, and `DELETE`) with `JdbcTemplate` @@ -186,52 +222,70 @@ Parameter values are usually provided as variable arguments or, alternatively, a The following example inserts a new entry: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- this.jdbcTemplate.update( "insert into t_actor (first_name, last_name) values (?, ?)", "Leonor", "Watling"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- jdbcTemplate.update( "insert into t_actor (first_name, last_name) values (?, ?)", "Leonor", "Watling") ---- +====== The following example updates an existing entry: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- this.jdbcTemplate.update( "update t_actor set last_name = ? where id = ?", "Banjo", 5276L); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- jdbcTemplate.update( "update t_actor set last_name = ? where id = ?", "Banjo", 5276L) ---- +====== The following example deletes an entry: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- this.jdbcTemplate.update( "delete from t_actor where id = ?", Long.valueOf(actorId)); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong()) ---- +====== [[jdbc-JdbcTemplate-examples-other]] === Other `JdbcTemplate` Operations @@ -241,33 +295,45 @@ method is often used for DDL statements. It is heavily overloaded with variants callback interfaces, binding variable arrays, and so on. The following example creates a table: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- jdbcTemplate.execute("create table mytable (id integer, name varchar(100))") ---- +====== The following example invokes a stored procedure: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- this.jdbcTemplate.update( "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", Long.valueOf(unionId)); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- jdbcTemplate.update( "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", unionId.toLong()) ---- +====== More sophisticated stored procedure support is xref:data-access/jdbc/object.adoc#jdbc-StoredProcedure[covered later]. @@ -288,8 +354,11 @@ that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is creat the setter for the `DataSource`. This leads to DAOs that resemble the following: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcCorporateEventDao implements CorporateEventDao { @@ -302,8 +371,10 @@ the setter for the `DataSource`. This leads to DAOs that resemble the following: // JDBC-backed implementations of the methods on the CorporateEventDao follow... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { @@ -312,6 +383,7 @@ the setter for the `DataSource`. This leads to DAOs that resemble the following: // JDBC-backed implementations of the methods on the CorporateEventDao follow... } ---- +====== -- The following example shows the corresponding XML configuration: @@ -350,8 +422,11 @@ support for dependency injection. In this case, you can annotate the class with method with `@Autowired`. The following example shows how to do so: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository // <1> public class JdbcCorporateEventDao implements CorporateEventDao { @@ -366,6 +441,7 @@ method with `@Autowired`. The following example shows how to do so: // 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`. @@ -440,8 +516,11 @@ section describes only those areas of the `NamedParameterJdbcTemplate` class tha from the `JdbcTemplate` itself -- namely, programming JDBC statements by using named parameters. The following example shows how to use `NamedParameterJdbcTemplate`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; @@ -460,8 +539,9 @@ parameters. The following example shows how to use `NamedParameterJdbcTemplate`: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) @@ -471,6 +551,7 @@ parameters. The following example shows how to use `NamedParameterJdbcTemplate`: return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!! } ---- +====== Notice the use of the named parameter notation in the value assigned to the `sql` variable and the corresponding value that is plugged into the `namedParameters` @@ -483,8 +564,11 @@ methods exposed by the `NamedParameterJdbcOperations` and implemented by the The following example shows the use of the `Map`-based style: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; @@ -502,8 +586,10 @@ The following example shows the use of the `Map`-based style: return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // some JDBC-backed DAO class... private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) @@ -514,6 +600,7 @@ The following example shows the use of the `Map`-based style: return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!! } ---- +====== One nice feature related to the `NamedParameterJdbcTemplate` (and existing in the same Java package) is the `SqlParameterSource` interface. You have already seen an example of @@ -531,8 +618,11 @@ of named parameter values. The following example shows a typical JavaBean: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class Actor { @@ -556,17 +646,23 @@ The following example shows a typical JavaBean: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- data class Actor(val id: Long, val firstName: String, val lastName: String) ---- +====== The following example uses a `NamedParameterJdbcTemplate` to return the count of the members of the class shown in the preceding example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; @@ -585,8 +681,10 @@ members of the class shown in the preceding example: return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // some JDBC-backed DAO class... private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) @@ -600,6 +698,7 @@ members of the class shown in the preceding example: return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!! } ---- +====== Remember that the `NamedParameterJdbcTemplate` class wraps a classic `JdbcTemplate` template. If you need access to the wrapped `JdbcTemplate` instance to access @@ -651,8 +750,11 @@ name from the database metadata of the database in use. You can extend `SQLErrorCodeSQLExceptionTranslator`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { @@ -664,8 +766,10 @@ You can extend `SQLErrorCodeSQLExceptionTranslator`, as the following example sh } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class CustomSQLErrorCodesTranslator : SQLErrorCodeSQLExceptionTranslator() { @@ -677,6 +781,7 @@ You can extend `SQLErrorCodeSQLExceptionTranslator`, as the following example sh } } ---- +====== In the preceding example, the specific error code (`-12345`) is translated, while other errors are left to be translated by the default translator implementation. To use this custom @@ -685,8 +790,11 @@ translator, you must pass it to the `JdbcTemplate` through the method processing where this translator is needed. The following example shows how you can use this custom translator: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- private JdbcTemplate jdbcTemplate; @@ -710,8 +818,10 @@ translator: " where id = ?", pct, orderId); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // create a JdbcTemplate and set data source private val jdbcTemplate = JdbcTemplate(dataSource).apply { @@ -728,6 +838,7 @@ translator: " where id = ?", pct, orderId) } ---- +====== The custom translator is passed a data source in order to look up the error codes in `sql-error-codes.xml`. @@ -741,8 +852,11 @@ Running an SQL statement requires very little code. You need a `DataSource` and `JdbcTemplate`. The following example shows what you need to include for a minimal but fully functional class that creates a new table: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; @@ -760,8 +874,10 @@ fully functional class that creates a new table: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import javax.sql.DataSource import org.springframework.jdbc.core.JdbcTemplate @@ -775,6 +891,7 @@ fully functional class that creates a new table: } } ---- +====== [[jdbc-statements-querying]] @@ -786,8 +903,11 @@ Java class that is passed in as an argument. If the type conversion is invalid, `InvalidDataAccessApiUsageException` is thrown. The following example contains two query methods, one for an `int` and one that queries for a `String`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; @@ -809,8 +929,10 @@ query methods, one for an `int` and one that queries for a `String`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import javax.sql.DataSource import org.springframework.jdbc.core.JdbcTemplate @@ -826,6 +948,7 @@ class RunAQuery(dataSource: DataSource) { get() = jdbcTemplate.queryForObject("select name from mytable") } ---- +====== In addition to the single result query methods, several methods return a list with an entry for each row that the query returned. The most generic method is `queryForList(..)`, @@ -833,8 +956,11 @@ which returns a `List` where each element is a `Map` containing one entry for ea using the column name as the key. If you add a method to the preceding example to retrieve a list of all the rows, it might be as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- private JdbcTemplate jdbcTemplate; @@ -846,8 +972,10 @@ list of all the rows, it might be as follows: return this.jdbcTemplate.queryForList("select * from mytable"); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- private val jdbcTemplate = JdbcTemplate(dataSource) @@ -855,6 +983,7 @@ list of all the rows, it might be as follows: return jdbcTemplate.queryForList("select * from mytable") } ---- +====== The returned list would resemble the following: @@ -869,8 +998,11 @@ The returned list would resemble the following: The following example updates a column for a certain primary key: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; @@ -888,8 +1020,10 @@ The following example updates a column for a certain primary key: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import javax.sql.DataSource import org.springframework.jdbc.core.JdbcTemplate @@ -903,6 +1037,7 @@ The following example updates a column for a certain primary key: } } ---- +====== In the preceding example, an SQL statement has placeholders for row parameters. You can pass the parameter values @@ -922,8 +1057,11 @@ update. There is no standard single way to create an appropriate `PreparedStatem (which explains why the method signature is the way it is). The following example works on Oracle but may not work on other platforms: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- final String INSERT_SQL = "insert into my_test (name) values(?)"; final String name = "Rob"; @@ -937,8 +1075,10 @@ on Oracle but may not work on other platforms: // keyHolder.getKey() now contains the generated key ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val INSERT_SQL = "insert into my_test (name) values(?)" val name = "Rob" @@ -950,6 +1090,7 @@ on Oracle but may not work on other platforms: // keyHolder.getKey() now contains the generated key ---- +====== diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc index 740d49db44d..eaa86da5f26 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc @@ -44,8 +44,11 @@ The `EmbeddedDatabaseBuilder` class provides a fluent API for constructing an em database programmatically. You can use this when you need to create an embedded database in a stand-alone environment or in a stand-alone integration test, as in the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- EmbeddedDatabase db = new EmbeddedDatabaseBuilder() .generateUniqueName(true) @@ -60,8 +63,10 @@ stand-alone environment or in a stand-alone integration test, as in the followin db.shutdown() ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val db = EmbeddedDatabaseBuilder() .generateUniqueName(true) @@ -76,6 +81,7 @@ stand-alone environment or in a stand-alone integration test, as in the followin db.shutdown() ---- +====== See the {api-spring-framework}/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.html[javadoc for `EmbeddedDatabaseBuilder`] for further details on all supported options. @@ -83,8 +89,11 @@ for further details on all supported options. You can also use the `EmbeddedDatabaseBuilder` to create an embedded database by using Java configuration, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class DataSourceConfig { @@ -102,8 +111,10 @@ configuration, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class DataSourceConfig { @@ -121,6 +132,7 @@ configuration, as the following example shows: } } ---- +====== [[jdbc-embedded-database-types]] @@ -168,8 +180,11 @@ configuring the embedded database as a bean in the Spring `ApplicationContext` a in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-xml[Creating an Embedded Database by Using Spring XML] and xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-java[Creating an Embedded Database Programmatically]. The following listing shows the test template: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class DataAccessIntegrationTestTemplate { @@ -198,8 +213,10 @@ shows the test template: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class DataAccessIntegrationTestTemplate { @@ -227,6 +244,7 @@ shows the test template: } } ---- +====== [[jdbc-embedded-database-unique-names]] diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/object.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/object.adoc index 4e0610bfd12..65fd60c76e0 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/object.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/object.adoc @@ -40,8 +40,11 @@ abstract `mapRow(..)` method to convert each row of the supplied `ResultSet` int object of the type specified. The following example shows a custom query that maps the data from the `t_actor` relation to an instance of the `Actor` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ActorMappingQuery extends MappingSqlQuery { @@ -61,8 +64,10 @@ data from the `t_actor` relation to an instance of the `Actor` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ActorMappingQuery(ds: DataSource) : MappingSqlQuery(ds, "select id, first_name, last_name from t_actor where id = ?") { @@ -79,6 +84,7 @@ data from the `t_actor` relation to an instance of the `Actor` class: } ---- +====== The class extends `MappingSqlQuery` parameterized with the `Actor` type. The constructor for this customer query takes a `DataSource` as the only parameter. In this @@ -93,8 +99,11 @@ thread-safe after it is compiled, so, as long as these instances are created whe is initialized, they can be kept as instance variables and be reused. The following example shows how to define such a class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- private ActorMappingQuery actorMappingQuery; @@ -107,13 +116,16 @@ example shows how to define such a class: return actorMappingQuery.findObject(id); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- private val actorMappingQuery = ActorMappingQuery(dataSource) fun getCustomer(id: Long) = actorMappingQuery.findObject(id) ---- +====== The method in the preceding example retrieves the customer with the `id` that is passed in as the only parameter. Since we want only one object to be returned, we call the `findObject` convenience @@ -122,19 +134,25 @@ list of objects and took additional parameters, we would use one of the `execute methods that takes an array of parameter values passed in as varargs. The following example shows such a method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public List searchForActors(int age, String namePattern) { return actorSearchMappingQuery.execute(age, namePattern); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun searchForActors(age: Int, namePattern: String) = actorSearchMappingQuery.execute(age, namePattern) ---- +====== [[jdbc-SqlUpdate]] @@ -149,8 +167,11 @@ However, you do not have to subclass the `SqlUpdate` class, since it can easily be parameterized by setting SQL and declaring parameters. The following example creates a custom update method named `execute`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import java.sql.Types; import javax.sql.DataSource; @@ -177,8 +198,10 @@ The following example creates a custom update method named `execute`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import java.sql.Types import javax.sql.DataSource @@ -205,6 +228,7 @@ The following example creates a custom update method named `execute`: } } ---- +====== [[jdbc-StoredProcedure]] @@ -219,18 +243,24 @@ To define a parameter for the `StoredProcedure` class, you can use an `SqlParame of its subclasses. You must specify the parameter name and SQL type in the constructor, as the following code snippet shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR), ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- SqlParameter("in_id", Types.NUMERIC), SqlOutParameter("out_first_name", Types.VARCHAR), ---- +====== The SQL type is specified using the `java.sql.Types` constants. @@ -259,8 +289,11 @@ returned date from the results `Map`. The results `Map` has an entry for each de output parameter (in this case, only one) by using the parameter name as the key. The following listing shows our custom StoredProcedure class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import java.sql.Types; import java.util.Date; @@ -306,8 +339,10 @@ The following listing shows our custom StoredProcedure class: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import java.sql.Types import java.util.Date @@ -343,12 +378,16 @@ The following listing shows our custom StoredProcedure class: } } ---- +====== The following example of a `StoredProcedure` has two output parameters (in this case, Oracle REF cursors): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import java.util.HashMap; import java.util.Map; @@ -374,8 +413,10 @@ Oracle REF cursors): } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import java.util.HashMap import javax.sql.DataSource @@ -401,6 +442,7 @@ Oracle REF cursors): } } ---- +====== Notice how the overloaded variants of the `declareParameter(..)` method that have been used in the `TitlesAndGenresStoredProcedure` constructor are passed `RowMapper` @@ -410,8 +452,11 @@ functionality. The next two examples provide code for the two `RowMapper` implem The `TitleMapper` class maps a `ResultSet` to a `Title` domain object for each row in the supplied `ResultSet`, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import java.sql.ResultSet; import java.sql.SQLException; @@ -428,8 +473,10 @@ the supplied `ResultSet`, as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import java.sql.ResultSet import com.foo.domain.Title @@ -441,12 +488,16 @@ the supplied `ResultSet`, as follows: Title(rs.getLong("id"), rs.getString("name")) } ---- +====== The `GenreMapper` class maps a `ResultSet` to a `Genre` domain object for each row in the supplied `ResultSet`, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import java.sql.ResultSet; import java.sql.SQLException; @@ -460,8 +511,10 @@ the supplied `ResultSet`, as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import java.sql.ResultSet import com.foo.domain.Genre @@ -474,13 +527,17 @@ the supplied `ResultSet`, as follows: } } ---- +====== To pass parameters to a stored procedure that has one or more input parameters in its definition in the RDBMS, you can code a strongly typed `execute(..)` method that would delegate to the untyped `execute(Map)` method in the superclass, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import java.sql.Types; import java.util.Date; @@ -511,8 +568,10 @@ delegate to the untyped `execute(Map)` method in the superclass, as the followin } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import java.sql.Types import java.util.Date @@ -539,6 +598,7 @@ delegate to the untyped `execute(Map)` method in the superclass, as the followin mapOf(CUTOFF_DATE_PARAM to cutoffDate)) } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc index 09210858805..7d7101d26e7 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc @@ -63,8 +63,11 @@ dependency injection. The following example shows how to create and insert a BLOB: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- final File blobIn = new File("spring2004.jpg"); final InputStream blobIs = new FileInputStream(blobIn); @@ -86,6 +89,7 @@ The following example shows how to create and insert a BLOB: 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. @@ -134,8 +138,11 @@ Now it is time to read the LOB data from the database. Again, you use a `JdbcTem with the same instance variable `lobHandler` and a reference to a `DefaultLobHandler`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- List> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table", new RowMapper>() { @@ -149,6 +156,7 @@ The following example shows how to do so: } }); ---- +====== <1> Using the method `getClobAsString` to retrieve the contents of the CLOB. <2> Using the method `getBlobAsBytes` to retrieve the contents of the BLOB. @@ -203,8 +211,11 @@ implemented. This interface is used as part of the declaration of an `SqlOutPara The following example shows returning the value of an Oracle `STRUCT` object of the user declared type `ITEM_TYPE`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class TestItemStoredProcedure extends StoredProcedure { @@ -223,8 +234,10 @@ declared type `ITEM_TYPE`: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() { @@ -239,6 +252,7 @@ declared type `ITEM_TYPE`: } } ---- +====== You can use `SqlTypeValue` to pass the value of a Java object (such as `TestItem`) to a stored procedure. The `SqlTypeValue` interface has a single method (named @@ -246,8 +260,11 @@ stored procedure. The `SqlTypeValue` interface has a single method (named can use it to create database-specific objects, such as `StructDescriptor` instances or `ArrayDescriptor` instances. The following example creates a `StructDescriptor` instance: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- final TestItem testItem = new TestItem(123L, "A test item", new SimpleDateFormat("yyyy-M-d").parse("2010-12-31")); @@ -265,8 +282,10 @@ or `ArrayDescriptor` instances. The following example creates a `StructDescripto } }; ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val (id, description, expirationDate) = TestItem(123L, "A test item", SimpleDateFormat("yyyy-M-d").parse("2010-12-31")) @@ -279,6 +298,7 @@ or `ArrayDescriptor` instances. The following example creates a `StructDescripto } } ---- +====== You can now add this `SqlTypeValue` to the `Map` that contains the input parameters for the `execute` call of the stored procedure. @@ -288,8 +308,11 @@ procedure. Oracle has its own internal `ARRAY` class that must be used in this c you can use the `SqlTypeValue` to create an instance of the Oracle `ARRAY` and populate it with values from the Java `ARRAY`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- final Long[] ids = new Long[] {1L, 2L}; @@ -301,8 +324,10 @@ it with values from the Java `ARRAY`, as the following example shows: } }; ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() { @@ -317,6 +342,7 @@ it with values from the Java `ARRAY`, as the following example shows: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc index 8b711afb556..9e0c0200a17 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc @@ -19,8 +19,11 @@ Configuration methods for this class follow the `fluid` style that returns the i of the `SimpleJdbcInsert`, which lets you chain all configuration methods. The following example uses only one configuration method (we show examples of multiple methods later): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -41,8 +44,10 @@ example uses only one configuration method (we show examples of multiple methods // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -59,6 +64,7 @@ example uses only one configuration method (we show examples of multiple methods // ... additional methods } ---- +====== The `execute` method used here takes a plain `java.util.Map` as its only parameter. The important thing to note here is that the keys used for the `Map` must match the column @@ -75,8 +81,11 @@ the `SimpleJdbcInsert`, in addition to specifying the table name, it specifies t of the generated key column with the `usingGeneratedKeyColumns` method. The following listing shows how it works: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -99,8 +108,10 @@ listing shows how it works: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -118,6 +129,7 @@ listing shows how it works: // ... additional methods } ---- +====== The main difference when you run the insert by using this second approach is that you do not add the `id` to the `Map`, and you call the `executeAndReturnKey` method. This returns a @@ -134,8 +146,11 @@ use a `KeyHolder` that is returned from the `executeAndReturnKeyHolder` method. You can limit the columns for an insert by specifying a list of column names with the `usingColumns` method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -159,8 +174,10 @@ You can limit the columns for an insert by specifying a list of column names wit // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -180,6 +197,7 @@ You can limit the columns for an insert by specifying a list of column names wit // ... additional methods } ---- +====== The execution of the insert is the same as if you had relied on the metadata to determine which columns to use. @@ -195,8 +213,11 @@ which is a very convenient class if you have a JavaBean-compliant class that con your values. It uses the corresponding getter method to extract the parameter values. The following example shows how to use `BeanPropertySqlParameterSource`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -217,8 +238,10 @@ values. The following example shows how to use `BeanPropertySqlParameterSource`: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -235,12 +258,16 @@ values. The following example shows how to use `BeanPropertySqlParameterSource`: // ... additional methods } ---- +====== Another option is the `MapSqlParameterSource` that resembles a `Map` but provides a more convenient `addValue` method that can be chained. The following example shows how to use it: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -263,8 +290,10 @@ convenient `addValue` method that can be chained. The following example shows ho // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -283,6 +312,7 @@ convenient `addValue` method that can be chained. The following example shows ho // ... additional methods } ---- +====== As you can see, the configuration is the same. Only the executing code has to change to use these alternative input classes. @@ -325,8 +355,11 @@ The following example of a `SimpleJdbcCall` configuration uses the preceding sto procedure (the only configuration option, in addition to the `DataSource`, is the name of the stored procedure): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -352,8 +385,10 @@ of the stored procedure): // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -374,6 +409,7 @@ of the stored procedure): // ... additional methods } ---- +====== The code you write for the execution of the call involves creating an `SqlParameterSource` containing the IN parameter. You must match the name provided for the input value @@ -397,8 +433,11 @@ To do the latter, you can create your own `JdbcTemplate` and set the `setResults property to `true`. Then you can pass this customized `JdbcTemplate` instance into the constructor of your `SimpleJdbcCall`. The following example shows this configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -414,8 +453,10 @@ the constructor of your `SimpleJdbcCall`. The following example shows this confi // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -426,6 +467,7 @@ the constructor of your `SimpleJdbcCall`. The following example shows this confi // ... additional methods } ---- +====== By taking this action, you avoid conflicts in the case used for the names of your returned `out` parameters. @@ -456,8 +498,11 @@ of IN parameter names to include for a given signature. The following example shows a fully declared procedure call and uses the information from the preceding example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -481,8 +526,10 @@ the preceding example: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -501,6 +548,7 @@ the preceding example: // ... additional methods } ---- +====== The execution and end results of the two examples are the same. The second example specifies all details explicitly rather than relying on metadata. @@ -515,18 +563,24 @@ To do so, you typically specify the parameter name and SQL type in the construct is specified by using the `java.sql.Types` constants. Earlier in this chapter, we saw declarations similar to the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR), ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- SqlParameter("in_id", Types.NUMERIC), SqlOutParameter("out_first_name", Types.VARCHAR), ---- +====== The first line with the `SqlParameter` declares an IN parameter. You can use IN parameters for both stored procedure calls and for queries by using the `SqlQuery` and its @@ -578,8 +632,11 @@ that returns an actor's full name: To call this function, we again create a `SimpleJdbcCall` in the initialization method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -602,8 +659,10 @@ as the following example shows: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -621,6 +680,7 @@ as the following example shows: // ... additional methods } ---- +====== The `executeFunction` method used returns a `String` that contains the return value from the function call. @@ -656,8 +716,11 @@ to map follows the JavaBean rules, you can use a `BeanPropertyRowMapper` that is passing in the required class to map to in the `newInstance` method. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class JdbcActorDao implements ActorDao { @@ -680,8 +743,10 @@ The following example shows how to do so: // ... additional methods } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class JdbcActorDao(dataSource: DataSource) : ActorDao { @@ -699,6 +764,7 @@ The following example shows how to do so: // ... additional methods } ---- +====== The `execute` call passes in an empty `Map`, because this call does not take any parameters. The list of actors is then retrieved from the results map and returned to the caller. diff --git a/framework-docs/modules/ROOT/pages/data-access/orm/general.adoc b/framework-docs/modules/ROOT/pages/data-access/orm/general.adoc index bb47bbd68f6..3138409d36e 100644 --- a/framework-docs/modules/ROOT/pages/data-access/orm/general.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/orm/general.adoc @@ -61,8 +61,11 @@ do not need any special exception treatment (or both). However, Spring lets exce translation be applied transparently through the `@Repository` annotation. The following examples (one for Java configuration and one for XML configuration) show how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repository public class ProductDaoImpl implements ProductDao { @@ -71,8 +74,10 @@ examples (one for Java configuration and one for XML configuration) show how to } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Repository class ProductDaoImpl : ProductDao { @@ -81,6 +86,7 @@ examples (one for Java configuration and one for XML configuration) show how to } ---- +====== [source,xml,indent=0,subs="verbatim,quotes"] ---- diff --git a/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc b/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc index c449194c0aa..883aafc3b0d 100644 --- a/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc @@ -95,8 +95,11 @@ one current `Session` per transaction. This is roughly equivalent to Spring's synchronization of one Hibernate `Session` per transaction. A corresponding DAO implementation resembles the following example, based on the plain Hibernate API: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ProductDaoImpl implements ProductDao { @@ -114,8 +117,10 @@ implementation resembles the following example, based on the plain Hibernate API } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao { @@ -127,6 +132,7 @@ implementation resembles the following example, based on the plain Hibernate API } } ---- +====== This style is similar to that of the Hibernate reference documentation and examples, except for holding the `SessionFactory` in an instance variable. We strongly recommend @@ -192,8 +198,11 @@ You can annotate the service layer with `@Transactional` annotations and instruc Spring container to find these annotations and provide transactional semantics for these annotated methods. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ProductServiceImpl implements ProductService { @@ -215,8 +224,10 @@ these annotated methods. The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ProductServiceImpl(private val productDao: ProductDao) : ProductService { @@ -230,6 +241,7 @@ these annotated methods. The following example shows how to do so: fun findAllProducts() = productDao.findAllProducts() } ---- +====== In the container, you need to set up the `PlatformTransactionManager` implementation (as a bean) and a `` entry, opting into `@Transactional` @@ -295,8 +307,11 @@ and an example for a business method implementation: ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ProductServiceImpl implements ProductService { @@ -321,8 +336,10 @@ and an example for a business method implementation: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ProductServiceImpl(transactionManager: PlatformTransactionManager, private val productDao: ProductDao) : ProductService { @@ -337,6 +354,7 @@ and an example for a business method implementation: } } ---- +====== Spring's `TransactionInterceptor` lets any checked application exception be thrown with the callback code, while `TransactionTemplate` is restricted to unchecked diff --git a/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc b/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc index 3f026516979..0f60323febc 100644 --- a/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc @@ -293,8 +293,11 @@ using an injected `EntityManagerFactory` or `EntityManager`. Spring can understa if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain JPA DAO implementation that uses the `@PersistenceUnit` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ProductDaoImpl implements ProductDao { @@ -320,8 +323,10 @@ that uses the `@PersistenceUnit` annotation: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ProductDaoImpl : ProductDao { @@ -340,6 +345,7 @@ that uses the `@PersistenceUnit` annotation: } } ---- +====== The preceding DAO has no dependency on Spring and still fits nicely into a Spring application context. Moreover, the DAO takes advantage of annotations to require the @@ -382,8 +388,11 @@ the factory. You can avoid this by requesting a transactional `EntityManager` (a called a "`shared EntityManager`" because it is a shared, thread-safe proxy for the actual transactional EntityManager) to be injected instead of the factory. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class ProductDaoImpl implements ProductDao { @@ -397,8 +406,10 @@ transactional EntityManager) to be injected instead of the factory. The followin } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class ProductDaoImpl : ProductDao { @@ -412,6 +423,7 @@ transactional EntityManager) to be injected instead of the factory. The followin } } ---- +====== The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults to `PersistenceContextType.TRANSACTION`. You can use this default to receive a shared diff --git a/framework-docs/modules/ROOT/pages/data-access/oxm.adoc b/framework-docs/modules/ROOT/pages/data-access/oxm.adoc index a1637cbff6a..3cbe3a502e7 100644 --- a/framework-docs/modules/ROOT/pages/data-access/oxm.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/oxm.adoc @@ -171,8 +171,11 @@ You can use Spring's OXM for a wide variety of situations. In the following exam use it to marshal the settings of a Spring-managed application as an XML file. In the following example, we use a simple JavaBean to represent the settings: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class Settings { @@ -187,21 +190,27 @@ use a simple JavaBean to represent the settings: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Settings { var isFooEnabled: Boolean = false } ---- +====== The application class uses this bean to store its settings. Besides a main method, the class has two methods: `saveSettings()` saves the settings bean to a file named `settings.xml`, and `loadSettings()` loads these settings again. The following `main()` method constructs a Spring application context and calls these two methods: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import java.io.FileInputStream; import java.io.FileOutputStream; @@ -249,8 +258,10 @@ constructs a Spring application context and calls these two methods: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Application { @@ -276,6 +287,7 @@ constructs a Spring application context and calls these two methods: application.loadSettings() } ---- +====== The `Application` requires both a `marshaller` and an `unmarshaller` property to be set. We can do so by using the following `applicationContext.xml`: diff --git a/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc b/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc index 1345dd5fb0e..57d5c964324 100644 --- a/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc @@ -60,16 +60,22 @@ and give it to DAOs as a bean reference. The simplest way to create a `DatabaseClient` object is through a static factory method, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DatabaseClient client = DatabaseClient.create(connectionFactory); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = DatabaseClient.create(connectionFactory) ---- +====== NOTE: The `ConnectionFactory` should always be configured as a bean in the Spring IoC container. @@ -119,18 +125,24 @@ See the attendant {api-spring-framework}/r2dbc/core/DatabaseClient.html[javadoc] The following example shows what you need to include for minimal but fully functional code that creates a new table: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);") .then(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);") .await() ---- +====== `DatabaseClient` is designed for convenient, fluent usage. It exposes intermediate, continuation, and terminal methods at each stage of the @@ -150,35 +162,47 @@ depending on the issued query. The following query gets the `id` and `name` columns from a table: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono> first = client.sql("SELECT id, name FROM person") .fetch().first(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val first = client.sql("SELECT id, name FROM person") .fetch().awaitSingle() ---- +====== The following query uses a bind variable: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn") .bind("fn", "Joe") .fetch().first(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn") .bind("fn", "Joe") .fetch().awaitSingle() ---- +====== You might have noticed the use of `fetch()` in the example above. `fetch()` is a continuation operator that lets you specify how much data you want to consume. @@ -205,20 +229,26 @@ collections and maps, and objects). The following example extracts the `name` column and emits its value: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Flux names = client.sql("SELECT name FROM person") .map(row -> row.get("name", String.class)) .all(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val names = client.sql("SELECT name FROM person") .map{ row: Row -> row.get("name", String.class) } .flow() ---- +====== [[r2dbc-DatabaseClient-mapping-null]] @@ -242,20 +272,26 @@ do not return tabular data so you use `rowsUpdated()` to consume results. The following example shows an `UPDATE` statement that returns the number of updated rows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono affectedRows = client.sql("UPDATE person SET first_name = :fn") .bind("fn", "Joe") .fetch().rowsUpdated(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val affectedRows = client.sql("UPDATE person SET first_name = :fn") .bind("fn", "Joe") .fetch().awaitRowsUpdated() ---- +====== [[r2dbc-DatabaseClient-named-parameters]] ==== Binding Values to Queries @@ -277,7 +313,6 @@ Parameter binding supports two binding strategies: The following example shows parameter binding for a query: -==== [source,java] ---- db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)") @@ -285,7 +320,6 @@ db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)") .bind("name", "Joe") .bind("age", 34); ---- -==== .R2DBC Native Bind Markers **** @@ -318,8 +352,11 @@ SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50 The preceding query can be parameterized and run as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- List tuples = new ArrayList<>(); tuples.add(new Object[] {"John", 35}); @@ -328,8 +365,10 @@ The preceding query can be parameterized and run as follows: client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)") .bind("tuples", tuples); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val tuples: MutableList> = ArrayList() tuples.add(arrayOf("John", 35)) @@ -338,19 +377,25 @@ The preceding query can be parameterized and run as follows: client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)") .bind("tuples", tuples) ---- +====== NOTE: Usage of select lists is vendor-dependent. The following example shows a simpler variant using `IN` predicates: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)") .bind("ages", Arrays.asList(35, 50)); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val tuples: MutableList> = ArrayList() tuples.add(arrayOf("John", 35)) @@ -359,6 +404,7 @@ The following example shows a simpler variant using `IN` predicates: client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)") .bind("tuples", arrayOf(35, 50)) ---- +====== NOTE: R2DBC itself does not support Collection-like values. Nevertheless, expanding a given `List` in the example above works for named parameters @@ -376,27 +422,36 @@ before it gets run. Register a `Statement` filter (`StatementFilterFunction`) through `DatabaseClient` to intercept and modify statements in their execution, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.sql("INSERT INTO table (name, state) VALUES(:name, :state)") .filter((s, next) -> next.execute(s.returnGeneratedValues("id"))) .bind("name", …) .bind("state", …); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.sql("INSERT INTO table (name, state) VALUES(:name, :state)") .filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) } .bind("name", …) .bind("state", …) ---- +====== `DatabaseClient` exposes also simplified `filter(…)` overload accepting `Function`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.sql("INSERT INTO table (name, state) VALUES(:name, :state)") .filter(statement -> s.returnGeneratedValues("id")); @@ -404,8 +459,10 @@ modify statements in their execution, as the following example shows: client.sql("SELECT id, name, state FROM table") .filter(statement -> s.fetchSize(25)); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.sql("INSERT INTO table (name, state) VALUES(:name, :state)") .filter { statement -> s.returnGeneratedValues("id") } @@ -413,6 +470,7 @@ modify statements in their execution, as the following example shows: client.sql("SELECT id, name, state FROM table") .filter { statement -> s.fetchSize(25) } ---- +====== `StatementFilterFunction` implementations allow filtering of the `Statement` and filtering of `Result` objects. @@ -432,8 +490,11 @@ that shared `ConnectionFactory` bean into your DAO classes. The `DatabaseClient` the setter for the `ConnectionFactory`. This leads to DAOs that resemble the following: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class R2dbcCorporateEventDao implements CorporateEventDao { @@ -446,8 +507,10 @@ the setter for the `ConnectionFactory`. This leads to DAOs that resemble the fol // R2DBC-backed implementations of the methods on the CorporateEventDao follow... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao { @@ -456,6 +519,7 @@ the setter for the `ConnectionFactory`. This leads to DAOs that resemble the fol // R2DBC-backed implementations of the methods on the CorporateEventDao follow... } ---- +====== -- An alternative to explicit configuration is to use component-scanning and annotation @@ -464,8 +528,11 @@ support for dependency injection. In this case, you can annotate the class with method with `@Autowired`. The following example shows how to do so: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component // <1> public class R2dbcCorporateEventDao implements CorporateEventDao { @@ -480,6 +547,7 @@ method with `@Autowired`. The following example shows how to do so: // 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`. @@ -516,8 +584,11 @@ that defines an auto-increment or identity column. To get full control over the column name to generate, simply register a `StatementFilterFunction` that requests the generated key for the desired column. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)") .filter(statement -> s.returnGeneratedValues("id")) @@ -526,8 +597,10 @@ requests the generated key for the desired column. // generatedId emits the generated key once the INSERT statement has finished ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)") .filter { statement -> s.returnGeneratedValues("id") } @@ -536,6 +609,7 @@ requests the generated key for the desired column. // generatedId emits the generated key once the INSERT statement has finished ---- +====== [[r2dbc-connections]] @@ -575,16 +649,22 @@ To configure a `ConnectionFactory`: The following example shows how to configure a `ConnectionFactory`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"); ---- +====== [[r2dbc-ConnectionFactoryUtils]] diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc index 83c9609e700..a43d6e06fb4 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc @@ -15,8 +15,11 @@ The ease-of-use afforded by the use of the `@Transactional` annotation is best illustrated with an example, which is explained in the text that follows. Consider the following class definition: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // the service class that we want to make transactional @Transactional @@ -43,8 +46,10 @@ Consider the following class definition: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // the service class that we want to make transactional @Transactional @@ -67,6 +72,7 @@ Consider the following class definition: } } ---- +====== Used at the class level as above, the annotation indicates a default for all methods of the declaring class (as well as its subclasses). Alternatively, each method can be @@ -128,8 +134,11 @@ preceding example. Reactive transactional methods use reactive return types in contrast to imperative programming arrangements as the following listing shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // the reactive service class that we want to make transactional @Transactional @@ -156,8 +165,10 @@ programming arrangements as the following listing shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // the reactive service class that we want to make transactional @Transactional @@ -180,6 +191,7 @@ programming arrangements as the following listing shows: } } ---- +====== Note that there are special considerations for the returned `Publisher` with regards to Reactive Streams cancellation signals. See the xref:data-access/transaction/programmatic.adoc#tx-prog-operator-cancel[Cancel Signals] section under @@ -322,8 +334,11 @@ annotated at the class level with the settings for a read-only transaction, but `@Transactional` annotation on the `updateFoo(Foo)` method in the same class takes precedence over the transactional settings defined at the class level. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Transactional(readOnly = true) public class DefaultFooService implements FooService { @@ -339,8 +354,10 @@ precedence over the transactional settings defined at the class level. } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Transactional(readOnly = true) class DefaultFooService : FooService { @@ -356,6 +373,7 @@ precedence over the transactional settings defined at the class level. } } ---- +====== [[transaction-declarative-attransactional-settings]] @@ -455,8 +473,11 @@ of the transaction manager bean. For example, using the qualifier notation, you combine the following Java code with the following transaction manager bean declarations in the application context: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class TransactionalService { @@ -470,8 +491,10 @@ in the application context: public Mono doSomethingReactive() { ... } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- class TransactionalService { @@ -491,6 +514,7 @@ in the application context: } } ---- +====== The following listing shows the bean declarations: @@ -527,8 +551,11 @@ methods, xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[Spring's define custom composed annotations for your specific use cases. For example, consider the following annotation definitions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @@ -542,8 +569,10 @@ following annotation definitions: public @interface AccountTx { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) @Retention(AnnotationRetention.RUNTIME) @@ -555,11 +584,15 @@ following annotation definitions: @Transactional(transactionManager = "account", label = ["retryable"]) annotation class AccountTx ---- +====== The preceding annotations let us write the example from the previous section as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class TransactionalService { @@ -574,8 +607,10 @@ The preceding annotations let us write the example from the previous section as } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- class TransactionalService { @@ -590,6 +625,7 @@ The preceding annotations let us write the example from the previous section as } } ---- +====== In the preceding example, we used the syntax to define the transaction manager qualifier and transactional labels, but we could also have included propagation behavior, diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/applying-more-than-just-tx-advice.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/applying-more-than-just-tx-advice.adoc index 38d49bc4b09..bcd41b9ab7c 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/applying-more-than-just-tx-advice.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/applying-more-than-just-tx-advice.adoc @@ -18,8 +18,11 @@ configuration and AOP in general. The following code shows the simple profiling aspect discussed earlier: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package x.y; @@ -55,8 +58,10 @@ The following code shows the simple profiling aspect discussed earlier: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"] -.Kotlin ---- package x.y @@ -92,6 +97,7 @@ The following code shows the simple profiling aspect discussed earlier: } } ---- +====== The ordering of advice is controlled through the `Ordered` interface. For full details on advice ordering, see diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/aspectj.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/aspectj.adoc index 477d4b0e9e6..58a65cafff6 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/aspectj.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/aspectj.adoc @@ -20,8 +20,11 @@ xref:core/aop.adoc[AOP] respectively. The following example shows how to create a transaction manager and configure the `AnnotationTransactionAspect` to use it: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // construct an appropriate transaction manager DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource()); @@ -29,8 +32,10 @@ The following example shows how to create a transaction manager and configure th // configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // construct an appropriate transaction manager val txManager = DataSourceTransactionManager(getDataSource()) @@ -38,6 +43,7 @@ The following example shows how to create a transaction manager and configure th // configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods AnnotationTransactionAspect.aspectOf().transactionManager = txManager ---- +====== NOTE: When you use this aspect, you must annotate the implementation class (or the methods within that class or both), not the interface (if any) that the class implements. AspectJ diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/first-example.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/first-example.adoc index 5bbe41208f1..c84bd17412f 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/first-example.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/first-example.adoc @@ -10,8 +10,11 @@ transactions being created and then rolled back in response to the `UnsupportedOperationException` instance. The following listing shows the `FooService` interface: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- // the service interface that we want to make transactional @@ -29,8 +32,10 @@ interface: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- // the service interface that we want to make transactional @@ -47,11 +52,15 @@ interface: fun updateFoo(foo: Foo) } ---- +====== The following example shows an implementation of the preceding interface: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package x.y.service; @@ -78,8 +87,10 @@ The following example shows an implementation of the preceding interface: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package x.y.service @@ -102,6 +113,7 @@ The following example shows an implementation of the preceding interface: } } ---- +====== Assume that the first two methods of the `FooService` interface, `getFoo(String)` and `getFoo(String, String)`, must run in the context of a transaction with read-only @@ -215,8 +227,11 @@ a transaction is started, suspended, marked as read-only, and so on, depending o transaction configuration associated with that method. Consider the following program that test drives the configuration shown earlier: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public final class Boot { @@ -227,8 +242,10 @@ that test drives the configuration shown earlier: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -238,6 +255,7 @@ that test drives the configuration shown earlier: fooService.insertFoo(Foo()) } ---- +====== The output from running the preceding program should resemble the following (the Log4J output and the stack trace from the `UnsupportedOperationException` thrown by the @@ -281,8 +299,11 @@ return type is reactive. The following listing shows a modified version of the previously used `FooService`, but this time the code uses reactive types: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- // the reactive service interface that we want to make transactional @@ -300,8 +321,10 @@ this time the code uses reactive types: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- // the reactive service interface that we want to make transactional @@ -318,11 +341,15 @@ this time the code uses reactive types: fun updateFoo(foo: Foo) : Mono } ---- +====== The following example shows an implementation of the preceding interface: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"] -.Java ---- package x.y.service; @@ -349,8 +376,10 @@ The following example shows an implementation of the preceding interface: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"] -.Kotlin ---- package x.y.service @@ -373,6 +402,7 @@ The following example shows an implementation of the preceding interface: } } ---- +====== Imperative and reactive transaction management share the same semantics for transaction boundary and transaction attribute definitions. The main difference between imperative diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc index 1145aabb86d..b002be989d2 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc @@ -26,8 +26,11 @@ automatically rolled back in case of a failure. For more information on Vavr's T refer to the [official Vavr documentation](https://www.vavr.io/vavr-docs/#_try). Here's an example of how to use Vavr's Try with a transactional method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Transactional public Try myTransactionalMethod() { @@ -37,6 +40,7 @@ Here's an example of how to use Vavr's Try with a transactional method: return Try.of(delegate::myDataAccessOperation); } ---- +====== Checked exceptions that are thrown from a transactional method do not result in a rollback in the default configuration. You can configure exactly which `Exception` types mark a @@ -138,8 +142,11 @@ is quite invasive and tightly couples your code to the Spring Framework's transa infrastructure. The following example shows how to programmatically indicate a required rollback: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public void resolvePosition() { try { @@ -150,8 +157,10 @@ rollback: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun resolvePosition() { try { @@ -162,6 +171,7 @@ rollback: } } ---- +====== You are strongly encouraged to use the declarative approach to rollback, if at all possible. Programmatic rollback is available should you absolutely need it, but its diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/event.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/event.adoc index 7bdbad98b33..b4edfd8c4e2 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/event.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/event.adoc @@ -15,8 +15,11 @@ event and that we want to define a listener that should only handle that event o transaction in which it has been published has committed successfully. The following example sets up such an event listener: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MyComponent { @@ -27,8 +30,10 @@ example sets up such an event listener: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MyComponent { @@ -39,6 +44,7 @@ example sets up such an event listener: } } ---- +====== The `@TransactionalEventListener` annotation exposes a `phase` attribute that lets you customize the phase of the transaction to which the listener should be bound. diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/programmatic.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/programmatic.adoc index 4b211aff981..6c4bbb7021f 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/programmatic.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/programmatic.adoc @@ -33,8 +33,11 @@ anonymous inner class) that contains the code that you need to run in the contex a transaction. You can then pass an instance of your custom `TransactionCallback` to the `execute(..)` method exposed on the `TransactionTemplate`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleService implements Service { @@ -57,8 +60,10 @@ a transaction. You can then pass an instance of your custom `TransactionCallback } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // use constructor-injection to supply the PlatformTransactionManager class SimpleService(transactionManager: PlatformTransactionManager) : Service { @@ -72,13 +77,17 @@ a transaction. You can then pass an instance of your custom `TransactionCallback } } ---- +====== If there is no return value, you can use the convenient `TransactionCallbackWithoutResult` class with an anonymous class, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { @@ -87,8 +96,10 @@ with an anonymous class, as follows: } }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- transactionTemplate.execute(object : TransactionCallbackWithoutResult() { override fun doInTransactionWithoutResult(status: TransactionStatus) { @@ -97,13 +108,17 @@ with an anonymous class, as follows: } }) ---- +====== Code within the callback can roll the transaction back by calling the `setRollbackOnly()` method on the supplied `TransactionStatus` object, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- transactionTemplate.execute(new TransactionCallbackWithoutResult() { @@ -117,8 +132,10 @@ Code within the callback can roll the transaction back by calling the } }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- transactionTemplate.execute(object : TransactionCallbackWithoutResult() { @@ -132,6 +149,7 @@ Code within the callback can roll the transaction back by calling the } }) ---- +====== [[tx-prog-template-settings]] === Specifying Transaction Settings @@ -143,8 +161,11 @@ xref:data-access/transaction/declarative/txadvice-settings.adoc[default transact following example shows the programmatic customization of the transactional settings for a specific `TransactionTemplate:` +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleService implements Service { @@ -160,8 +181,10 @@ a specific `TransactionTemplate:` } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleService(transactionManager: PlatformTransactionManager) : Service { @@ -173,6 +196,7 @@ a specific `TransactionTemplate:` } } ---- +====== The following example defines a `TransactionTemplate` with some custom transactional settings by using Spring XML configuration: @@ -212,8 +236,11 @@ to make yourself. Application code that must run in a transactional context and that explicitly uses the `TransactionalOperator` resembles the next example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleService implements Service { @@ -235,8 +262,10 @@ the `TransactionalOperator` resembles the next example: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // use constructor-injection to supply the ReactiveTransactionManager class SimpleService(transactionManager: ReactiveTransactionManager) : Service { @@ -250,6 +279,7 @@ the `TransactionalOperator` resembles the next example: } } ---- +====== `TransactionalOperator` can be used in two ways: @@ -259,8 +289,11 @@ the `TransactionalOperator` resembles the next example: Code within the callback can roll the transaction back by calling the `setRollbackOnly()` method on the supplied `ReactiveTransaction` object, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- transactionalOperator.execute(new TransactionCallback<>() { @@ -271,8 +304,10 @@ method on the supplied `ReactiveTransaction` object, as follows: } }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- transactionalOperator.execute(object : TransactionCallback() { @@ -282,6 +317,7 @@ method on the supplied `ReactiveTransaction` object, as follows: } }) ---- +====== [[tx-prog-operator-cancel]] === Cancel Signals @@ -306,8 +342,11 @@ xref:data-access/transaction/declarative/txadvice-settings.adoc[default transact following example shows customization of the transactional settings for a specific `TransactionalOperator:` +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SimpleService implements Service { @@ -325,8 +364,10 @@ following example shows customization of the transactional settings for a specif } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SimpleService(transactionManager: ReactiveTransactionManager) : Service { @@ -339,6 +380,7 @@ following example shows customization of the transactional settings for a specif private val transactionalOperator = TransactionalOperator(transactionManager, definition) } ---- +====== [[transaction-programmatic-tm]] == Using the `TransactionManager` @@ -356,8 +398,11 @@ use to your bean through a bean reference. Then, by using the `TransactionDefini `TransactionStatus` objects, you can initiate transactions, roll back, and commit. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // explicitly setting the transaction name is something that can be done only programmatically @@ -373,8 +418,10 @@ following example shows how to do so: } txManager.commit(status); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val def = DefaultTransactionDefinition() // explicitly setting the transaction name is something that can be done only programmatically @@ -391,6 +438,7 @@ following example shows how to do so: txManager.commit(status) ---- +====== [[transaction-programmatic-rtm]] @@ -403,8 +451,11 @@ use to your bean through a bean reference. Then, by using the `TransactionDefini `ReactiveTransaction` objects, you can initiate transactions, roll back, and commit. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // explicitly setting the transaction name is something that can be done only programmatically @@ -421,8 +472,10 @@ following example shows how to do so: .onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex))); }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val def = DefaultTransactionDefinition() // explicitly setting the transaction name is something that can be done only programmatically @@ -438,5 +491,6 @@ following example shows how to do so: .onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/languages/kotlin/bean-definition-dsl.adoc b/framework-docs/modules/ROOT/pages/languages/kotlin/bean-definition-dsl.adoc index 5309da8d921..68949a9bf0b 100644 --- a/framework-docs/modules/ROOT/pages/languages/kotlin/bean-definition-dsl.adoc +++ b/framework-docs/modules/ROOT/pages/languages/kotlin/bean-definition-dsl.adoc @@ -38,12 +38,10 @@ you can instead write the following: registerBean { Bar(it.getBean()) } } ---- -==== When the class `Bar` has a single constructor, you can even just specify the bean class, the constructor parameters will be autowired by type: -==== [source,kotlin,indent=0] ---- val context = GenericApplicationContext().apply { diff --git a/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc b/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc index 721eda14c8a..021d436a37d 100644 --- a/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc +++ b/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc @@ -256,7 +256,6 @@ in order to use `val` instead of `lateinit var`. You can use {api-spring-framework}/test/context/TestConstructor.html[`@TestConstructor(autowireMode = AutowireMode.ALL)`] to enable autowiring for all parameters. -==== [source,kotlin,indent=0] ---- @SpringJUnitConfig(TestConfig::class) @@ -267,7 +266,6 @@ class OrderServiceIntegrationTests(val orderService: OrderService, // tests that use the injected OrderService and CustomerService } ---- -==== [[per_class-lifecycle]] diff --git a/framework-docs/modules/ROOT/pages/rsocket.adoc b/framework-docs/modules/ROOT/pages/rsocket.adoc index 0672161ec6c..13777f8f9f2 100644 --- a/framework-docs/modules/ROOT/pages/rsocket.adoc +++ b/framework-docs/modules/ROOT/pages/rsocket.adoc @@ -180,8 +180,11 @@ settings for the `SETUP` frame. This is the most basic way to connect with default settings: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RSocketRequester requester = RSocketRequester.builder().tcp("localhost", 7000); @@ -189,14 +192,16 @@ This is the most basic way to connect with default settings: RSocketRequester requester = RSocketRequester.builder().webSocket(url); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val requester = RSocketRequester.builder().tcp("localhost", 7000) URI url = URI.create("https://example.org:8080/rsocket"); val requester = RSocketRequester.builder().webSocket(url) ---- +====== The above does not connect immediately. When requests are made, a shared connection is established transparently and used. @@ -233,8 +238,11 @@ metadata values. By default only the basic codecs from `spring-core` for `String `byte[]`, and `ByteBuffer` are registered. Adding `spring-web` provides access to more that can be registered as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RSocketStrategies strategies = RSocketStrategies.builder() .encoders(encoders -> encoders.add(new Jackson2CborEncoder())) @@ -246,8 +254,9 @@ can be registered as follows: .tcp("localhost", 7000); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val strategies = RSocketStrategies.builder() .encoders { it.add(Jackson2CborEncoder()) } @@ -258,6 +267,7 @@ can be registered as follows: .rsocketStrategies(strategies) .tcp("localhost", 7000) ---- +====== `RSocketStrategies` is designed for re-use. In some scenarios, e.g. client and server in the same application, it may be preferable to declare it in Spring configuration. @@ -272,8 +282,11 @@ server. You can use annotated handlers for client-side responding based on the same infrastructure that's used on a server, but registered programmatically as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RSocketStrategies strategies = RSocketStrategies.builder() .routeMatcher(new PathPatternRouteMatcher()) // <1> @@ -286,6 +299,7 @@ infrastructure that's used on a server, but registered programmatically as follo .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. @@ -314,8 +328,11 @@ Note the above is only a shortcut designed for programmatic registration of clie responders. For alternative scenarios, where client responders are in Spring configuration, you can still declare `RSocketMessageHandler` as a Spring bean and then apply as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext context = ... ; RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class); @@ -325,8 +342,9 @@ you can still declare `RSocketMessageHandler` as a Spring bean and then apply as .tcp("localhost", 7000); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -337,6 +355,7 @@ you can still declare `RSocketMessageHandler` as a Spring bean and then apply as .rsocketConnector { it.acceptor(handler.responder()) } .tcp("localhost", 7000) ---- +====== For the above you may also need to use `setHandlerPredicate` in `RSocketMessageHandler` to switch to a different strategy for detecting client responders, e.g. based on a custom @@ -355,8 +374,11 @@ See also xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders], for m intervals, session resumption, interceptors, and more. You can configure options at that level as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RSocketRequester requester = RSocketRequester.builder() .rsocketConnector(connector -> { @@ -365,8 +387,9 @@ at that level as follows: .tcp("localhost", 7000); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val requester = RSocketRequester.builder() .rsocketConnector { @@ -374,6 +397,7 @@ at that level as follows: } .tcp("localhost", 7000) ---- +====== [[rsocket-requester-server]] @@ -388,8 +412,11 @@ mind that `@ConnectMapping` methods are essentially handlers of the `SETUP` fram must be handled before requests can begin. Therefore, requests at the very start must be decoupled from handling. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ConnectMapping Mono handle(RSocketRequester requester) { @@ -401,6 +428,7 @@ decoupled from handling. For example: return ... // <2> } ---- +====== <1> Start the request asynchronously, independent from handling. <2> Perform handling and return completion `Mono`. @@ -428,8 +456,11 @@ decoupled from handling. For example: Once you have a xref:rsocket.adoc#rsocket-requester-client[client] or xref:rsocket.adoc#rsocket-requester-server[server] requester, you can make requests as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ViewBox viewBox = ... ; @@ -438,6 +469,7 @@ xref:rsocket.adoc#rsocket-requester-server[server] requester, you can make reque .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. @@ -475,27 +507,36 @@ data(Object producer, ParameterizedTypeReference elementTypeRef); The `data(Object)` step is optional. Skip it for requests that don't send data: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono location = requester.route("find.radar.EWR")) .retrieveMono(AirportLocation.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.messaging.rsocket.retrieveAndAwait val location = requester.route("find.radar.EWR") .retrieveAndAwait() ---- +====== Extra metadata values can be added if using {gh-rsocket-extensions}/CompositeMetadata.md[composite metadata] (the default) and if the values are supported by a registered `Encoder`. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String securityToken = ... ; ViewBox viewBox = ... ; @@ -506,8 +547,10 @@ values are supported by a registered `Encoder`. For example: .data(viewBox) .retrieveFlux(AirportLocation.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.messaging.rsocket.retrieveFlow @@ -522,6 +565,7 @@ values are supported by a registered `Encoder`. For example: .data(viewBox) .retrieveFlow() ---- +====== For `Fire-and-Forget` use the `send()` method that returns `Mono`. Note that the `Mono` indicates only that the message was successfully sent, and not that it was handled. @@ -547,8 +591,11 @@ To use annotated responders on the server side, add `RSocketMessageHandler` to y configuration to detect `@Controller` beans with `@MessageMapping` and `@ConnectMapping` methods: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration static class ServerConfig { @@ -561,8 +608,10 @@ methods: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class ServerConfig { @@ -573,12 +622,16 @@ methods: } } ---- +====== Then start an RSocket server through the Java RSocket API and plug the `RSocketMessageHandler` for the responder as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext context = ... ; RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class); @@ -588,8 +641,10 @@ Then start an RSocket server through the Java RSocket API and plug the .bind(TcpServerTransport.create("localhost", 7000)) .block(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.beans.factory.getBean @@ -600,6 +655,7 @@ Then start an RSocket server through the Java RSocket API and plug the .bind(TcpServerTransport.create("localhost", 7000)) .awaitSingle() ---- +====== `RSocketMessageHandler` supports {gh-rsocket-extensions}/CompositeMetadata.md[composite] and @@ -619,8 +675,11 @@ decoding as with HTTP URLs. `RSocketMessageHandler` can be configured via `RSocketStrategies` which may be useful if you need to share configuration between a client and a server in the same process: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration static class ServerConfig { @@ -642,8 +701,10 @@ you need to share configuration between a client and a server in the same proces } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class ServerConfig { @@ -661,6 +722,7 @@ you need to share configuration between a client and a server in the same proces .build() } ---- +====== @@ -680,8 +742,11 @@ Once xref:rsocket.adoc#rsocket-annot-responders-server[server] or xref:rsocket.adoc#rsocket-annot-responders-client[client] responder configuration is in place, `@MessageMapping` methods can be used as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class RadarsController { @@ -692,8 +757,10 @@ xref:rsocket.adoc#rsocket-annot-responders-client[client] responder configuratio } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class RadarsController { @@ -704,6 +771,7 @@ class RadarsController { } } ---- +====== The above `@MessageMapping` method responds to a Request-Stream interaction having the route "locate.radars.within". It supports a flexible method signature with the option to @@ -833,28 +901,37 @@ the box it has built-in support for `String` and saves under the "route" key. For any other mime type you'll need to provide a `Decoder` and register the mime type as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders); extractor.metadataToExtract(fooMimeType, Foo.class, "foo"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.messaging.rsocket.metadataToExtract val extractor = DefaultMetadataExtractor(metadataDecoders) extractor.metadataToExtract(fooMimeType, "foo") ---- +====== Composite metadata works well to combine independent metadata values. However the requester might not support composite metadata, or may choose not to use it. For this, `DefaultMetadataExtractor` may needs custom logic to map the decoded value to the output map. Here is an example where JSON is used for metadata: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders); extractor.metadataToExtract( @@ -864,8 +941,10 @@ map. Here is an example where JSON is used for metadata: outputMap.putAll(jsonMap); }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.messaging.rsocket.metadataToExtract @@ -874,13 +953,17 @@ map. Here is an example where JSON is used for metadata: outputMap.putAll(jsonMap) } ---- +====== When configuring `MetadataExtractor` through `RSocketStrategies`, you can let `RSocketStrategies.Builder` create the extractor with the configured decoders, and simply use a callback to customize registrations as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RSocketStrategies strategies = RSocketStrategies.builder() .metadataExtractorRegistry(registry -> { @@ -889,8 +972,10 @@ simply use a callback to customize registrations as follows: }) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.messaging.rsocket.metadataToExtract @@ -901,6 +986,7 @@ simply use a callback to customize registrations as follows: } .build() ---- +====== diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc index aaa87fca2ac..98f394130e5 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc @@ -25,14 +25,18 @@ classes may be declared with the `value` attribute in `@SpringJUnitConfig`. The following example shows how to use the `@SpringJUnitConfig` annotation to specify a configuration class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) // <1> class ConfigurationClassJUnitJupiterSpringTests { // class body... } ---- +====== <1> Specify the configuration class. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -49,14 +53,18 @@ configuration class: The following example shows how to use the `@SpringJUnitConfig` annotation to specify the location of a configuration file: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(locations = "/test-config.xml") // <1> class XmlJUnitJupiterSpringTests { // class body... } ---- +====== <1> Specify the location of a configuration file. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -90,14 +98,18 @@ attribute from `@WebAppConfiguration` only by using the `resourcePath` attribute The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify a configuration class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig(TestConfig.class) // <1> class ConfigurationClassJUnitJupiterSpringWebTests { // class body... } ---- +====== <1> Specify the configuration class. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -114,14 +126,18 @@ a configuration class: The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify the location of a configuration file: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig(locations = "/test-config.xml") // <1> class XmlJUnitJupiterSpringWebTests { // class body... } ---- +====== <1> Specify the location of a configuration file. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -248,8 +264,11 @@ equivalent to `@Disabled` and `@EnabledIf("true")` is logically meaningless. You can use `@EnabledIf` as a meta-annotation to create custom composed annotations. For example, you can create a custom `@EnabledOnMac` annotation as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -259,8 +278,10 @@ example, you can create a custom `@EnabledOnMac` annotation as follows: ) public @interface EnabledOnMac {} ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @@ -270,6 +291,7 @@ example, you can create a custom `@EnabledOnMac` annotation as follows: ) annotation class EnabledOnMac {} ---- +====== [NOTE] ==== @@ -308,8 +330,11 @@ equivalent to `@Disabled` and `@DisabledIf("false")` is logically meaningless. You can use `@DisabledIf` as a meta-annotation to create custom composed annotations. For example, you can create a custom `@DisabledOnMac` annotation as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -320,8 +345,9 @@ example, you can create a custom `@DisabledOnMac` annotation as follows: public @interface DisabledOnMac {} ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @@ -331,6 +357,7 @@ example, you can create a custom `@DisabledOnMac` annotation as follows: ) annotation class DisabledOnMac {} ---- +====== [NOTE] ==== diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc index 924a17f81c7..d228a83c185 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit4.adoc @@ -27,8 +27,11 @@ means the test is implicitly enabled. This is analogous to the semantics of JUni The following example shows a test that has an `@IfProfileValue` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1> @Test @@ -36,6 +39,7 @@ The following example shows a test that has an `@IfProfileValue` annotation: // some logic that should run only on Java VMs from Oracle Corporation } ---- +====== <1> Run this test only when the Java vendor is "Oracle Corporation". [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -54,8 +58,11 @@ Alternatively, you can configure `@IfProfileValue` with a list of `values` (with semantics) to achieve TestNG-like support for test groups in a JUnit 4 environment. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) // <1> @Test @@ -63,6 +70,7 @@ Consider the following example: // some logic that should run only for unit and integration test groups } ---- +====== <1> Run this test for unit tests and integration tests. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -86,14 +94,18 @@ of `ProfileValueSource` to use when retrieving profile values configured through test, `SystemProfileValueSource` is used by default. The following example shows how to use `@ProfileValueSourceConfiguration`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ProfileValueSourceConfiguration(CustomProfileValueSource.class) // <1> public class CustomProfileValueSourceTests { // class body... } ---- +====== <1> Use a custom profile value source. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -118,14 +130,18 @@ The time period includes running the test method itself, any repetitions of the `@Repeat`), as well as any setting up or tearing down of the test fixture. The following example shows how to use it: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Timed(millis = 1000) // <1> public void testProcessWithOneSecondTimeout() { // some logic that should not take longer than 1 second to run } ---- +====== <1> Set the time period for the test to one second. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -158,8 +174,11 @@ xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules preparation of the test instance by `TestExecutionListener` implementations. The following example shows how to use the `@Repeat` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Repeat(10) // <1> @Test @@ -167,6 +186,7 @@ following example shows how to use the `@Repeat` annotation: // ... } ---- +====== <1> Repeat this test ten times. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-meta.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-meta.adoc index 66ee7b11232..1e992c75e86 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-meta.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-meta.adoc @@ -38,8 +38,11 @@ xref:testing/testcontext-framework.adoc[TestContext framework]. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RunWith(SpringRunner.class) @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) @@ -54,8 +57,9 @@ Consider the following example: public class UserRepositoryTests { } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RunWith(SpringRunner::class) @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") @@ -69,13 +73,17 @@ Consider the following example: @Transactional class UserRepositoryTests { } ---- +====== If we discover that we are repeating the preceding configuration across our JUnit 4-based test suite, we can reduce the duplication by introducing a custom composed annotation that centralizes the common test configuration for Spring, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -85,8 +93,9 @@ that centralizes the common test configuration for Spring, as follows: public @interface TransactionalDevTestConfig { } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.TYPE) @Retention(AnnotationRetention.RUNTIME) @@ -95,12 +104,16 @@ that centralizes the common test configuration for Spring, as follows: @Transactional annotation class TransactionalDevTestConfig { } ---- +====== Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the configuration of individual JUnit 4 based test classes, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RunWith(SpringRunner.class) @TransactionalDevTestConfig @@ -111,8 +124,9 @@ configuration of individual JUnit 4 based test classes, as follows: public class UserRepositoryTests { } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RunWith(SpringRunner::class) @TransactionalDevTestConfig @@ -122,13 +136,17 @@ configuration of individual JUnit 4 based test classes, as follows: @TransactionalDevTestConfig class UserRepositoryTests ---- +====== If we write tests that use JUnit Jupiter, we can reduce code duplication even further, since annotations in JUnit 5 can also be used as meta-annotations. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) @@ -142,8 +160,10 @@ example: @Transactional class UserRepositoryTests { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") @@ -157,14 +177,18 @@ example: @Transactional class UserRepositoryTests { } ---- +====== If we discover that we are repeating the preceding configuration across our JUnit Jupiter-based test suite, we can reduce the duplication by introducing a custom composed annotation that centralizes the common test configuration for Spring and JUnit Jupiter, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -174,8 +198,10 @@ as follows: @Transactional public @interface TransactionalDevTestConfig { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.TYPE) @Retention(AnnotationRetention.RUNTIME) @@ -185,12 +211,16 @@ as follows: @Transactional annotation class TransactionalDevTestConfig { } ---- +====== Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the configuration of individual JUnit Jupiter based test classes, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @TransactionalDevTestConfig class OrderRepositoryTests { } @@ -198,8 +228,10 @@ configuration of individual JUnit Jupiter based test classes, as follows: @TransactionalDevTestConfig class UserRepositoryTests { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @TransactionalDevTestConfig class OrderRepositoryTests { } @@ -207,6 +239,7 @@ configuration of individual JUnit Jupiter based test classes, as follows: @TransactionalDevTestConfig class UserRepositoryTests { } ---- +====== Since JUnit Jupiter supports the use of `@Test`, `@RepeatedTest`, `ParameterizedTest`, and others as meta-annotations, you can also create custom composed annotations at the @@ -215,8 +248,11 @@ the `@Test` and `@Tag` annotations from JUnit Jupiter with the `@Transactional` annotation from Spring, we could create an `@TransactionalIntegrationTest` annotation, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -225,8 +261,10 @@ follows: @Test // org.junit.jupiter.api.Test public @interface TransactionalIntegrationTest { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Target(AnnotationTarget.TYPE) @Retention(AnnotationRetention.RUNTIME) @@ -235,12 +273,16 @@ follows: @Test // org.junit.jupiter.api.Test annotation class TransactionalIntegrationTest { } ---- +====== Then we can use our custom `@TransactionalIntegrationTest` annotation to simplify the configuration of individual JUnit Jupiter based test methods, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @TransactionalIntegrationTest void saveOrder() { } @@ -249,8 +291,9 @@ configuration of individual JUnit Jupiter based test methods, as follows: void deleteOrder() { } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @TransactionalIntegrationTest fun saveOrder() { } @@ -258,6 +301,7 @@ configuration of individual JUnit Jupiter based test methods, as follows: @TransactionalIntegrationTest fun deleteOrder() { } ---- +====== For further details, see the https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-activeprofiles.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-activeprofiles.adoc index 7ff17ec5425..71217d5b79c 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-activeprofiles.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-activeprofiles.adoc @@ -7,8 +7,11 @@ integration test. The following example indicates that the `dev` profile should be active: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @ActiveProfiles("dev") // <1> @@ -16,6 +19,7 @@ The following example indicates that the `dev` profile should be active: // class body... } ---- +====== <1> Indicate that the `dev` profile should be active. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -33,8 +37,11 @@ The following example indicates that the `dev` profile should be active: The following example indicates that both the `dev` and the `integration` profiles should be active: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @ActiveProfiles({"dev", "integration"}) // <1> @@ -42,6 +49,7 @@ be active: // class body... } ---- +====== <1> Indicate that the `dev` and `integration` profiles should be active. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-aftertransaction.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-aftertransaction.adoc index d54a7343fe2..680c554da08 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-aftertransaction.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-aftertransaction.adoc @@ -7,14 +7,18 @@ transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` m are not required to be `public` and may be declared on Java 8-based interface default methods. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @AfterTransaction // <1> void afterTransaction() { // logic to be run after a transaction has ended } ---- +====== <1> Run this method after a transaction. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beforetransaction.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beforetransaction.adoc index 943451ef2a8..3723945221e 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beforetransaction.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beforetransaction.adoc @@ -9,14 +9,18 @@ methods. The following example shows how to use the `@BeforeTransaction` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @BeforeTransaction // <1> void beforeTransaction() { // logic to be run before a transaction is started } ---- +====== <1> Run this method before a transaction. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-commit.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-commit.adoc index 7702fcfa78b..86278154703 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-commit.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-commit.adoc @@ -9,8 +9,11 @@ annotation. The following example shows how to use the `@Commit` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Commit // <1> @Test @@ -18,6 +21,7 @@ The following example shows how to use the `@Commit` annotation: // ... } ---- +====== <1> Commit the result of the test to the database. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contextconfiguration.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contextconfiguration.adoc index 8d783ac240c..e5e8d853e99 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contextconfiguration.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contextconfiguration.adoc @@ -15,14 +15,18 @@ xref:testing/testcontext-framework/ctx-management/javaconfig.adoc#testcontext-ct The following example shows a `@ContextConfiguration` annotation that refers to an XML file: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration("/test-config.xml") // <1> class XmlApplicationContextTests { // class body... } ---- +====== <1> Referring to an XML file. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -38,14 +42,18 @@ file: The following example shows a `@ContextConfiguration` annotation that refers to a class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration(classes = TestConfig.class) // <1> class ConfigClassApplicationContextTests { // class body... } ---- +====== <1> Referring to a class. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -63,14 +71,18 @@ As an alternative or in addition to declaring resource locations or component cl you can use `@ContextConfiguration` to declare `ApplicationContextInitializer` classes. The following example shows such a case: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration(initializers = CustomContextInitializer.class) // <1> class ContextInitializerTests { // class body... } ---- +====== <1> Declaring an initializer class. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -91,14 +103,18 @@ component `classes`. The following example uses both a location and a loader: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) // <1> class CustomLoaderXmlApplicationContextTests { // class body... } ---- +====== <1> Configuring both a location and a custom loader. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contexthierarchy.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contexthierarchy.adoc index 89bf3190589..f136a4da5c9 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contexthierarchy.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contexthierarchy.adoc @@ -8,8 +8,11 @@ defines a level in the context hierarchy. The following examples demonstrate the `@ContextHierarchy` within a single test class (`@ContextHierarchy` can also be used within a test class hierarchy): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextHierarchy({ @ContextConfiguration("/parent-config.xml"), @@ -19,8 +22,10 @@ within a test class hierarchy): // class body... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ContextHierarchy( ContextConfiguration("/parent-config.xml"), @@ -29,9 +34,13 @@ within a test class hierarchy): // class body... } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @WebAppConfiguration @ContextHierarchy({ @@ -42,8 +51,10 @@ within a test class hierarchy): // class body... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @WebAppConfiguration @ContextHierarchy( @@ -53,6 +64,7 @@ within a test class hierarchy): // class body... } ---- +====== If you need to merge or override the configuration for a given level of the context hierarchy within a test class hierarchy, you must explicitly name that level by supplying diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dirtiescontext.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dirtiescontext.adoc index 9f6103a1c60..2ccca2f0b08 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dirtiescontext.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dirtiescontext.adoc @@ -20,14 +20,18 @@ configuration scenarios: * Before the current test class, when declared on a class with class mode set to `BEFORE_CLASS`. + +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @DirtiesContext(classMode = BEFORE_CLASS) // <1> class FreshContextTests { // some tests that require a new Spring container } ---- +====== <1> Dirty the context before the current test class. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -43,14 +47,18 @@ configuration scenarios: * After the current test class, when declared on a class with class mode set to `AFTER_CLASS` (i.e., the default class mode). + +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @DirtiesContext // <1> class ContextDirtyingTests { // some tests that result in the Spring container being dirtied } ---- +====== <1> Dirty the context after the current test class. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -67,14 +75,18 @@ configuration scenarios: * Before each test method in the current test class, when declared on a class with class mode set to `BEFORE_EACH_TEST_METHOD.` + +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1> class FreshContextTests { // some tests that require a new Spring container } ---- +====== <1> Dirty the context before each test method. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -91,14 +103,18 @@ mode set to `BEFORE_EACH_TEST_METHOD.` * After each test method in the current test class, when declared on a class with class mode set to `AFTER_EACH_TEST_METHOD.` + +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1> class ContextDirtyingTests { // some tests that result in the Spring container being dirtied } ---- +====== <1> Dirty the context after each test method. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -115,8 +131,11 @@ mode set to `AFTER_EACH_TEST_METHOD.` * Before the current test, when declared on a method with the method mode set to `BEFORE_METHOD`. + +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @DirtiesContext(methodMode = BEFORE_METHOD) // <1> @Test @@ -124,6 +143,7 @@ mode set to `AFTER_EACH_TEST_METHOD.` // some logic that requires a new Spring container } ---- +====== <1> Dirty the context before the current test method. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -140,8 +160,11 @@ mode set to `AFTER_EACH_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). + +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @DirtiesContext // <1> @Test @@ -149,6 +172,7 @@ mode set to `AFTER_EACH_TEST_METHOD.` // some logic that results in the Spring container being dirtied } ---- +====== <1> Dirty the context after the current test method. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -173,8 +197,11 @@ context are removed from the context cache and closed. If the exhaustive algorit overkill for a particular use case, you can specify the simpler current level algorithm, as the following example shows. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextHierarchy({ @ContextConfiguration("/parent-config.xml"), @@ -193,6 +220,7 @@ as the following example shows. } } ---- +====== <1> Use the current-level algorithm. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc index 21bafb5324d..94edb68c367 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc @@ -10,8 +10,11 @@ https://www.testcontainers.org/[Testcontainers] project. The following example demonstrates how to register a dynamic property: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration class MyIntegrationTests { @@ -26,6 +29,7 @@ The following example demonstrates how to register a dynamic property: // 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. diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-rollback.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-rollback.adoc index d5eb93ed280..1f2d84cf93e 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-rollback.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-rollback.adoc @@ -15,8 +15,11 @@ method, potentially overriding class-level `@Rollback` or `@Commit` semantics. The following example causes a test method's result to not be rolled back (that is, the result is committed to the database): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Rollback(false) // <1> @Test @@ -24,6 +27,7 @@ result is committed to the database): // ... } ---- +====== <1> Do not roll back the result. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sql.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sql.adoc index 975e043e868..982838d8b88 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sql.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sql.adoc @@ -5,8 +5,11 @@ against a given database during integration tests. The following example shows how to use it: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Test @Sql({"/test-schema.sql", "/test-user-data.sql"}) // <1> @@ -14,6 +17,7 @@ it: // run code that relies on the test schema and test data } ---- +====== <1> Run two scripts for this test. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlconfig.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlconfig.adoc index 98c961544ef..128d6794f97 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlconfig.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlconfig.adoc @@ -4,8 +4,11 @@ `@SqlConfig` defines metadata that is used to determine how to parse and run SQL scripts configured with the `@Sql` annotation. The following example shows how to use it: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Test @Sql( @@ -16,6 +19,7 @@ configured with the `@Sql` annotation. The following example shows how to use it // run code that relies on the test data } ---- +====== <1> Set the comment prefix and the separator in SQL scripts. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlgroup.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlgroup.adoc index e7505085359..564645d30b3 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlgroup.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlgroup.adoc @@ -7,8 +7,11 @@ in conjunction with Java 8's support for repeatable annotations, where `@Sql` ca declared several times on the same class or method, implicitly generating this container annotation. The following example shows how to declare an SQL group: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Test @SqlGroup({ // <1> @@ -19,6 +22,7 @@ annotation. The following example shows how to declare an SQL group: // run code that uses the test schema and test data } ---- +====== <1> Declare a group of SQL scripts. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlmergemode.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlmergemode.adoc index cd594989144..8e101fcc083 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlmergemode.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-sqlmergemode.adoc @@ -11,8 +11,11 @@ Note that a method-level `@SqlMergeMode` declaration overrides a class-level dec The following example shows how to use `@SqlMergeMode` at the class level. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) @Sql("/test-schema.sql") @@ -26,6 +29,7 @@ The following example shows how to use `@SqlMergeMode` at the class level. } } ---- +====== <1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -47,8 +51,11 @@ The following example shows how to use `@SqlMergeMode` at the class level. The following example shows how to use `@SqlMergeMode` at the method level. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) @Sql("/test-schema.sql") @@ -62,6 +69,7 @@ The following example shows how to use `@SqlMergeMode` at the method level. } } ---- +====== <1> Set the `@Sql` merge mode to `MERGE` for a specific test method. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc index e79aa245eca..998cb90a2fa 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc @@ -8,8 +8,11 @@ xref:testing/testcontext-framework/tel-config.adoc[`TestExecutionListener` Confi The following example shows how to register two `TestExecutionListener` implementations: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) // <1> @@ -17,6 +20,7 @@ The following example shows how to register two `TestExecutionListener` implemen // class body... } ---- +====== <1> Register two `TestExecutionListener` implementations. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testpropertysource.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testpropertysource.adoc index 40ba2240a39..97905b1dd4b 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testpropertysource.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testpropertysource.adoc @@ -8,8 +8,11 @@ integration test. The following example demonstrates how to declare a properties file from the classpath: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestPropertySource("/test.properties") // <1> @@ -17,6 +20,7 @@ The following example demonstrates how to declare a properties file from the cla // class body... } ---- +====== <1> Get properties from `test.properties` in the root of the classpath. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -33,8 +37,11 @@ The following example demonstrates how to declare a properties file from the cla The following example demonstrates how to declare inlined properties: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) // <1> @@ -42,6 +49,7 @@ The following example demonstrates how to declare inlined properties: // class body... } ---- +====== <1> Declare `timezone` and `port` properties. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-webappconfiguration.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-webappconfiguration.adoc index d81df81904d..568a722269d 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-webappconfiguration.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-webappconfiguration.adoc @@ -13,8 +13,11 @@ resource base path). The resource base path is used behind the scenes to create The following example shows how to use the `@WebAppConfiguration` annotation: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @WebAppConfiguration // <1> @@ -22,6 +25,7 @@ The following example shows how to use the `@WebAppConfiguration` annotation: // class body... } ---- +====== <1> The `@WebAppConfiguration` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -43,8 +47,11 @@ supported. If no resource prefix is supplied, the path is assumed to be a file s resource. The following example shows how to specify a classpath resource: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @WebAppConfiguration("classpath:test-web-resources") // <1> @@ -52,6 +59,7 @@ resource. The following example shows how to specify a classpath resource: // class body... } ---- +====== <1> Specifying a classpath resource. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-client.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-client.adoc index 1bbd496e264..a223a9f4ca3 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-client.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-client.adoc @@ -6,8 +6,11 @@ idea is to declare expected requests and to provide "`stub`" responses so that y focus on testing the code in isolation (that is, without running a server). The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RestTemplate restTemplate = new RestTemplate(); @@ -18,8 +21,10 @@ example shows how to do so: mockServer.verify(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val restTemplate = RestTemplate() @@ -30,6 +35,7 @@ example shows how to do so: mockServer.verify() ---- +====== In the preceding example, `MockRestServiceServer` (the central class for client-side REST tests) configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that @@ -45,24 +51,33 @@ can set the `ignoreExpectOrder` option when building the server, in which case a expectations are checked (in order) to find a match for a given request. That means requests are allowed to come in any order. The following example uses `ignoreExpectOrder`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build() ---- +====== Even with unordered requests by default, each request is allowed to run once only. The `expect` method provides an overloaded variant that accepts an `ExpectedCount` argument that specifies a count range (for example, `once`, `manyTimes`, `max`, `min`, `between`, and so on). The following example uses `times`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RestTemplate restTemplate = new RestTemplate(); @@ -74,8 +89,10 @@ argument that specifies a count range (for example, `once`, `manyTimes`, `max`, mockServer.verify(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val restTemplate = RestTemplate() @@ -87,6 +104,7 @@ argument that specifies a count range (for example, `once`, `manyTimes`, `max`, mockServer.verify() ---- +====== Note that, when `ignoreExpectOrder` is not set (the default), and, therefore, requests are expected in order of declaration, then that order applies only to the first of any @@ -100,29 +118,38 @@ As an alternative to all of the above, the client-side test support also provide bind it to a `MockMvc` instance. That allows processing requests using actual server-side logic but without running a server. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc)); // Test code that uses the above RestTemplate ... ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build() restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc)) // Test code that uses the above RestTemplate ... ---- +====== In some cases it may be necessary to perform an actual call to a remote service instead of mocking the response. The following example shows how to do that through `ExecutingResponseCreator`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RestTemplate restTemplate = new RestTemplate(); @@ -137,8 +164,10 @@ of mocking the response. The following example shows how to do that through mockServer.verify(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val restTemplate = RestTemplate() @@ -153,6 +182,7 @@ of mocking the response. The following example shows how to do that through mockServer.verify() ---- +====== In the preceding example, we create the `ExecutingResponseCreator` using the `ClientHttpRequestFactory` from the `RestTemplate` _before_ `MockRestServiceServer` replaces diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/async-requests.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/async-requests.adoc index f8e8cda9090..d8c17db3e94 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/async-requests.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/async-requests.adoc @@ -16,8 +16,11 @@ first, then manually performing the async dispatch, and finally verifying the re Below is an example test for controller methods that return `DeferredResult`, `Callable`, or reactive type such as Reactor `Mono`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* @@ -34,6 +37,7 @@ or reactive type such as Reactor `Mono`: .andExpect(content().string("body")); } ---- +====== <1> Check response status is still unchanged <2> Async processing must have started <3> Wait and assert the async result diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-defining-expectations.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-defining-expectations.adoc index a558611599d..e581e32b190 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-defining-expectations.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-defining-expectations.adoc @@ -5,16 +5,20 @@ You can define expectations by appending one or more `andExpect(..)` calls after performing a request, as the following example shows. As soon as one expectation fails, no other expectations will be asserted. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.get @@ -22,14 +26,18 @@ no other expectations will be asserted. status { isOk() } } ---- +====== You can define multiple expectations by appending `andExpectAll(..)` after performing a request, as the following example shows. In contrast to `andExpect(..)`, `andExpectAll(..)` guarantees that all supplied expectations will be asserted and that all failures will be tracked and reported. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* @@ -38,8 +46,9 @@ all failures will be tracked and reported. content().contentType("application/json;charset=UTF-8")); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.get @@ -48,6 +57,7 @@ all failures will be tracked and reported. content { contentType(APPLICATION_JSON) } } ---- +====== `MockMvcResultMatchers.*` provides a number of expectations, some of which are further nested with more detailed expectations. @@ -64,16 +74,20 @@ inspect Servlet specific aspects, such as request and session attributes. The following test asserts that binding or validation failed: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(post("/persons")) .andExpect(status().isOk()) .andExpect(model().attributeHasErrors("person")); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.post @@ -84,13 +98,17 @@ The following test asserts that binding or validation failed: } } ---- +====== Many times, when writing tests, it is useful to dump the results of the performed request. You can do so as follows, where `print()` is a static import from `MockMvcResultHandlers`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(post("/persons")) .andDo(print()) @@ -98,8 +116,9 @@ request. You can do so as follows, where `print()` is a static import from .andExpect(model().attributeHasErrors("person")); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.post @@ -112,6 +131,7 @@ request. You can do so as follows, where `print()` is a static import from } } ---- +====== As long as request processing does not cause an unhandled exception, the `print()` method prints all the available result data to `System.out`. There is also a `log()` method and @@ -126,36 +146,47 @@ In some cases, you may want to get direct access to the result and verify someth cannot be verified otherwise. This can be achieved by appending `.andReturn()` after all other expectations, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn(); // ... ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn() // ... ---- +====== If all tests repeat the same expectations, you can set up common expectations once when building the `MockMvc` instance, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- standaloneSetup(new SimpleController()) .alwaysExpect(status().isOk()) .alwaysExpect(content().contentType("application/json;charset=UTF-8")) .build() ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- +====== Note that common expectations are always applied and cannot be overridden without creating a separate `MockMvc` instance. @@ -164,15 +195,19 @@ When a JSON response content contains hypermedia links created with https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the resulting links by using JsonPath expressions, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people")); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- mockMvc.get("/people") { accept(MediaType.APPLICATION_JSON) @@ -182,21 +217,26 @@ resulting links by using JsonPath expressions, as the following example shows: } } ---- +====== When XML response content contains hypermedia links created with https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the resulting links by using XPath expressions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Map ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom"); mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML)) .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people")); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val ns = mapOf("ns" to "http://www.w3.org/2005/Atom") mockMvc.get("/handle") { @@ -207,4 +247,5 @@ resulting links by using XPath expressions: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-filters.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-filters.adoc index ad3c33b5192..7b379b88cde 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-filters.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-filters.adoc @@ -5,16 +5,22 @@ When setting up a `MockMvc` instance, you can register one or more Servlet `Filter` instances, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- +====== Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the last filter delegates to the `DispatcherServlet`. diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/mah.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/mah.adoc index 3809ffd3b55..145fbb6b809 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/mah.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/mah.adoc @@ -14,8 +14,11 @@ First, make sure that you have included a test dependency on We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the `MockMvcWebClientBuilder`, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient webClient; @@ -27,8 +30,9 @@ We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by usi } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- lateinit var webClient: WebClient @@ -39,6 +43,7 @@ We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by usi .build() } ---- +====== NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage, see xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`]. @@ -55,17 +60,22 @@ Now we can use HtmlUnit as we normally would but without the need to deploy our application to a Servlet container. For example, we can request the view to create a message with the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form"); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val createMsgFormPage = webClient.getPage("http://localhost/messages/form") ---- +====== NOTE: The default context path is `""`. Alternatively, we can specify the context path, as described in xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`]. @@ -73,8 +83,11 @@ as described in xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc# Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it to create a message, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm"); HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary"); @@ -84,8 +97,10 @@ to create a message, as the following example shows: HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit"); HtmlPage newMessagePage = submit.click(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val form = createMsgFormPage.getHtmlElementById("messageForm") val summaryInput = createMsgFormPage.getHtmlElementById("summary") @@ -95,12 +110,16 @@ to create a message, as the following example shows: val submit = form.getOneHtmlElementByAttribute("input", "type", "submit") val newMessagePage = submit.click() ---- +====== Finally, we can verify that a new message was created successfully. The following assertions use the https://assertj.github.io/doc/[AssertJ] library: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123"); String id = newMessagePage.getHtmlElementById("id").getTextContent(); @@ -110,8 +129,10 @@ assertions use the https://assertj.github.io/doc/[AssertJ] library: String text = newMessagePage.getHtmlElementById("text").getTextContent(); assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123") val id = newMessagePage.getHtmlElementById("id").getTextContent() @@ -121,6 +142,7 @@ assertions use the https://assertj.github.io/doc/[AssertJ] library: val text = newMessagePage.getHtmlElementById("text").getTextContent() assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!") ---- +====== The preceding code improves on our xref:testing/spring-mvc-test-framework/server-htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways. @@ -142,8 +164,11 @@ In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest w possible, by building a `WebClient` based on the `WebApplicationContext` loaded for us by the Spring TestContext Framework. This approach is repeated in the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient webClient; @@ -155,8 +180,9 @@ the Spring TestContext Framework. This approach is repeated in the following exa } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- lateinit var webClient: WebClient @@ -167,11 +193,15 @@ the Spring TestContext Framework. This approach is repeated in the following exa .build() } ---- +====== We can also specify additional configuration options, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient webClient; @@ -188,8 +218,10 @@ We can also specify additional configuration options, as the following example s .build(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- lateinit var webClient: WebClient @@ -206,12 +238,16 @@ We can also specify additional configuration options, as the following example s .build() } ---- +====== As an alternative, we can perform the exact same setup by configuring the `MockMvc` instance separately and supplying it to the `MockMvcWebClientBuilder`, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- MockMvc mockMvc = MockMvcBuilders .webAppContextSetup(context) @@ -228,11 +264,13 @@ instance separately and supplying it to the `MockMvcWebClientBuilder`, as follow .build(); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- +====== This is more verbose, but, by building the `WebClient` with a `MockMvc` instance, we have the full power of MockMvc at our fingertips. diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc index fbc2d34485f..79bb9e9fb82 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc @@ -26,25 +26,34 @@ afterwards. If one of the fields were named "`summary`", we might have something that resembles the following repeated in multiple places within our tests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); summaryInput.setValueAttribute(summary); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val summaryInput = currentPage.getHtmlElementById("summary") summaryInput.setValueAttribute(summary) ---- +====== So what happens if we change the `id` to `smmry`? Doing so would force us to update all of our tests to incorporate this change. This violates the DRY principle, so we should ideally extract this code into its own method, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) { setSummary(currentPage, summary); @@ -57,8 +66,9 @@ ideally extract this code into its own method, as follows: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{ setSummary(currentPage, summary); @@ -70,14 +80,18 @@ ideally extract this code into its own method, as follows: summaryInput.setValueAttribute(summary) } ---- +====== Doing so ensures that we do not have to update all of our tests if we change the UI. We might even take this a step further and place this logic within an `Object` that represents the `HtmlPage` we are currently on, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class CreateMessagePage { @@ -111,8 +125,10 @@ represents the `HtmlPage` we are currently on, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class CreateMessagePage(private val currentPage: HtmlPage) { @@ -139,6 +155,7 @@ represents the `HtmlPage` we are currently on, as the following example shows: } } ---- +====== Formerly, this pattern was known as the https://github.com/SeleniumHQ/selenium/wiki/PageObjects[Page Object Pattern]. While we @@ -154,8 +171,11 @@ includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver` We can easily create a Selenium WebDriver that integrates with MockMvc by using the `MockMvcHtmlUnitDriverBuilder` as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebDriver driver; @@ -166,8 +186,10 @@ We can easily create a Selenium WebDriver that integrates with MockMvc by using .build(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- lateinit var driver: WebDriver @@ -178,6 +200,7 @@ We can easily create a Selenium WebDriver that integrates with MockMvc by using .build() } ---- +====== NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced usage, see xref:testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`]. @@ -195,35 +218,45 @@ application to a Servlet container. For example, we can request the view to crea message with the following: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- CreateMessagePage page = CreateMessagePage.to(driver); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val page = CreateMessagePage.to(driver) ---- +====== -- We can then fill out the form and submit it to create a message, as follows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ViewMessagePage viewMessagePage = page.createMessage(ViewMessagePage.class, expectedSummary, expectedText); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val viewMessagePage = page.createMessage(ViewMessagePage::class, expectedSummary, expectedText) ---- +====== -- This improves on the design of our xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] @@ -233,8 +266,11 @@ with HtmlUnit, but it is much easier with WebDriver. Consider the following `CreateMessagePage` implementation: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class CreateMessagePage extends AbstractPage { // <1> @@ -262,6 +298,7 @@ with HtmlUnit, but it is much easier with WebDriver. Consider the following } } ---- +====== <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 @@ -327,26 +364,35 @@ Finally, we can verify that a new message was created successfully. The followin assertions use the https://assertj.github.io/doc/[AssertJ] assertion library: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage); assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- assertThat(viewMessagePage.message).isEqualTo(expectedMessage) assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message") ---- +====== -- We can see that our `ViewMessagePage` lets us interact with our custom domain model. For example, it exposes a method that returns a `Message` object: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public Message getMessage() throws ParseException { Message message = new Message(); @@ -357,11 +403,14 @@ example, it exposes a method that returns a `Message` object: return message; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun getMessage() = Message(getId(), getCreated(), getSummary(), getText()) ---- +====== -- We can then use the rich domain objects in our assertions. @@ -370,8 +419,11 @@ Lastly, we must not forget to close the `WebDriver` instance when the test is co as follows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @AfterEach void destroy() { @@ -381,8 +433,9 @@ as follows: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @AfterEach fun destroy() { @@ -391,6 +444,7 @@ as follows: } } ---- +====== -- For additional information on using WebDriver, see the Selenium @@ -403,8 +457,11 @@ In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simpl possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by the Spring TestContext Framework. This approach is repeated here, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebDriver driver; @@ -416,8 +473,9 @@ the Spring TestContext Framework. This approach is repeated here, as follows: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- lateinit var driver: WebDriver @@ -428,11 +486,15 @@ the Spring TestContext Framework. This approach is repeated here, as follows: .build() } ---- +====== We can also specify additional configuration options, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebDriver driver; @@ -449,8 +511,10 @@ We can also specify additional configuration options, as follows: .build(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- lateinit var driver: WebDriver @@ -467,12 +531,16 @@ We can also specify additional configuration options, as follows: .build() } ---- +====== As an alternative, we can perform the exact same setup by configuring the `MockMvc` instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- MockMvc mockMvc = MockMvcBuilders .webAppContextSetup(context) @@ -489,11 +557,13 @@ instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as f .build(); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- +====== This is more verbose, but, by building the `WebDriver` with a `MockMvc` instance, we have the full power of MockMvc at our fingertips. diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/why.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/why.adoc index 174f9108fa1..04493364f85 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/why.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-htmlunit/why.adoc @@ -8,8 +8,11 @@ supports paging through all messages. How would you go about testing it? With Spring MVC Test, we can easily test if we are able to create a `Message`, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- MockHttpServletRequestBuilder createMessage = post("/messages/") .param("summary", "Spring Rocks") @@ -19,8 +22,10 @@ With Spring MVC Test, we can easily test if we are able to create a `Message`, a .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/messages/123")); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Test fun test() { @@ -33,6 +38,7 @@ With Spring MVC Test, we can easily test if we are able to create a `Message`, a } } ---- +====== What if we want to test the form view that lets us create the message? For example, assume our form looks like the following snippet: @@ -57,31 +63,39 @@ assume our form looks like the following snippet: How do we ensure that our form produce the correct request to create a new message? A naive attempt might resemble the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(get("/messages/form")) .andExpect(xpath("//input[@name='summary']").exists()) .andExpect(xpath("//textarea[@name='text']").exists()); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- mockMvc.get("/messages/form").andExpect { xpath("//input[@name='summary']") { exists() } xpath("//textarea[@name='text']") { exists() } } ---- +====== This test has some obvious drawbacks. If we update our controller to use the parameter `message` instead of `text`, our form test continues to pass, even though the HTML form is out of synch with the controller. To resolve this we can combine our two tests, as follows: +[tabs] +====== +Java:: ++ [[spring-mvc-test-server-htmlunit-mock-mvc-test]] [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String summaryParamName = "summary"; String textParamName = "text"; @@ -98,8 +112,9 @@ follows: .andExpect(redirectedUrl("/messages/123")); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val summaryParamName = "summary"; val textParamName = "text"; @@ -115,6 +130,7 @@ follows: redirectedUrl("/messages/123") } ---- +====== This would reduce the risk of our test incorrectly passing, but there are still some problems: diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-performing-requests.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-performing-requests.adoc index 4bb64992e32..ba8ac3772dc 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-performing-requests.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-performing-requests.adoc @@ -7,16 +7,20 @@ xref:testing/webtestclient.adoc#webtestclient-tests[Writing Tests] instead. To perform requests that use any HTTP method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // static import of MockMvcRequestBuilders.* mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.post @@ -24,19 +28,24 @@ To perform requests that use any HTTP method, as the following example shows: accept = MediaType.APPLICATION_JSON } ---- +====== You can also perform file upload requests that internally use `MockMultipartHttpServletRequest` so that there is no actual parsing of a multipart request. Rather, you have to set it up to be similar to the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8"))); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.multipart @@ -44,30 +53,42 @@ request. Rather, you have to set it up to be similar to the following example: file("a1", "ABC".toByteArray(charset("UTF8"))) } ---- +====== You can specify query parameters in URI template style, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(get("/hotels?thing={thing}", "somewhere")); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- mockMvc.get("/hotels?thing={thing}", "somewhere") ---- +====== You can also add Servlet request parameters that represent either query or form parameters, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(get("/hotels").param("thing", "somewhere")); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.get @@ -75,6 +96,7 @@ parameters, as the following example shows: param("thing", "somewhere") } ---- +====== If application code relies on Servlet request parameters and does not check the query string explicitly (as is most often the case), it does not matter which option you use. @@ -87,13 +109,18 @@ request URI. If you must test with the full request URI, be sure to set the `con and `servletPath` accordingly so that request mappings work, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.servlet.get @@ -102,13 +129,17 @@ shows: servletPath = "/main" } ---- +====== In the preceding example, it would be cumbersome to set the `contextPath` and `servletPath` with every performed request. Instead, you can set up default request properties, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class MyWebTests { @@ -123,11 +154,14 @@ properties, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- +====== The preceding properties affect every request performed through the `MockMvc` instance. If the same property is also specified on a given request, it overrides the default diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-options.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-options.adoc index d6a6cf9f484..b38d69ed683 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-options.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-options.adoc @@ -7,8 +7,11 @@ point to Spring configuration with Spring MVC and controller infrastructure in i To set up MockMvc for testing a specific controller, use the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class MyWebTests { @@ -24,8 +27,9 @@ To set up MockMvc for testing a specific controller, use the following: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyWebTests { @@ -40,6 +44,7 @@ To set up MockMvc for testing a specific controller, use the following: } ---- +====== Or you can also use this setup when testing through the xref:testing/webtestclient.adoc#webtestclient-controller-config[WebTestClient] which delegates to the same builder @@ -47,8 +52,11 @@ as shown above. To set up MockMvc through Spring configuration, use the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig(locations = "my-servlet-context.xml") class MyWebTests { @@ -65,8 +73,9 @@ To set up MockMvc through Spring configuration, use the following: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitWebConfig(locations = ["my-servlet-context.xml"]) class MyWebTests { @@ -82,6 +91,7 @@ To set up MockMvc through Spring configuration, use the following: } ---- +====== Or you can also use this setup when testing through the xref:testing/webtestclient.adoc#webtestclient-context-config[WebTestClient] which delegates to the same builder @@ -108,8 +118,11 @@ a mock service with Mockito: You can then inject the mock service into the test to set up and verify your expectations, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig(locations = "test-servlet-context.xml") class AccountTests { @@ -128,8 +141,10 @@ expectations, as the following example shows: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitWebConfig(locations = ["test-servlet-context.xml"]) class AccountTests { @@ -148,6 +163,7 @@ expectations, as the following example shows: } ---- +====== The `standaloneSetup`, on the other hand, is a little closer to a unit test. It tests one controller at a time. You can manually inject the controller with mock dependencies, and diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-steps.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-steps.adoc index 3ebd6a8a00c..2ec41f725a5 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-steps.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/server-setup-steps.adoc @@ -6,8 +6,11 @@ some common and very useful features. For example, you can declare an `Accept` h all requests and expect a status of 200 as well as a `Content-Type` header in all responses, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // static import of MockMvcBuilders.standaloneSetup @@ -18,19 +21,24 @@ responses, as follows: .build(); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- +====== In addition, third-party frameworks (and applications) can pre-package setup instructions, such as those in a `MockMvcConfigurer`. The Spring Framework has one such built-in implementation that helps to save and re-use the HTTP session across requests. You can use it as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // static import of SharedHttpSessionConfigurer.sharedHttpSession @@ -41,11 +49,13 @@ You can use it as follows: // Use mockMvc to perform requests... ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- +====== See the javadoc for {api-spring-framework}/test/web/servlet/setup/ConfigurableMockMvcBuilder.html[`ConfigurableMockMvcBuilder`] diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc index f1004ec9c16..c5c3e7dc5a2 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc @@ -5,8 +5,11 @@ The best way to test streaming responses such as Server-Sent Events is through t <> which can be used as a test client to connect to a `MockMvc` instance to perform tests on Spring MVC controllers without a running server. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build(); @@ -26,6 +29,7 @@ to perform tests on Spring MVC controllers without a running server. For example .thenCancel() .verify(); ---- +====== `WebTestClient` can also connect to a live server and perform full end-to-end integration tests. This is also supported in Spring Boot where you can diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc index b9779c01522..eba93a4326a 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc @@ -28,8 +28,11 @@ https://assertj.github.io/doc/[AssertJ] to assert the types of application event published while invoking a method in a Spring-managed component: // Don't use "quotes" in the "subs" section because of the asterisks in /* ... */ +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @SpringJUnitConfig(/* ... */) @RecordApplicationEvents // <1> @@ -51,6 +54,7 @@ published while invoking a method in a Spring-managed component: } } ---- +====== <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. diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc index 3807f827d0d..c922c427181 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc @@ -16,8 +16,11 @@ As an alternative to implementing the `ApplicationContextAware` interface, you c the application context for your test class through the `@Autowired` annotation on either a field or setter method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig class MyTest { @@ -28,6 +31,7 @@ a field or setter method, as the following example shows: // class body... } ---- +====== <1> Injecting the `ApplicationContext`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -48,8 +52,11 @@ a field or setter method, as the following example shows: Similarly, if your test is configured to load a `WebApplicationContext`, you can inject the web application context into your test, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig // <1> class MyWebAppTest { @@ -60,6 +67,7 @@ the web application context into your test, as follows: // class body... } ---- +====== <1> Configuring the `WebApplicationContext`. <2> Injecting the `WebApplicationContext`. diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc index 30de9635eb9..ad418ef4cdf 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc @@ -38,8 +38,11 @@ ensure that each subclass gets its own `ApplicationContext` with the correct dyn properties. ==== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(/* ... */) @Testcontainers @@ -59,8 +62,10 @@ properties. } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(/* ... */) @Testcontainers @@ -85,6 +90,7 @@ properties. } ---- +====== [[precedence]] == Precedence diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc index 1f41ef8a09c..bb868bf5fae 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc @@ -59,8 +59,11 @@ Consider two examples with XML configuration and `@Configuration` classes: ---- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "classpath:/app-config.xml" @@ -77,8 +80,10 @@ Consider two examples with XML configuration and `@Configuration` classes: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) // ApplicationContext will be loaded from "classpath:/app-config.xml" @@ -95,6 +100,7 @@ Consider two examples with XML configuration and `@Configuration` classes: } } ---- +====== When `TransferServiceTest` is run, its `ApplicationContext` is loaded from the `app-config.xml` configuration file in the root of the classpath. If you inspect @@ -118,8 +124,11 @@ but define an in-memory data source as a default when neither of these is active The following code listings demonstrate how to implement the same configuration and integration test with `@Configuration` classes instead of XML: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @Profile("dev") @@ -135,8 +144,10 @@ integration test with `@Configuration` classes instead of XML: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @Profile("dev") @@ -152,9 +163,13 @@ integration test with `@Configuration` classes instead of XML: } } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @Profile("production") @@ -167,8 +182,10 @@ integration test with `@Configuration` classes instead of XML: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @Profile("production") @@ -181,9 +198,13 @@ integration test with `@Configuration` classes instead of XML: } } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @Profile("default") @@ -198,8 +219,10 @@ integration test with `@Configuration` classes instead of XML: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @Profile("default") @@ -214,9 +237,13 @@ integration test with `@Configuration` classes instead of XML: } } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class TransferServiceConfig { @@ -239,8 +266,10 @@ integration test with `@Configuration` classes instead of XML: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class TransferServiceConfig { @@ -264,9 +293,13 @@ integration test with `@Configuration` classes instead of XML: } } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig({ TransferServiceConfig.class, @@ -285,8 +318,10 @@ integration test with `@Configuration` classes instead of XML: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig( TransferServiceConfig::class, @@ -305,6 +340,7 @@ integration test with `@Configuration` classes instead of XML: } } ---- +====== In this variation, we have split the XML configuration into four independent `@Configuration` classes: @@ -333,8 +369,11 @@ has been moved to an abstract superclass, `AbstractIntegrationTest`: NOTE: As of Spring Framework 5.3, test configuration may also be inherited from enclosing classes. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration] for details. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig({ TransferServiceConfig.class, @@ -346,8 +385,9 @@ classes. See xref:testing/testcontext-framework/support-classes.adoc#testcontext } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig( TransferServiceConfig::class, @@ -358,9 +398,13 @@ classes. See xref:testing/testcontext-framework/support-classes.adoc#testcontext abstract class AbstractIntegrationTest { } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // "dev" profile inherited from superclass class TransferServiceTest extends AbstractIntegrationTest { @@ -374,8 +418,10 @@ classes. See xref:testing/testcontext-framework/support-classes.adoc#testcontext } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // "dev" profile inherited from superclass class TransferServiceTest : AbstractIntegrationTest() { @@ -389,12 +435,16 @@ classes. See xref:testing/testcontext-framework/support-classes.adoc#testcontext } } ---- +====== `@ActiveProfiles` also supports an `inheritProfiles` attribute that can be used to disable the inheritance of active profiles, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // "dev" profile overridden with "production" @ActiveProfiles(profiles = "production", inheritProfiles = false) @@ -403,8 +453,9 @@ disable the inheritance of active profiles, as the following example shows: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // "dev" profile overridden with "production" @ActiveProfiles("production", inheritProfiles = false) @@ -412,6 +463,7 @@ disable the inheritance of active profiles, as the following example shows: // test body } ---- +====== [[testcontext-ctx-management-env-profiles-ActiveProfilesResolver]] Furthermore, it is sometimes necessary to resolve active profiles for tests @@ -430,8 +482,11 @@ attribute of `@ActiveProfiles`. For further information, see the corresponding The following example demonstrates how to implement and register a custom `OperatingSystemActiveProfilesResolver`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // "dev" profile overridden programmatically via a custom resolver @ActiveProfiles( @@ -442,8 +497,9 @@ The following example demonstrates how to implement and register a custom } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // "dev" profile overridden programmatically via a custom resolver @ActiveProfiles( @@ -453,9 +509,13 @@ The following example demonstrates how to implement and register a custom // test body } ---- +====== +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { @@ -467,8 +527,10 @@ The following example demonstrates how to implement and register a custom } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver { @@ -479,4 +541,5 @@ The following example demonstrates how to implement and register a custom } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/groovy.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/groovy.adoc index e34e5b3becc..f44fdfd3965 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/groovy.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/groovy.adoc @@ -14,8 +14,11 @@ TestContext Framework is enabled automatically if Groovy is on the classpath. The following example shows how to specify Groovy configuration files: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "/AppConfig.groovy" and @@ -25,6 +28,7 @@ The following example shows how to specify Groovy configuration files: // class body... } ---- +====== <1> Specifying the location of Groovy configuration files. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -49,8 +53,11 @@ detect a default location based on the name of the test class. If your class is `"classpath:com/example/MyTestContext.groovy"`. The following example shows how to use the default: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from @@ -60,6 +67,7 @@ the default: // class body... } ---- +====== <1> Loading configuration from the default location. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -87,8 +95,11 @@ configured resource location ends with `.xml`, it is loaded by using an The following listing shows how to combine both in an integration test: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from @@ -98,8 +109,10 @@ The following listing shows how to combine both in an integration test: // class body... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) // ApplicationContext will be loaded from @@ -109,5 +122,6 @@ The following listing shows how to combine both in an integration test: // class body... } ---- +====== ===== diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/hierarchies.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/hierarchies.adoc index 1411e7c1b18..c92ebb9064b 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/hierarchies.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/hierarchies.adoc @@ -35,8 +35,11 @@ one for the root `WebApplicationContext` (loaded by using the `TestAppConfig` that is autowired into the test instance is the one for the child context (that is, the lowest context in the hierarchy). The following listing shows this configuration scenario: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @WebAppConfiguration @@ -53,8 +56,9 @@ lowest context in the hierarchy). The following listing shows this configuration } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @WebAppConfiguration @@ -69,6 +73,7 @@ lowest context in the hierarchy). The following listing shows this configuration // ... } ---- +====== -- **Class hierarchy with implicit parent context** @@ -86,8 +91,11 @@ based on the configuration in `AbstractWebTests` is set as the parent context fo the contexts loaded for the concrete subclasses. The following listing shows this configuration scenario: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @WebAppConfiguration @@ -100,8 +108,10 @@ configuration scenario: @ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")) public class RestWebServiceTests extends AbstractWebTests {} ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @WebAppConfiguration @@ -115,6 +125,7 @@ configuration scenario: class RestWebServiceTests : AbstractWebTests() ---- +====== -- **Class hierarchy with merged context hierarchy configuration** @@ -131,8 +142,11 @@ application context loaded from `/app-config.xml` is set as the parent context f contexts loaded from `/user-config.xml` and `{"/user-config.xml", "/order-config.xml"}`. The following listing shows this configuration scenario: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @ContextHierarchy({ @@ -147,8 +161,9 @@ The following listing shows this configuration scenario: class ExtendedTests extends BaseTests {} ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @ContextHierarchy( @@ -161,6 +176,7 @@ The following listing shows this configuration scenario: ) class ExtendedTests : BaseTests() {} ---- +====== -- **Class hierarchy with overridden context hierarchy configuration** @@ -172,8 +188,11 @@ application context for `ExtendedTests` is loaded only from `/test-user-config.x has its parent set to the context loaded from `/app-config.xml`. The following listing shows this configuration scenario: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @ContextHierarchy({ @@ -190,8 +209,10 @@ shows this configuration scenario: )) class ExtendedTests extends BaseTests {} ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @ContextHierarchy( @@ -207,6 +228,7 @@ shows this configuration scenario: )) class ExtendedTests : BaseTests() {} ---- +====== .Dirtying a context within a context hierarchy NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/inheritance.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/inheritance.adoc index 6c7b7650f26..b4b8fed16fb 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/inheritance.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/inheritance.adoc @@ -26,8 +26,11 @@ Beans defined in `extended-config.xml` can, therefore, override (that is, replac defined in `base-config.xml`. The following example shows how one class can extend another and use both its own configuration file and the superclass's configuration file: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "/base-config.xml" @@ -44,6 +47,7 @@ another and use both its own configuration file and the superclass's configurati // class body... } ---- +====== <1> Configuration file defined in the superclass. <2> Configuration file defined in the subclass. @@ -75,8 +79,11 @@ order. Beans defined in `ExtendedConfig` can, therefore, override (that is, repl those defined in `BaseConfig`. The following example shows how one class can extend another and use both its own configuration class and the superclass's configuration class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // ApplicationContext will be loaded from BaseConfig @SpringJUnitConfig(BaseConfig.class) // <1> @@ -90,6 +97,7 @@ another and use both its own configuration class and the superclass's configurat // class body... } ---- +====== <1> Configuration class defined in the superclass. <2> Configuration class defined in the subclass. @@ -119,8 +127,11 @@ implement Spring's `Ordered` interface or are annotated with Spring's `@Order` a or the standard `@Priority` annotation. The following example shows how one class can extend another and use both its own initializer and the superclass's initializer: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // ApplicationContext will be initialized by BaseInitializer @SpringJUnitConfig(initializers = BaseInitializer.class) // <1> @@ -135,6 +146,7 @@ extend another and use both its own initializer and the superclass's initializer // class body... } ---- +====== <1> Initializer defined in the superclass. <2> Initializer defined in the subclass. diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/initializers.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/initializers.adoc index 4263559232f..caba982d288 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/initializers.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/initializers.adoc @@ -13,8 +13,11 @@ order in which the initializers are invoked depends on whether they implement Sp `Ordered` interface or are annotated with Spring's `@Order` annotation or the standard `@Priority` annotation. The following example shows how to use initializers: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from TestConfig @@ -26,6 +29,7 @@ order in which the initializers are invoked depends on whether they implement Sp // class body... } ---- +====== <1> Specifying configuration by using a configuration class and an initializer. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -50,8 +54,11 @@ component classes in `@ContextConfiguration` entirely and instead declare only in the context -- for example, by programmatically loading bean definitions from XML files or configuration classes. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be initialized by EntireAppInitializer @@ -61,6 +68,7 @@ files or configuration classes. The following example shows how to do so: // class body... } ---- +====== <1> Specifying configuration by using only an initializer. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/javaconfig.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/javaconfig.adoc index bd7af6c1487..97cb4df3801 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/javaconfig.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/javaconfig.adoc @@ -6,8 +6,11 @@ xref:core/beans/java.adoc[Java-based container configuration]), you can annotate class with `@ContextConfiguration` and configure the `classes` attribute with an array that contains references to component classes. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from AppConfig and TestConfig @@ -16,6 +19,7 @@ that contains references to component classes. The following example shows how t // class body... } ---- +====== <1> Specifying component classes. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -64,8 +68,11 @@ example, the `OrderServiceTest` class declares a `static` nested configuration c named `Config` that is automatically used to load the `ApplicationContext` for the test class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig <1> // ApplicationContext will be loaded from the static nested Config class @@ -93,6 +100,7 @@ class: } ---- +====== <1> Loading configuration information from the nested `Config` class. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc index 263e21446d4..4d881232542 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc @@ -40,8 +40,11 @@ loaded by using the specified resource protocol. Resource location wildcards (su The following example uses a test properties file: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestPropertySource("/test.properties") // <1> @@ -49,6 +52,7 @@ The following example uses a test properties file: // class body... } ---- +====== <1> Specifying a properties file with an absolute path. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -77,8 +81,11 @@ a Java properties file: The following example sets two inlined properties: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) // <1> @@ -86,6 +93,7 @@ The following example sets two inlined properties: // class body... } ---- +====== <1> Setting two properties by using two variations of the key-value syntax. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -147,8 +155,11 @@ entries for the `timezone` and `port` properties those are overridden by the inl properties declared by using the `properties` attribute. The following example shows how to specify properties both in a file and inline: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestPropertySource( @@ -160,8 +171,9 @@ to specify properties both in a file and inline: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ContextConfiguration @TestPropertySource("/test.properties", @@ -171,6 +183,7 @@ to specify properties both in a file and inline: // class body... } ---- +====== [[inheriting-and-overriding-test-property-sources]] == Inheriting and Overriding Test Property Sources @@ -199,8 +212,11 @@ for `ExtendedTest` is loaded by using the `base.properties` and `extended.proper files as test property source locations. The following example shows how to define properties in both a subclass and its superclass by using `properties` files: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @TestPropertySource("base.properties") @ContextConfiguration @@ -214,8 +230,10 @@ properties in both a subclass and its superclass by using `properties` files: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @TestPropertySource("base.properties") @ContextConfiguration @@ -229,14 +247,18 @@ properties in both a subclass and its superclass by using `properties` files: // ... } ---- +====== In the next example, the `ApplicationContext` for `BaseTest` is loaded by using only the inlined `key1` property. In contrast, the `ApplicationContext` for `ExtendedTest` is loaded by using the inlined `key1` and `key2` properties. The following example shows how to define properties in both a subclass and its superclass by using inline properties: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @TestPropertySource(properties = "key1 = value1") @ContextConfiguration @@ -250,8 +272,10 @@ to define properties in both a subclass and its superclass by using inline prope // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @TestPropertySource(properties = ["key1 = value1"]) @ContextConfiguration @@ -265,4 +289,5 @@ to define properties in both a subclass and its superclass by using inline prope // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web-mocks.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web-mocks.adoc index 99088862538..267baf8e0e0 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web-mocks.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web-mocks.adoc @@ -18,9 +18,11 @@ mocks can be autowired into your test instance. Note that the `WebApplicationCon `MockServletContext` are both cached across the test suite, whereas the other mocks are managed per test method by the `ServletTestExecutionListener`. -.Injecting mocks +[tabs] +====== +Injecting mocks:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig class WacTests { @@ -47,8 +49,9 @@ managed per test method by the `ServletTestExecutionListener`. } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitWebConfig class WacTests { @@ -74,4 +77,5 @@ managed per test method by the `ServletTestExecutionListener`. //... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web.adoc index 163720fc87c..cfc6778bbb9 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/web.adoc @@ -29,9 +29,11 @@ The remaining examples in this section show some of the various configuration op loading a `WebApplicationContext`. The following example shows the TestContext framework's support for convention over configuration: -.Conventions +[tabs] +====== +Conventions:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @@ -45,8 +47,10 @@ framework's support for convention over configuration: //... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @@ -60,6 +64,7 @@ framework's support for convention over configuration: //... } ---- +====== If you annotate a test class with `@WebAppConfiguration` without specifying a resource base path, the resource path effectively defaults to `file:src/main/webapp`. Similarly, @@ -71,9 +76,11 @@ as the `WacTests` class or static nested `@Configuration` classes). The following example shows how to explicitly declare a resource base path with `@WebAppConfiguration` and an XML resource location with `@ContextConfiguration`: -.Default resource semantics +[tabs] +====== +Default resource semantics:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @@ -86,8 +93,10 @@ The following example shows how to explicitly declare a resource base path with //... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @@ -100,6 +109,7 @@ The following example shows how to explicitly declare a resource base path with //... } ---- +====== The important thing to note here is the different semantics for paths with these two annotations. By default, `@WebAppConfiguration` resource paths are file system based, @@ -108,9 +118,11 @@ whereas `@ContextConfiguration` resource locations are classpath based. The following example shows that we can override the default resource semantics for both annotations by specifying a Spring resource prefix: -.Explicit resource semantics +[tabs] +====== +Explicit resource semantics:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @@ -123,8 +135,10 @@ annotations by specifying a Spring resource prefix: //... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) @@ -137,6 +151,7 @@ annotations by specifying a Spring resource prefix: //... } ---- +====== Contrast the comments in this example with the previous example. diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/xml.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/xml.adoc index b11a6c4be70..2ee0dc5bddf 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/xml.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/xml.adoc @@ -10,8 +10,11 @@ is treated as an absolute classpath location (for example, `/org/example/config. path that represents a resource URL (i.e., a path prefixed with `classpath:`, `file:`, `http:`, etc.) is used _as is_. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "/app-config.xml" and @@ -21,6 +24,7 @@ path that represents a resource URL (i.e., a path prefixed with `classpath:`, `f // class body... } ---- +====== <1> Setting the locations attribute to a list of XML files. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -43,8 +47,11 @@ attributes in `@ContextConfiguration`, you can omit the declaration of the `loca attribute name and declare the resource locations by using the shorthand format demonstrated in the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @ContextConfiguration({"/app-config.xml", "/test-config.xml"}) <1> @@ -52,6 +59,7 @@ demonstrated in the following example: // class body... } ---- +====== <1> Specifying XML files without using the `locations` attribute. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -74,8 +82,11 @@ class. If your class is named `com.example.MyTest`, `GenericXmlContextLoader` lo application context from `"classpath:com/example/MyTest-context.xml"`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from @@ -85,6 +96,7 @@ example shows how to do so: // class body... } ---- +====== <1> Loading configuration from the default location. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc index bfb51004538..dc51cfa9d0d 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/executing-sql.adoc @@ -46,8 +46,11 @@ to run the populator against a `javax.sql.DataSource`. The following example specifies SQL scripts for a test schema and test data, sets the statement separator to `@@`, and run the scripts against a `DataSource`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Test void databaseTest() { @@ -60,8 +63,10 @@ specifies SQL scripts for a test schema and test data, sets the statement separa // run code that uses the test schema and data } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Test fun databaseTest() { @@ -74,6 +79,7 @@ specifies SQL scripts for a test schema and test data, sets the statement separa // run code that uses the test schema and data } ---- +====== Note that `ResourceDatabasePopulator` internally delegates to `ScriptUtils` for parsing and running SQL scripts. Similarly, the `executeSqlScript(..)` methods in @@ -110,8 +116,11 @@ the specified resource protocol. The following example shows how to use `@Sql` at the class level and at the method level within a JUnit Jupiter based integration test class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig @Sql("/test-schema.sql") @@ -130,8 +139,9 @@ within a JUnit Jupiter based integration test class: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig @Sql("/test-schema.sql") @@ -149,6 +159,7 @@ within a JUnit Jupiter based integration test class: } } ---- +====== [[testcontext-executing-sql-declaratively-script-detection]] === Default Script Detection @@ -175,8 +186,11 @@ Java 8, you can use `@Sql` as a repeatable annotation. Otherwise, you can use th The following example shows how to use `@Sql` as a repeatable annotation with Java 8: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Test @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")) @@ -185,11 +199,14 @@ The following example shows how to use `@Sql` as a repeatable annotation with Ja // run code that uses the test schema and test data } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin ---- +====== In the scenario presented in the preceding example, the `test-schema.sql` script uses a different syntax for single-line comments. @@ -199,8 +216,11 @@ declarations are grouped together within `@SqlGroup`. With Java 8 and above, the `@SqlGroup` is optional, but you may need to use `@SqlGroup` for compatibility with other JVM languages such as Kotlin. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Test @SqlGroup({ @@ -211,8 +231,10 @@ other JVM languages such as Kotlin. // run code that uses the test schema and test data } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Test @SqlGroup( @@ -222,6 +244,7 @@ other JVM languages such as Kotlin. // Run code that uses the test schema and test data } ---- +====== [[testcontext-executing-sql-declaratively-script-execution-phases]] === Script Execution Phases @@ -231,8 +254,11 @@ you need to run a particular set of scripts after the test method (for example, up database state), you can use the `executionPhase` attribute in `@Sql`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Test @Sql( @@ -249,8 +275,10 @@ following example shows: // to the database outside of the test's transaction } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Test @SqlGroup( @@ -264,6 +292,7 @@ following example shows: // to the database outside of the test's transaction } ---- +====== Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from `Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively. @@ -319,8 +348,11 @@ reference manual, the javadoc for provide detailed information, and the following example shows a typical testing scenario that uses JUnit Jupiter and transactional tests with `@Sql`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestDatabaseConfig.class) @Transactional @@ -351,8 +383,10 @@ that uses JUnit Jupiter and transactional tests with `@Sql`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(TestDatabaseConfig::class) @Transactional @@ -378,6 +412,7 @@ that uses JUnit Jupiter and transactional tests with `@Sql`: } } ---- +====== Note that there is no need to clean up the database after the `usersTest()` method is run, since any changes made to the database (either within the test method or within the diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/fixture-di.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/fixture-di.adoc index 2951d0430d2..54ce51bffef 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/fixture-di.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/fixture-di.adoc @@ -54,8 +54,11 @@ example. The first code listing shows a JUnit Jupiter based implementation of the test class that uses `@Autowired` for field injection: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // specifies the Spring configuration to load for this test fixture @@ -74,8 +77,9 @@ uses `@Autowired` for field injection: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) // specifies the Spring configuration to load for this test fixture @@ -93,12 +97,16 @@ uses `@Autowired` for field injection: } } ---- +====== Alternatively, you can configure the class to use `@Autowired` for setter injection, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) // specifies the Spring configuration to load for this test fixture @@ -121,8 +129,9 @@ follows: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExtendWith(SpringExtension::class) // specifies the Spring configuration to load for this test fixture @@ -144,6 +153,7 @@ follows: } } ---- +====== The preceding code listings use the same XML context file referenced by the `@ContextConfiguration` annotation (that is, `repository-config.xml`). The following @@ -178,8 +188,11 @@ such a case, you can override the setter method and use the `@Qualifier` annotat indicate a specific target bean, as follows (but make sure to delegate to the overridden method in the superclass as well): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // ... @@ -192,8 +205,9 @@ method in the superclass as well): // ... ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // ... @@ -204,6 +218,7 @@ method in the superclass as well): // ... ---- +====== The specified qualifier value indicates the specific `DataSource` bean to inject, narrowing the set of type matches to a specific bean. Its value is matched against diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc index ae9aa71b1bc..7f0a8c19aa2 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc @@ -20,8 +20,11 @@ xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules The following code listing shows the minimal requirements for configuring a test class to run with the custom Spring `Runner`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RunWith(SpringRunner.class) @TestExecutionListeners({}) @@ -34,8 +37,9 @@ run with the custom Spring `Runner`: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RunWith(SpringRunner::class) @TestExecutionListeners @@ -47,6 +51,7 @@ run with the custom Spring `Runner`: } } ---- +====== In the preceding example, `@TestExecutionListeners` is configured with an empty list, to disable the default listeners, which otherwise would require an `ApplicationContext` to @@ -74,8 +79,11 @@ To support the full functionality of the TestContext framework, you must combine `SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way to declare these rules in an integration test: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Optionally specify a non-Spring Runner via @RunWith(...) @ContextConfiguration @@ -94,8 +102,9 @@ to declare these rules in an integration test: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Optionally specify a non-Spring Runner via @RunWith(...) @ContextConfiguration @@ -115,6 +124,7 @@ to declare these rules in an integration test: } } ---- +====== [[testcontext-support-classes-junit4]] == JUnit 4 Support Classes @@ -179,8 +189,11 @@ TestNG: The following code listing shows how to configure a test class to use the `SpringExtension` in conjunction with `@ContextConfiguration`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Instructs JUnit Jupiter to extend the test with Spring support. @ExtendWith(SpringExtension.class) @@ -195,8 +208,9 @@ The following code listing shows how to configure a test class to use the } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Instructs JUnit Jupiter to extend the test with Spring support. @ExtendWith(SpringExtension::class) @@ -210,6 +224,7 @@ The following code listing shows how to configure a test class to use the } } ---- +====== Since you can also use annotations in JUnit 5 as meta-annotations, Spring provides the `@SpringJUnitConfig` and `@SpringJUnitWebConfig` composed annotations to simplify the @@ -218,8 +233,11 @@ configuration of the test `ApplicationContext` and JUnit Jupiter. The following example uses `@SpringJUnitConfig` to reduce the amount of configuration used in the previous example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Instructs Spring to register the SpringExtension with JUnit // Jupiter and load an ApplicationContext from TestConfig.class @@ -233,8 +251,9 @@ used in the previous example: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Instructs Spring to register the SpringExtension with JUnit // Jupiter and load an ApplicationContext from TestConfig.class @@ -247,12 +266,16 @@ used in the previous example: } } ---- +====== Similarly, the following example uses `@SpringJUnitWebConfig` to create a `WebApplicationContext` for use with JUnit Jupiter: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Instructs Spring to register the SpringExtension with JUnit // Jupiter and load a WebApplicationContext from TestWebConfig.class @@ -266,8 +289,9 @@ Similarly, the following example uses `@SpringJUnitWebConfig` to create a } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Instructs Spring to register the SpringExtension with JUnit // Jupiter and load a WebApplicationContext from TestWebConfig::class @@ -280,6 +304,7 @@ Similarly, the following example uses `@SpringJUnitWebConfig` to create a } } ---- +====== See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details. @@ -345,8 +370,11 @@ In the following example, Spring injects the `OrderService` bean from the `ApplicationContext` loaded from `TestConfig.class` into the `OrderServiceIntegrationTests` constructor. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -362,8 +390,9 @@ In the following example, Spring injects the `OrderService` bean from the } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(TestConfig::class) class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){ @@ -371,6 +400,7 @@ In the following example, Spring injects the `OrderService` bean from the } ---- +====== Note that this feature lets test dependencies be `final` and therefore immutable. @@ -378,8 +408,11 @@ If the `spring.test.constructor.autowire.mode` property is to `all` (see xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]), we can omit the declaration of `@Autowired` on the constructor in the previous example, resulting in the following. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -394,14 +427,16 @@ xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-anno } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(TestConfig::class) class OrderServiceIntegrationTests(val orderService:OrderService) { // tests that use the injected OrderService } ---- +====== [[testcontext-junit-jupiter-di-method]] ==== Method Injection @@ -414,8 +449,11 @@ parameter with the corresponding bean from the test's `ApplicationContext`. In the following example, Spring injects the `OrderService` from the `ApplicationContext` loaded from `TestConfig.class` into the `deleteOrder()` test method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -427,8 +465,9 @@ loaded from `TestConfig.class` into the `deleteOrder()` test method: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(TestConfig::class) class OrderServiceIntegrationTests { @@ -439,6 +478,7 @@ loaded from `TestConfig.class` into the `deleteOrder()` test method: } } ---- +====== Due to the robustness of the `ParameterResolver` support in JUnit Jupiter, you can also have multiple dependencies injected into a single method, not only from Spring but also @@ -447,8 +487,11 @@ from JUnit Jupiter itself or other third-party extensions. The following example shows how to have both Spring and JUnit Jupiter inject dependencies into the `placeOrderRepeatedly()` test method simultaneously. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -463,8 +506,9 @@ into the `placeOrderRepeatedly()` test method simultaneously. } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(TestConfig::class) class OrderServiceIntegrationTests { @@ -477,6 +521,7 @@ into the `placeOrderRepeatedly()` test method simultaneously. } } ---- +====== Note that the use of `@RepeatedTest` from JUnit Jupiter lets the test method gain access to the `RepetitionInfo`. @@ -514,8 +559,11 @@ xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations] to see which annotations can be inherited in `@Nested` test classes. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) class GreetingServiceTests { @@ -542,8 +590,9 @@ which annotations can be inherited in `@Nested` test classes. } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(TestConfig::class) class GreetingServiceTests { @@ -569,6 +618,7 @@ which annotations can be inherited in `@Nested` test classes. } } ---- +====== [[testcontext-support-classes-testng]] == TestNG Support Classes diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tel-config.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tel-config.adoc index 6b255cb59b2..3bb86387d39 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tel-config.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tel-config.adoc @@ -37,8 +37,11 @@ If you extend a class that is annotated with `@TestExecutionListeners` and you n switch to using the default set of listeners, you can annotate your class with the following. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Switch to default listeners @TestExecutionListeners( @@ -50,8 +53,9 @@ following. } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Switch to default listeners @TestExecutionListeners( @@ -62,6 +66,7 @@ following. // class body... } ---- +====== ==== [[testcontext-tel-config-automatic-discovery]] @@ -103,8 +108,11 @@ default listeners are not registered. In most common testing scenarios, this eff forces the developer to manually declare all default listeners in addition to any custom listeners. The following listing demonstrates this style of configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestExecutionListeners({ @@ -121,8 +129,9 @@ listeners. The following listing demonstrates this style of configuration: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ContextConfiguration @TestExecutionListeners( @@ -138,6 +147,7 @@ listeners. The following listing demonstrates this style of configuration: // class body... } ---- +====== The challenge with this approach is that it requires that the developer know exactly which listeners are registered by default. Moreover, the set of default listeners can @@ -165,8 +175,11 @@ configures its `order` value (for example, `500`) to be less than the order of t defaults in front of the `ServletTestExecutionListener`, and the previous example could be replaced with the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration @TestExecutionListeners( @@ -177,8 +190,10 @@ be replaced with the following: // class body... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ContextConfiguration @TestExecutionListeners( @@ -189,4 +204,5 @@ be replaced with the following: // class body... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc index babf258e0ff..3d609bf25e9 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc @@ -102,8 +102,11 @@ are preconfigured for transactional support at the class level. The following example demonstrates a common scenario for writing an integration test for a Hibernate-based `UserRepository`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(TestConfig.class) @Transactional @@ -145,8 +148,9 @@ a Hibernate-based `UserRepository`: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig(TestConfig::class) @Transactional @@ -187,6 +191,7 @@ a Hibernate-based `UserRepository`: } } ---- +====== As explained in xref:testing/testcontext-framework/tx.adoc#testcontext-tx-rollback-and-commit-behavior[Transaction Rollback and Commit Behavior], there is no need to clean up the database after the `createUser()` method runs, since any changes made to the @@ -214,8 +219,11 @@ The following example demonstrates some of the features of `TestTransaction`. Se javadoc for {api-spring-framework}/test/context/transaction/TestTransaction.html[`TestTransaction`] for further details. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ContextConfiguration(classes = TestConfig.class) public class ProgrammaticTransactionManagementTests extends @@ -244,8 +252,10 @@ for further details. } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ContextConfiguration(classes = [TestConfig::class]) class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() { @@ -273,6 +283,7 @@ for further details. } } ---- +====== [[testcontext-tx-before-and-after-tx]] == Running Code Outside of a Transaction @@ -318,8 +329,11 @@ information and configuration examples. xref:testing/testcontext-framework/execu declarative SQL script execution with default transaction rollback semantics. The following example shows the relevant annotations: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig @Transactional(transactionManager = "txMgr") @@ -356,8 +370,9 @@ following example shows the relevant annotations: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitConfig @Transactional(transactionManager = "txMgr") @@ -393,6 +408,7 @@ following example shows the relevant annotations: } ---- +====== [[testcontext-tx-false-positives]] .Avoid false positives when testing ORM code @@ -407,8 +423,11 @@ of work. In the following Hibernate-based example test case, one method demonstr false positive, and the other method correctly exposes the results of flushing the session: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // ... @@ -434,8 +453,9 @@ session: // ... ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // ... @@ -460,11 +480,15 @@ session: // ... ---- +====== The following example shows matching methods for JPA: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // ... @@ -489,8 +513,10 @@ The following example shows matching methods for JPA: // ... ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // ... @@ -515,6 +541,7 @@ The following example shows matching methods for JPA: // ... ---- +====== ===== [[testcontext-tx-orm-lifecycle-callbacks]] @@ -539,8 +566,11 @@ The following example shows how to flush the `EntityManager` to ensure that a `@PostPersist` callback method has been registered for the `Person` entity used in the example. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // ... @@ -565,8 +595,10 @@ example. // ... ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // ... @@ -591,6 +623,7 @@ example. // ... ---- +====== See https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/JpaEntityListenerTests.java[JpaEntityListenerTests] diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/web-scoped-beans.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/web-scoped-beans.adoc index 5c87d206a11..20e7926a7f5 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/web-scoped-beans.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/web-scoped-beans.adoc @@ -47,9 +47,11 @@ the provided `MockHttpServletRequest`. When the `loginUser()` method is invoked set parameters). We can then perform assertions against the results based on the known inputs for the username and password. The following listing shows how to do so: -.Request-scoped bean test +[tabs] +====== +Request-scoped bean test:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig class RequestScopedBeanTests { @@ -67,8 +69,10 @@ inputs for the username and password. The following listing shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitWebConfig class RequestScopedBeanTests { @@ -86,6 +90,7 @@ inputs for the username and password. The following listing shows how to do so: } } ---- +====== The following code snippet is similar to the one we saw earlier for a request-scoped bean. However, this time, the `userService` bean has a dependency on a session-scoped @@ -119,9 +124,11 @@ the user service has access to the session-scoped `userPreferences` for the curr `MockHttpSession`, and we can perform assertions against the results based on the configured theme. The following example shows how to do so: -.Session-scoped bean test +[tabs] +====== +Session-scoped bean test:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitWebConfig class SessionScopedBeanTests { @@ -139,8 +146,9 @@ configured theme. The following example shows how to do so: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @SpringJUnitWebConfig class SessionScopedBeanTests { @@ -157,4 +165,5 @@ configured theme. The following example shows how to do so: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc b/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc index f5f1cab0a7b..900596e07ea 100644 --- a/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc +++ b/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc @@ -32,17 +32,23 @@ xref:web/webflux/dispatcher-handler.adoc#webflux-framework-config[WebFlux Java c controller(s), and creates a xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebTestClient client = WebTestClient.bindToController(new TestController()).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = WebTestClient.bindToController(TestController()).build() ---- +====== For Spring MVC, use the following which delegates to the {api-spring-framework}/test/web/servlet/setup/StandaloneMockMvcBuilder.html[StandaloneMockMvcBuilder] @@ -50,17 +56,23 @@ to load infrastructure equivalent to the xref:web/webmvc/mvc-config.adoc[WebMvc registers the given controller(s), and creates an instance of xref:testing/spring-mvc-test-framework.adoc[MockMvc] to handle requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebTestClient client = MockMvcWebTestClient.bindToController(new TestController()).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = MockMvcWebTestClient.bindToController(TestController()).build() ---- +====== @@ -76,8 +88,11 @@ For WebFlux, use the following where the Spring `ApplicationContext` is passed t to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @SpringJUnitConfig(WebConfig.class) // <1> class MyTests { @@ -90,6 +105,7 @@ requests: } } ---- +====== <1> Specify the configuration to load <2> Inject the configuration <3> Create the `WebTestClient` @@ -117,8 +133,11 @@ For Spring MVC, use the following where the Spring `ApplicationContext` is passe to create a xref:testing/spring-mvc-test-framework.adoc[MockMvc] instance to handle requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExtendWith(SpringExtension.class) @WebAppConfiguration("classpath:META-INF/web-resources") // <1> @@ -139,6 +158,7 @@ requests: } } ---- +====== <1> Specify the configuration to load <2> Inject the configuration <3> Create the `WebTestClient` @@ -180,18 +200,24 @@ mock request and response objects, without a running server. For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to create a server setup to handle requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = ... client = WebTestClient.bindToRouterFunction(route).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val route: RouterFunction<*> = ... val client = WebTestClient.bindToRouterFunction(route).build() ---- +====== For Spring MVC there are currently no options to test xref:web/webmvc-functional.adoc[WebMvc functional endpoints]. @@ -203,16 +229,22 @@ xref:web/webmvc-functional.adoc[WebMvc functional endpoints]. This setup connects to a running server to perform full, end-to-end HTTP tests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build() ---- +====== @@ -225,22 +257,28 @@ are readily available following `bindToServer()`. For all other configuration op you need to use `configureClient()` to transition from server to client configuration, as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client = WebTestClient.bindToController(new TestController()) .configureClient() .baseUrl("/test") .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client = WebTestClient.bindToController(TestController()) .configureClient() .baseUrl("/test") .build() ---- +====== @@ -258,8 +296,11 @@ instead continues with a workflow to verify responses. To assert the response status and headers, use the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.get().uri("/persons/1") .accept(MediaType.APPLICATION_JSON) @@ -267,8 +308,10 @@ To assert the response status and headers, use the following: .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.get().uri("/persons/1") .accept(MediaType.APPLICATION_JSON) @@ -276,14 +319,18 @@ To assert the response status and headers, use the following: .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON) ---- +====== If you would like for all expectations to be asserted even if one of them fails, you can use `expectAll(..)` instead of multiple chained `expect*(..)` calls. This feature is similar to the _soft assertions_ support in AssertJ and the `assertAll()` support in JUnit Jupiter. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.get().uri("/persons/1") .accept(MediaType.APPLICATION_JSON) @@ -293,6 +340,7 @@ JUnit Jupiter. spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) ); ---- +====== You can then choose to decode the response body through one of the following: @@ -302,16 +350,21 @@ You can then choose to decode the response body through one of the following: And perform assertions on the resulting higher level Object(s): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.get().uri("/persons") .exchange() .expectStatus().isOk() .expectBodyList(Person.class).hasSize(3).contains(person); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.reactive.server.expectBodyList @@ -320,12 +373,16 @@ And perform assertions on the resulting higher level Object(s): .expectStatus().isOk() .expectBodyList().hasSize(3).contains(person) ---- +====== If the built-in assertions are insufficient, you can consume the object instead and perform any other assertions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.test.web.reactive.server.expectBody @@ -337,8 +394,10 @@ perform any other assertions: // custom assertions (e.g. AssertJ)... }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.get().uri("/persons/1") .exchange() @@ -348,11 +407,15 @@ perform any other assertions: // custom assertions (e.g. AssertJ)... } ---- +====== Or you can exit the workflow and obtain an `EntityExchangeResult`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- EntityExchangeResult result = client.get().uri("/persons/1") .exchange() @@ -360,8 +423,10 @@ Or you can exit the workflow and obtain an `EntityExchangeResult`: .expectBody(Person.class) .returnResult(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.reactive.server.expectBody @@ -371,6 +436,7 @@ Or you can exit the workflow and obtain an `EntityExchangeResult`: .expectBody() .returnResult() ---- +====== TIP: When you need to decode to a target type with generics, look for the overloaded methods that accept @@ -384,8 +450,11 @@ instead of `Class`. If the response is not expected to have content, you can assert that as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.post().uri("/persons") .body(personMono, Person.class) @@ -393,8 +462,10 @@ If the response is not expected to have content, you can assert that as follows: .expectStatus().isCreated() .expectBody().isEmpty(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.post().uri("/persons") .bodyValue(person) @@ -402,26 +473,33 @@ If the response is not expected to have content, you can assert that as follows: .expectStatus().isCreated() .expectBody().isEmpty() ---- +====== If you want to ignore the response content, the following releases the content without any assertions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.get().uri("/persons/123") .exchange() .expectStatus().isNotFound() .expectBody(Void.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.get().uri("/persons/123") .exchange() .expectStatus().isNotFound .expectBody() ---- +====== @@ -433,8 +511,11 @@ content rather than through higher level Object(s). To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAssert]: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.get().uri("/persons/1") .exchange() @@ -442,8 +523,10 @@ To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAsse .expectBody() .json("{\"name\":\"Jane\"}") ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.get().uri("/persons/1") .exchange() @@ -451,11 +534,15 @@ To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAsse .expectBody() .json("{\"name\":\"Jane\"}") ---- +====== To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- client.get().uri("/persons") .exchange() @@ -464,8 +551,10 @@ To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]: .jsonPath("$[0].name").isEqualTo("Jane") .jsonPath("$[1].name").isEqualTo("Jason"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- client.get().uri("/persons") .exchange() @@ -474,6 +563,7 @@ To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]: .jsonPath("$[0].name").isEqualTo("Jane") .jsonPath("$[1].name").isEqualTo("Jason") ---- +====== @@ -484,8 +574,11 @@ To test potentially infinite streams such as `"text/event-stream"` or `"application/x-ndjson"`, start by verifying the response status and headers, and then obtain a `FluxExchangeResult`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- FluxExchangeResult result = client.get().uri("/events") .accept(TEXT_EVENT_STREAM) @@ -494,8 +587,10 @@ obtain a `FluxExchangeResult`: .returnResult(MyEvent.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.test.web.reactive.server.returnResult @@ -505,11 +600,15 @@ obtain a `FluxExchangeResult`: .expectStatus().isOk() .returnResult() ---- +====== Now you're ready to consume the response stream with `StepVerifier` from `reactor-test`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Flux eventFlux = result.getResponseBody(); @@ -520,8 +619,10 @@ Now you're ready to consume the response stream with `StepVerifier` from `reacto .thenCancel() .verify(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val eventFlux = result.getResponseBody() @@ -532,6 +633,7 @@ Now you're ready to consume the response stream with `StepVerifier` from `reacto .thenCancel() .verify() ---- +====== [[webtestclient-mockmvc]] @@ -544,8 +646,11 @@ When testing a Spring MVC application with a MockMvc server setup, you have the choice to perform further assertions on the server response. To do that start by obtaining an `ExchangeResult` after asserting the body: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // For a response with a body EntityExchangeResult result = client.get().uri("/persons/1") @@ -559,8 +664,10 @@ obtaining an `ExchangeResult` after asserting the body: .exchange() .expectBody().isEmpty(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // For a response with a body val result = client.get().uri("/persons/1") @@ -574,21 +681,28 @@ obtaining an `ExchangeResult` after asserting the body: .exchange() .expectBody().isEmpty(); ---- +====== Then switch to MockMvc server response assertions: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - MockMvcWebTestClient.resultActionsFor(result) - .andExpect(model().attribute("integer", 3)) - .andExpect(model().attribute("string", "a string value")); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- MockMvcWebTestClient.resultActionsFor(result) .andExpect(model().attribute("integer", 3)) .andExpect(model().attribute("string", "a string value")); ---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + MockMvcWebTestClient.resultActionsFor(result) + .andExpect(model().attribute("integer", 3)) + .andExpect(model().attribute("string", "a string value")); +---- +====== + diff --git a/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc b/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc index 8ee097700de..9c63eed3a29 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc @@ -84,8 +84,11 @@ annotation enables cross-origin requests on annotated controller methods, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController @RequestMapping("/account") @@ -103,8 +106,10 @@ following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController @RequestMapping("/account") @@ -122,6 +127,7 @@ following example shows: } } ---- +====== -- By default, `@CrossOrigin` allows: @@ -142,8 +148,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig The following example specifies a certain domain and sets `maxAge` to an hour: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @CrossOrigin(origins = "https://domain2.com", maxAge = 3600) @RestController @@ -161,8 +170,10 @@ The following example specifies a certain domain and sets `maxAge` to an hour: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @CrossOrigin("https://domain2.com", maxAge = 3600) @RestController @@ -180,14 +191,18 @@ The following example specifies a certain domain and sets `maxAge` to an hour: } } ---- +====== -- You can use `@CrossOrigin` at both the class and the method level, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @CrossOrigin(maxAge = 3600) // <1> @RestController @@ -206,6 +221,7 @@ as the following example shows: } } ---- +====== <1> Using `@CrossOrigin` at the class level. <2> Using `@CrossOrigin` at the method level. @@ -261,8 +277,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig To enable CORS in the WebFlux Java configuration, you can use the `CorsRegistry` callback, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -282,8 +301,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -302,6 +323,7 @@ as the following example shows: } } ---- +====== @@ -321,8 +343,11 @@ CORS. To configure the filter, you can declare a `CorsWebFilter` bean and pass a `CorsConfigurationSource` to its constructor, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Bean CorsWebFilter corsFilter() { @@ -343,8 +368,10 @@ To configure the filter, you can declare a `CorsWebFilter` bean and pass a return new CorsWebFilter(source); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Bean fun corsFilter(): CorsWebFilter { @@ -365,3 +392,4 @@ To configure the filter, you can declare a `CorsWebFilter` bean and pass a return CorsWebFilter(source) } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc b/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc index daba67c2902..d0ed6a69d07 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc @@ -30,8 +30,11 @@ difference that router functions provide not just data, but also behavior. `RouterFunctions.route()` provides a router builder that facilitates the creation of routers, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; @@ -64,6 +67,7 @@ as the following example shows: } } ---- +====== <1> Create router using `route()`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -133,80 +137,113 @@ while access to the body is provided through the `body` methods. The following example extracts the request body to a `Mono`: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Mono string = request.bodyToMono(String.class); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val string = request.awaitBody() ---- +====== The following example extracts the body to a `Flux` (or a `Flow` in Kotlin), where `Person` objects are decoded from some serialized form, such as JSON or XML: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Flux people = request.bodyToFlux(Person.class); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val people = request.bodyToFlow() ---- +====== The preceding examples are shortcuts that use the more general `ServerRequest.body(BodyExtractor)`, which accepts the `BodyExtractor` functional strategy interface. The utility class `BodyExtractors` provides access to a number of instances. For example, the preceding examples can also be written as follows: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Mono string = request.body(BodyExtractors.toMono(String.class)); Flux people = request.body(BodyExtractors.toFlux(Person.class)); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle() val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow() ---- +====== The following example shows how to access form data: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Mono> map = request.formData(); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val map = request.awaitFormData() ---- +====== The following example shows how to access multipart data as a map: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Mono> map = request.multipartData(); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val map = request.awaitMultipartData() ---- +====== The following example shows how to access multipart data, one at a time, in streaming fashion: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Flux allPartEvents = request.bodyToFlux(PartEvent.class); allPartsEvents.windowUntil(PartEvent::isLast) @@ -232,8 +269,9 @@ allPartsEvents.windowUntil(PartEvent::isLast) })); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val parts = request.bodyToFlux() allPartsEvents.windowUntil(PartEvent::isLast) @@ -258,6 +296,7 @@ allPartsEvents.windowUntil(PartEvent::isLast) } } ---- +====== Note that the body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks. @@ -269,47 +308,65 @@ a `build` method to create it. You can use the builder to set the response statu headers, or to provide a body. The following example creates a 200 (OK) response with JSON content: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Mono person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val person: Person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person) ---- +====== The following example shows how to build a 201 (CREATED) response with a `Location` header and no body: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- URI location = ... ServerResponse.created(location).build(); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val location: URI = ... ServerResponse.created(location).build() ---- +====== Depending on the codec used, it is possible to pass hint parameters to customize how the body is serialized or deserialized. For example, to specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON view]: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...) ---- +====== [[webflux-fn-handler-classes]] @@ -318,17 +375,23 @@ ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::cla We can write a handler function as a lambda, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HandlerFunction helloWorld = request -> ServerResponse.ok().bodyValue("Hello World"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val helloWorld = HandlerFunction { ServerResponse.ok().bodyValue("Hello World") } ---- +====== -- That is convenient, but in an application we need multiple functions, and multiple inline @@ -338,8 +401,11 @@ has a similar role as `@Controller` in an annotation-based application. For example, the following class exposes a reactive `Person` repository: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.ServerResponse.ok; @@ -370,6 +436,7 @@ 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. @@ -422,8 +489,11 @@ A functional endpoint can use Spring's xref:web/webmvc/mvc-config/validation.ado apply validation to the request body. For example, given a custom Spring xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Person`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class PersonHandler { @@ -445,6 +515,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Pers } } ---- +====== <1> Create `Validator` instance. <2> Apply validation. <3> Raise exception for a 400 response. @@ -515,15 +586,20 @@ and so on. The following example uses a request predicate to create a constraint based on the `Accept` header: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = RouterFunctions.route() .GET("/hello-world", accept(MediaType.TEXT_PLAIN), request -> ServerResponse.ok().bodyValue("Hello World")).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val route = coRouter { GET("/hello-world", accept(TEXT_PLAIN)) { @@ -531,6 +607,7 @@ header: } } ---- +====== You can compose multiple request predicates together by using: @@ -568,8 +645,11 @@ There are also other ways to compose multiple router functions together: The following example shows the composition of four routes: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; @@ -586,6 +666,7 @@ RouterFunction 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 @@ -630,8 +711,11 @@ this duplication by using a type-level `@RequestMapping` annotation that maps to router function builder. For instance, the last few lines of the example above can be improved in the following way by using nested routes: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = route() .path("/person", builder -> builder // <1> @@ -640,6 +724,7 @@ RouterFunction route = route() .POST(handler::createPerson)) .build(); ---- +====== <1> Note that second parameter of `path` is a consumer that takes the router builder. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -660,8 +745,11 @@ the `nest` method on the builder. The above still contains some duplication in the form of the shared `Accept`-header predicate. We can further improve by using the `nest` method together with `accept`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = route() .path("/person", b1 -> b1 @@ -671,8 +759,10 @@ We can further improve by using the `nest` method together with `accept`: .POST(handler::createPerson)) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val route = coRouter { "/person".nest { @@ -684,6 +774,7 @@ We can further improve by using the `nest` method together with `accept`: } } ---- +====== [[webflux-fn-running]] @@ -721,8 +812,11 @@ starter. The following example shows a WebFlux Java configuration (see xref:web/webflux/dispatcher-handler.adoc[DispatcherHandler] for how to run it): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -756,8 +850,10 @@ xref:web/webflux/dispatcher-handler.adoc[DispatcherHandler] for how to run it): } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -788,6 +884,7 @@ xref:web/webflux/dispatcher-handler.adoc[DispatcherHandler] for how to run it): } } ---- +====== @@ -803,8 +900,11 @@ The filter will apply to all routes that are built by the builder. This means that filters defined in nested routes do not apply to "top-level" routes. For instance, consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = route() .path("/person", b1 -> b1 @@ -818,6 +918,7 @@ For instance, consider the following example: .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. @@ -853,8 +954,11 @@ Now we can add a simple security filter to our route, assuming that we have a `S can determine whether a particular path is allowed. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- SecurityManager securityManager = ... @@ -874,8 +978,10 @@ The following example shows how to do so: }) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val securityManager: SecurityManager = ... @@ -895,6 +1001,7 @@ The following example shows how to do so: } } ---- +====== The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional. We only let the handler function be run when access is allowed. diff --git a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc index 6122ef22bd1..491a064f3ff 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc @@ -47,8 +47,11 @@ integration for using Spring WebFlux with FreeMarker templates. The following example shows how to configure FreeMarker as a view technology: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -69,8 +72,10 @@ The following example shows how to configure FreeMarker as a view technology: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -88,6 +93,7 @@ The following example shows how to configure FreeMarker as a view technology: } } ---- +====== Your templates need to be stored in the directory specified by the `FreeMarkerConfigurer`, shown in the preceding example. Given the preceding configuration, if your controller @@ -106,8 +112,11 @@ properties on the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property a `java.util.Properties` object, and the `freemarkerVariables` property requires a `java.util.Map`. The following example shows how to use a `FreeMarkerConfigurer`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -127,8 +136,10 @@ a `java.util.Properties` object, and the `freemarkerVariables` property requires } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -143,6 +154,7 @@ a `java.util.Properties` object, and the `freemarkerVariables` property requires } } ---- +====== See the FreeMarker documentation for details of settings and variables as they apply to the `Configuration` object. @@ -245,8 +257,11 @@ You can declare a `ScriptTemplateConfigurer` bean to specify the script engine t the script files to load, what function to call to render templates, and so on. The following example uses Mustache templates and the Nashorn JavaScript engine: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -268,8 +283,10 @@ The following example uses Mustache templates and the Nashorn JavaScript engine: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -288,6 +305,7 @@ The following example uses Mustache templates and the Nashorn JavaScript engine: } } ---- +====== The `render` function is called with the following parameters: @@ -307,8 +325,11 @@ https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some browser facilities not available in the server-side script engine. The following example shows how to set a custom render function: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -330,8 +351,10 @@ The following example shows how to set a custom render function: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -350,6 +373,7 @@ The following example shows how to set a custom render function: } } ---- +====== NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe script engines with templating libraries not designed for concurrency, such as Handlebars or diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-attributes.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-attributes.adoc index 78fc4db2a52..1683021b269 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-attributes.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-attributes.adoc @@ -5,8 +5,11 @@ You can add attributes to a request. This is convenient if you want to pass info through the filter chain and influence the behavior of filters for a given request. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient client = WebClient.builder() .filter((request, next) -> { @@ -22,8 +25,10 @@ For example: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = WebClient.builder() .filter { request, _ -> @@ -37,6 +42,7 @@ For example: .retrieve() .awaitBody() ---- +====== Note that you can configure a `defaultRequest` callback globally at the `WebClient.Builder` level which lets you insert attributes into all requests, diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-body.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-body.adoc index 900041278a7..4419eaa296f 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-body.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-body.adoc @@ -4,8 +4,11 @@ The request body can be encoded from any asynchronous type handled by `ReactiveAdapterRegistry`, like `Mono` or Kotlin Coroutines `Deferred` as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono personMono = ... ; @@ -16,8 +19,10 @@ like `Mono` or Kotlin Coroutines `Deferred` as the following example shows: .retrieve() .bodyToMono(Void.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val personDeferred: Deferred = ... @@ -28,11 +33,15 @@ like `Mono` or Kotlin Coroutines `Deferred` as the following example shows: .retrieve() .awaitBody() ---- +====== You can also have a stream of objects be encoded, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Flux personFlux = ... ; @@ -43,8 +52,10 @@ You can also have a stream of objects be encoded, as the following example shows .retrieve() .bodyToMono(Void.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val people: Flow = ... @@ -55,12 +66,16 @@ You can also have a stream of objects be encoded, as the following example shows .retrieve() .awaitBody() ---- +====== Alternatively, if you have the actual value, you can use the `bodyValue` shortcut method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Person person = ... ; @@ -71,8 +86,10 @@ as the following example shows: .retrieve() .bodyToMono(Void.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val person: Person = ... @@ -83,6 +100,7 @@ as the following example shows: .retrieve() .awaitBody() ---- +====== @@ -93,8 +111,11 @@ To send form data, you can provide a `MultiValueMap` as the body content is automatically set to `application/x-www-form-urlencoded` by the `FormHttpMessageWriter`. The following example shows how to use `MultiValueMap`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- MultiValueMap formData = ... ; @@ -104,8 +125,10 @@ content is automatically set to `application/x-www-form-urlencoded` by the .retrieve() .bodyToMono(Void.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val formData: MultiValueMap = ... @@ -115,11 +138,15 @@ content is automatically set to `application/x-www-form-urlencoded` by the .retrieve() .awaitBody() ---- +====== You can also supply form data in-line by using `BodyInserters`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.web.reactive.function.BodyInserters.*; @@ -129,8 +156,10 @@ You can also supply form data in-line by using `BodyInserters`, as the following .retrieve() .bodyToMono(Void.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.reactive.function.BodyInserters.* @@ -140,6 +169,7 @@ You can also supply form data in-line by using `BodyInserters`, as the following .retrieve() .awaitBody() ---- +====== @@ -151,8 +181,11 @@ either `Object` instances that represent part content or `HttpEntity` instances headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a multipart request. The following example shows how to create a `MultiValueMap`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- MultipartBodyBuilder builder = new MultipartBodyBuilder(); builder.part("fieldPart", "fieldValue"); @@ -162,8 +195,10 @@ multipart request. The following example shows how to create a `MultiValueMap> parts = builder.build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val builder = MultipartBodyBuilder().apply { part("fieldPart", "fieldValue") @@ -174,6 +209,7 @@ multipart request. The following example shows how to create a `MultiValueMap() ---- +====== If the `MultiValueMap` contains at least one non-`String` value, which could also represent regular form data (that is, `application/x-www-form-urlencoded`), you need not @@ -215,8 +257,11 @@ set the `Content-Type` to `multipart/form-data`. This is always the case when us As an alternative to `MultipartBodyBuilder`, you can also provide multipart content, inline-style, through the built-in `BodyInserters`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.web.reactive.function.BodyInserters.*; @@ -226,8 +271,10 @@ inline-style, through the built-in `BodyInserters`, as the following example sho .retrieve() .bodyToMono(Void.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.reactive.function.BodyInserters.* @@ -237,6 +284,7 @@ inline-style, through the built-in `BodyInserters`, as the following example sho .retrieve() .awaitBody() ---- +====== [[partevent]] === `PartEvent` @@ -252,8 +300,11 @@ the `WebClient`. For instance, this sample will POST a multipart form containing a form field and a file. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Resource resource = ... Mono result = webClient @@ -266,8 +317,10 @@ Mono result = webClient .retrieve() .bodyToMono(String.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- var resource: Resource = ... var result: Mono = webClient @@ -282,6 +335,7 @@ var result: Mono = webClient .retrieve() .bodyToMono() ---- +====== On the server side, `PartEvent` objects that are received via `@RequestBody` or `ServerRequest::bodyToFlux(PartEvent.class)` can be relayed to another service diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-builder.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-builder.adoc index 3b4b44e64d6..08d788f0e04 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-builder.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-builder.adoc @@ -21,26 +21,35 @@ You can also use `WebClient.builder()` with further options: For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient client = WebClient.builder() .codecs(configurer -> ... ) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val webClient = WebClient.builder() .codecs { configurer -> ... } .build() ---- +====== Once built, a `WebClient` is immutable. However, you can clone it and build a modified copy as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient client1 = WebClient.builder() .filter(filterA).filter(filterB).build(); @@ -52,8 +61,10 @@ modified copy as follows: // client2 has filterA, filterB, filterC, filterD ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client1 = WebClient.builder() .filter(filterA).filter(filterB).build() @@ -65,6 +76,7 @@ modified copy as follows: // client2 has filterA, filterB, filterC, filterD ---- +====== [[webflux-client-builder-maxinmemorysize]] == MaxInMemorySize @@ -79,20 +91,26 @@ org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on m To change the limit for default codecs, use the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient webClient = WebClient.builder() .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val webClient = WebClient.builder() .codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) } .build() ---- +====== @@ -101,8 +119,11 @@ To change the limit for default codecs, use the following: To customize Reactor Netty settings, provide a pre-configured `HttpClient`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...); @@ -110,8 +131,10 @@ To customize Reactor Netty settings, provide a pre-configured `HttpClient`: .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val httpClient = HttpClient.create().secure { ... } @@ -119,6 +142,7 @@ To customize Reactor Netty settings, provide a pre-configured `HttpClient`: .clientConnector(ReactorClientHttpConnector(httpClient)) .build() ---- +====== [[webflux-client-builder-reactor-resources]] @@ -137,20 +161,26 @@ Netty global resources are shut down when the Spring `ApplicationContext` is clo as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Bean public ReactorResourceFactory reactorResourceFactory() { return new ReactorResourceFactory(); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Bean fun reactorResourceFactory() = ReactorResourceFactory() ---- +====== -- You can also choose not to participate in the global Reactor Netty resources. However, @@ -158,8 +188,11 @@ in this mode, the burden is on you to ensure that all Reactor Netty client and s instances use shared resources, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Bean public ReactorResourceFactory resourceFactory() { @@ -181,6 +214,7 @@ instances use shared resources, as the following example shows: 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`. @@ -216,8 +250,11 @@ instances use shared resources, as the following example shows: To configure a connection timeout: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import io.netty.channel.ChannelOption; @@ -228,8 +265,10 @@ To configure a connection timeout: .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import io.netty.channel.ChannelOption @@ -240,11 +279,15 @@ To configure a connection timeout: .clientConnector(ReactorClientHttpConnector(httpClient)) .build(); ---- +====== To configure a read or write timeout: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; @@ -257,8 +300,10 @@ To configure a read or write timeout: // Create WebClient... ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import io.netty.handler.timeout.ReadTimeoutHandler import io.netty.handler.timeout.WriteTimeoutHandler @@ -271,30 +316,40 @@ To configure a read or write timeout: // Create WebClient... ---- +====== To configure a response timeout for all requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpClient httpClient = HttpClient.create() .responseTimeout(Duration.ofSeconds(2)); // Create WebClient... ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val httpClient = HttpClient.create() .responseTimeout(Duration.ofSeconds(2)); // Create WebClient... ---- +====== To configure a response timeout for a specific request: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient.create().get() .uri("https://example.org/path") @@ -305,8 +360,10 @@ To configure a response timeout for a specific request: .retrieve() .bodyToMono(String.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- WebClient.create().get() .uri("https://example.org/path") @@ -317,6 +374,7 @@ To configure a response timeout for a specific request: .retrieve() .bodyToMono(String::class.java) ---- +====== @@ -325,8 +383,11 @@ To configure a response timeout for a specific request: The following example shows how to customize the JDK `HttpClient`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpClient httpClient = HttpClient.newBuilder() .followRedirects(Redirect.NORMAL) @@ -339,8 +400,9 @@ The following example shows how to customize the JDK `HttpClient`: WebClient webClient = WebClient.builder().clientConnector(connector).build(); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val httpClient = HttpClient.newBuilder() .followRedirects(Redirect.NORMAL) @@ -351,6 +413,7 @@ The following example shows how to customize the JDK `HttpClient`: val webClient = WebClient.builder().clientConnector(connector).build() ---- +====== @@ -360,8 +423,11 @@ The following example shows how to customize the JDK `HttpClient`: The following example shows how to customize Jetty `HttpClient` settings: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpClient httpClient = new HttpClient(); httpClient.setCookieStore(...); @@ -370,8 +436,10 @@ The following example shows how to customize Jetty `HttpClient` settings: .clientConnector(new JettyClientHttpConnector(httpClient)) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val httpClient = HttpClient() httpClient.cookieStore = ... @@ -380,6 +448,7 @@ The following example shows how to customize Jetty `HttpClient` settings: .clientConnector(JettyClientHttpConnector(httpClient)) .build(); ---- +====== -- By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`), @@ -391,8 +460,11 @@ declaring a Spring-managed bean of type `JettyResourceFactory`, as the following shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Bean public JettyResourceFactory resourceFactory() { @@ -411,6 +483,7 @@ shows: return WebClient.builder().clientConnector(connector).build(); <2> } ---- +====== <1> Use the `JettyClientHttpConnector` constructor with resource factory. <2> Plug the connector into the `WebClient.Builder`. @@ -442,8 +515,11 @@ shows: The following example shows how to customize Apache HttpComponents `HttpClient` settings: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom(); clientBuilder.setDefaultRequestConfig(...); @@ -453,8 +529,10 @@ The following example shows how to customize Apache HttpComponents `HttpClient` WebClient webClient = WebClient.builder().clientConnector(connector).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = HttpAsyncClients.custom().apply { setDefaultRequestConfig(...) @@ -462,5 +540,6 @@ The following example shows how to customize Apache HttpComponents `HttpClient` val connector = HttpComponentsClientHttpConnector(client) val webClient = WebClient.builder().clientConnector(connector).build() ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-context.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-context.adoc index 0cb4e619b3c..749517ae205 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-context.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-context.adoc @@ -9,8 +9,11 @@ e.g. via `concatMap`, then you'll need to use the Reactor `Context`. The Reactor `Context` needs to be populated at the end of a reactive chain in order to apply to all operations. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient client = WebClient.builder() .filter((request, next) -> @@ -28,6 +31,7 @@ apply to all operations. For example: }) .contextWrite(context -> context.put("foo", ...)); ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-exchange.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-exchange.adoc index e1f1f397650..83ddb7f3a88 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-exchange.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-exchange.adoc @@ -5,8 +5,11 @@ The `exchangeToMono()` and `exchangeToFlux()` methods (or `awaitExchange { }` an are useful for more advanced cases that require more control, such as to decode the response differently depending on the response status: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono entityMono = client.get() .uri("/persons/1") @@ -21,8 +24,10 @@ depending on the response status: } }); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val entity = client.get() .uri("/persons/1") @@ -36,6 +41,7 @@ val entity = client.get() } } ---- +====== When using the above, after the returned `Mono` or `Flux` completes, the response body is checked and if not consumed it is released to prevent memory and connection leaks. diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-filter.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-filter.adoc index c571ed258cf..4fd49bf8045 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-filter.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-filter.adoc @@ -4,8 +4,11 @@ You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder` in order to intercept and modify requests, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient client = WebClient.builder() .filter((request, next) -> { @@ -18,8 +21,10 @@ in order to intercept and modify requests, as the following example shows: }) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = WebClient.builder() .filter { request, next -> @@ -32,12 +37,16 @@ in order to intercept and modify requests, as the following example shows: } .build() ---- +====== This can be used for cross-cutting concerns, such as authentication. The following example uses a filter for basic authentication through a static factory method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; @@ -45,8 +54,10 @@ a filter for basic authentication through a static factory method: .filter(basicAuthentication("user", "password")) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication @@ -54,12 +65,16 @@ a filter for basic authentication through a static factory method: .filter(basicAuthentication("user", "password")) .build() ---- +====== Filters can be added or removed by mutating an existing `WebClient` instance, resulting in a new `WebClient` instance that does not affect the original one. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; @@ -69,13 +84,16 @@ in a new `WebClient` instance that does not affect the original one. For example }) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = webClient.mutate() .filters { it.add(0, basicAuthentication("user", "password")) } .build() ---- +====== `WebClient` is a thin facade around the chain of filters followed by an `ExchangeFunction`. It provides a workflow to make requests, to encode to and from higher @@ -85,8 +103,11 @@ its content or to otherwise propagate it downstream to the `WebClient` which wil the same. Below is a filter that handles the `UNAUTHORIZED` status code but ensures that any response content, whether expected or not, is released: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public ExchangeFilterFunction renewTokenFilter() { return (request, next) -> next.exchange(request).flatMap(response -> { @@ -103,8 +124,10 @@ any response content, whether expected or not, is released: }); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun renewTokenFilter(): ExchangeFilterFunction? { return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction -> @@ -123,6 +146,7 @@ any response content, whether expected or not, is released: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-retrieve.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-retrieve.adoc index 500ae9d3a8b..28cb417588a 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-retrieve.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-retrieve.adoc @@ -3,8 +3,11 @@ The `retrieve()` method can be used to declare how to extract the response. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient client = WebClient.create("https://example.org"); @@ -13,8 +16,10 @@ The `retrieve()` method can be used to declare how to extract the response. For .retrieve() .toEntity(Person.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = WebClient.create("https://example.org") @@ -23,11 +28,15 @@ The `retrieve()` method can be used to declare how to extract the response. For .retrieve() .toEntity().awaitSingle() ---- +====== Or to get only the body: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient client = WebClient.create("https://example.org"); @@ -36,8 +45,10 @@ Or to get only the body: .retrieve() .bodyToMono(Person.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = WebClient.create("https://example.org") @@ -46,32 +57,42 @@ Or to get only the body: .retrieve() .awaitBody() ---- +====== To get a stream of decoded objects: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Flux result = client.get() .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlux(Quote.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val result = client.get() .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlow() ---- +====== By default, 4xx or 5xx responses result in an `WebClientResponseException`, including sub-classes for specific HTTP status codes. To customize the handling of error responses, use `onStatus` handlers as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono result = client.get() .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) @@ -80,8 +101,10 @@ responses, use `onStatus` handlers as follows: .onStatus(HttpStatus::is5xxServerError, response -> ...) .bodyToMono(Person.class); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val result = client.get() .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) @@ -90,6 +113,7 @@ responses, use `onStatus` handlers as follows: .onStatus(HttpStatus::is5xxServerError) { ... } .awaitBody() ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-synchronous.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-synchronous.adoc index 07cb96ad72c..7f9c4a0e4ff 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-synchronous.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient/client-synchronous.adoc @@ -3,8 +3,11 @@ `WebClient` can be used in synchronous style by blocking at the end for the result: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Person person = client.get().uri("/person/{id}", i).retrieve() .bodyToMono(Person.class) @@ -15,8 +18,10 @@ .collectList() .block(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val person = runBlocking { client.get().uri("/person/{id}", i).retrieve() @@ -29,12 +34,16 @@ .toList() } ---- +====== However if multiple calls need to be made, it's more efficient to avoid blocking on each response individually, and instead wait for the combined result: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono personMono = client.get().uri("/person/{id}", personId) .retrieve().bodyToMono(Person.class); @@ -50,8 +59,10 @@ response individually, and instead wait for the combined result: }) .block(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val data = runBlocking { val personDeferred = async { @@ -67,6 +78,7 @@ response individually, and instead wait for the combined result: mapOf("person" to personDeferred.await(), "hobbies" to hobbiesDeferred.await()) } ---- +====== The above is merely one example. There are lots of other patterns and operators for putting together a reactive pipeline that makes many remote calls, potentially some nested, diff --git a/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc b/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc index 01f1134dd85..a2f3060cd1e 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc @@ -23,8 +23,11 @@ server-side applications that handle WebSocket messages. To create a WebSocket server, you can first create a `WebSocketHandler`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.web.reactive.socket.WebSocketHandler; import org.springframework.web.reactive.socket.WebSocketSession; @@ -37,8 +40,10 @@ The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.reactive.socket.WebSocketHandler import org.springframework.web.reactive.socket.WebSocketSession @@ -50,11 +55,15 @@ The following example shows how to do so: } } ---- +====== Then you can map it to a URL: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration class WebConfig { @@ -69,8 +78,10 @@ Then you can map it to a URL: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class WebConfig { @@ -84,13 +95,17 @@ Then you can map it to a URL: } } ---- +====== If using the xref:web/webflux/dispatcher-handler.adoc#webflux-framework-config[WebFlux Config] there is nothing further to do, or otherwise if not using the WebFlux config you'll need to declare a `WebSocketHandlerAdapter` as shown below: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration class WebConfig { @@ -103,8 +118,10 @@ further to do, or otherwise if not using the WebFlux config you'll need to decla } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class WebConfig { @@ -115,6 +132,7 @@ further to do, or otherwise if not using the WebFlux config you'll need to decla fun handlerAdapter() = WebSocketHandlerAdapter() } ---- +====== @@ -155,8 +173,11 @@ receives a cancellation signal. The most basic implementation of a handler is one that handles the inbound stream. The following example shows such an implementation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class ExampleHandler implements WebSocketHandler { @@ -173,6 +194,7 @@ following example shows such an implementation: } } ---- +====== <1> Access the stream of inbound messages. <2> Do something with each message. <3> Perform nested asynchronous operations that use the message content. @@ -208,8 +230,11 @@ xref:core/databuffer-codec.adoc[Data Buffers and Codecs]. The following implementation combines the inbound and outbound streams: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class ExampleHandler implements WebSocketHandler { @@ -229,6 +254,7 @@ The following implementation combines the inbound and outbound streams: } } ---- +====== <1> Handle the inbound message stream. <2> Create the outbound message, producing a combined flow. <3> Return a `Mono` that does not complete while we continue to receive. @@ -261,8 +287,11 @@ The following implementation combines the inbound and outbound streams: Inbound and outbound streams can be independent and be joined only for completion, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class ExampleHandler implements WebSocketHandler { @@ -285,6 +314,7 @@ as the following example shows: } } ---- +====== <1> Handle inbound message stream. <2> Send outgoing messages. <3> Join the streams and return a `Mono` that completes when either stream ends. @@ -359,8 +389,11 @@ such properties as shown in the corresponding section of the xref:web/webflux/config.adoc#webflux-config-websocket-service[WebFlux Config], or otherwise if not using the WebFlux config, use the below: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration class WebConfig { @@ -378,8 +411,10 @@ not using the WebFlux config, use the below: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class WebConfig { @@ -397,6 +432,7 @@ not using the WebFlux config, use the below: } } ---- +====== Check the upgrade strategy for your server to see what options are available. Currently, only Tomcat and Jetty expose such options. @@ -429,8 +465,11 @@ API to suspend receiving messages for back pressure. To start a WebSocket session, you can create an instance of the client and use its `execute` methods: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebSocketClient client = new ReactorNettyWebSocketClient(); @@ -440,8 +479,10 @@ methods: .doOnNext(System.out::println) .then()); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val client = ReactorNettyWebSocketClient() @@ -452,6 +493,7 @@ methods: .then() } ---- +====== Some clients, such as Jetty, implement `Lifecycle` and need to be stopped and started before you can use them. All clients have constructor options related to configuration diff --git a/framework-docs/modules/ROOT/pages/web/webflux/caching.adoc b/framework-docs/modules/ROOT/pages/web/webflux/caching.adoc index 0c5c120714b..8ebcf5cf59d 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/caching.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/caching.adoc @@ -30,8 +30,11 @@ While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234] describes all directives for the `Cache-Control` response header, the `CacheControl` type takes a use case-oriented approach that focuses on the common scenarios, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Cache for an hour - "Cache-Control: max-age=3600" CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); @@ -45,8 +48,9 @@ use case-oriented approach that focuses on the common scenarios, as the followin CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic(); ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Cache for an hour - "Cache-Control: max-age=3600" val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS) @@ -60,6 +64,7 @@ use case-oriented approach that focuses on the common scenarios, as the followin val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic() ---- +====== @@ -73,8 +78,11 @@ against conditional request headers. A controller can add an `ETag` and `Cache-C settings to a `ResponseEntity`, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/book/{id}") public ResponseEntity showBook(@PathVariable Long id) { @@ -90,8 +98,9 @@ settings to a `ResponseEntity`, as the following example shows: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/book/{id}") fun showBook(@PathVariable id: Long): ResponseEntity { @@ -106,6 +115,7 @@ settings to a `ResponseEntity`, as the following example shows: .body(book) } ---- +====== -- The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison @@ -116,8 +126,11 @@ You can also make the check against conditional request headers in the controlle as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RequestMapping public String myHandleMethod(ServerWebExchange exchange, Model model) { @@ -132,6 +145,7 @@ as the following example shows: return "myViewName"; } ---- +====== <1> Application-specific calculation. <2> Response has been set to 304 (NOT_MODIFIED). No further processing. <3> Continue with request processing. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc index 4ee87320682..10e87907b6d 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc @@ -22,8 +22,11 @@ xref:web/webflux/config.adoc#webflux-config-advanced-java[Advanced Configuration You can use the `@EnableWebFlux` annotation in your Java config, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -31,13 +34,15 @@ You can use the `@EnableWebFlux` annotation in your Java config, as the followin } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux class WebConfig ---- +====== The preceding example registers a number of Spring WebFlux xref:web/webflux/dispatcher-handler.adoc#webflux-special-bean-types[infrastructure beans] and adapts to dependencies @@ -52,8 +57,11 @@ available on the classpath -- for JSON, XML, and others. In your Java configuration, you can implement the `WebFluxConfigurer` interface, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -63,8 +71,9 @@ as the following example shows: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -73,6 +82,7 @@ class WebConfig : WebFluxConfigurer { // Implement configuration methods... } ---- +====== @@ -85,8 +95,11 @@ for customization via `@NumberFormat` and `@DateTimeFormat` on fields. To register custom formatters and converters in Java config, use the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -99,8 +112,10 @@ To register custom formatters and converters in Java config, use the following: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -111,14 +126,18 @@ To register custom formatters and converters in Java config, use the following: } } ---- +====== By default Spring WebFlux considers the request Locale when parsing and formatting date values. This works for forms where dates are represented as Strings with "input" form fields. For "date" and "time" form fields, however, browsers use a fixed format defined in the HTML spec. For such cases date and time formatting can be customized as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -132,8 +151,10 @@ in the HTML spec. For such cases date and time formatting can be customized as f } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -146,6 +167,7 @@ in the HTML spec. For such cases date and time formatting can be customized as f } } ---- +====== NOTE: See xref:core/validation/format.adoc#format-FormatterRegistrar-SPI[`FormatterRegistrar` SPI] and the `FormattingConversionServiceFactoryBean` for more information on when to @@ -165,8 +187,11 @@ is registered as a global xref:core/validation/validator.adoc[validator] for use In your Java configuration, you can customize the global `Validator` instance, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -179,8 +204,10 @@ as the following example shows: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -192,12 +219,16 @@ as the following example shows: } ---- +====== Note that you can also register `Validator` implementations locally, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class MyController { @@ -209,8 +240,10 @@ as the following example shows: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class MyController { @@ -221,6 +254,7 @@ as the following example shows: } } ---- +====== TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and @@ -238,8 +272,11 @@ but you can also enable a query parameter-based strategy. The following example shows how to customize the requested content type resolution: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -251,8 +288,10 @@ The following example shows how to customize the requested content type resoluti } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -263,6 +302,7 @@ The following example shows how to customize the requested content type resoluti } } ---- +====== @@ -272,8 +312,11 @@ The following example shows how to customize the requested content type resoluti The following example shows how to customize how the request and response body are read and written: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -285,8 +328,10 @@ The following example shows how to customize how the request and response body a } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -297,6 +342,7 @@ The following example shows how to customize how the request and response body a } } ---- +====== `ServerCodecConfigurer` provides a set of default readers and writers. You can use it to add more readers and writers, customize the default ones, or replace the default ones completely. @@ -323,8 +369,11 @@ It also automatically registers the following well-known modules if they are det The following example shows how to configure view resolution: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -336,8 +385,10 @@ The following example shows how to configure view resolution: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -348,13 +399,17 @@ The following example shows how to configure view resolution: } } ---- +====== The `ViewResolverRegistry` has shortcuts for view technologies with which the Spring Framework integrates. The following example uses FreeMarker (which also requires configuring the underlying FreeMarker view technology): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -376,8 +431,10 @@ underlying FreeMarker view technology): } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -395,11 +452,15 @@ underlying FreeMarker view technology): } } ---- +====== You can also plug in any `ViewResolver` implementation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -413,8 +474,10 @@ You can also plug in any `ViewResolver` implementation, as the following example } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -426,14 +489,18 @@ You can also plug in any `ViewResolver` implementation, as the following example } } ---- +====== To support xref:web/webflux/dispatcher-handler.adoc#webflux-multiple-representations[Content Negotiation] and rendering other formats through view resolution (besides HTML), you can configure one or more default views based on the `HttpMessageWriterView` implementation, which accepts any of the available xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from `spring-web`. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -451,8 +518,10 @@ xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from `spring-web`. // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -469,6 +538,7 @@ xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from `spring-web`. // ... } ---- +====== See xref:web/webflux-view.adoc[View Technologies] for more on the view technologies that are integrated with Spring WebFlux. @@ -488,8 +558,11 @@ and a reduction in HTTP requests made by the browser. The `Last-Modified` header evaluated and, if present, a `304` status code is returned. The following listing shows the example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -504,8 +577,10 @@ the example: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -518,6 +593,7 @@ the example: } } ---- +====== See also xref:web/webflux/caching.adoc#webflux-caching-static-resources[HTTP caching support for static resources]. @@ -533,8 +609,11 @@ JavaScript resources used with a module loader). The following example shows how to use `VersionResourceResolver` in your Java configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -550,8 +629,10 @@ The following example shows how to use `VersionResourceResolver` in your Java co } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -566,6 +647,7 @@ The following example shows how to use `VersionResourceResolver` in your Java co } ---- +====== You can use `ResourceUrlProvider` to rewrite URLs and apply the full chain of resolvers and transformers (for example, to insert versions). The WebFlux configuration provides a `ResourceUrlProvider` @@ -606,8 +688,11 @@ You can customize options related to path matching. For details on the individua {api-spring-framework}/web/reactive/config/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc. The following example shows how to use `PathMatchConfigurer`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -621,8 +706,10 @@ The following example shows how to use `PathMatchConfigurer`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -636,6 +723,7 @@ The following example shows how to use `PathMatchConfigurer`: } } ---- +====== [TIP] ==== @@ -664,8 +752,11 @@ In some cases it may be necessary to create the `WebSocketHandlerAdapter` bean w provided `WebSocketService` service which allows configuring WebSocket server properties. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -679,8 +770,10 @@ For example: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -695,6 +788,7 @@ For example: } } ---- +====== @@ -713,8 +807,11 @@ For advanced mode, you can remove `@EnableWebFlux` and extend directly from `DelegatingWebFluxConfiguration` instead of implementing `WebFluxConfigurer`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class WebConfig extends DelegatingWebFluxConfiguration { @@ -722,8 +819,10 @@ as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class WebConfig : DelegatingWebFluxConfiguration { @@ -731,6 +830,7 @@ as the following example shows: // ... } ---- +====== You can keep existing methods in `WebConfig`, but you can now also override bean declarations from the base class and still have any number of other `WebMvcConfigurer` implementations on diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller.adoc index 32d1a1fb60f..5db87830f5a 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller.adoc @@ -10,8 +10,11 @@ do not have to extend base classes nor implement specific interfaces. The following listing shows a basic example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController public class HelloController { @@ -22,8 +25,10 @@ The following listing shows a basic example: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController class HelloController { @@ -32,6 +37,7 @@ The following listing shows a basic example: fun handle() = "Hello WebFlux" } ---- +====== In the preceding example, the method returns a `String` to be written to the response body. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-advice.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-advice.adoc index 1ad6529c068..36242536928 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-advice.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-advice.adoc @@ -25,8 +25,11 @@ By default, `@ControllerAdvice` methods apply to every request (that is, all con but you can narrow that down to a subset of controllers by using attributes on the annotation, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Target all Controllers annotated with @RestController @ControllerAdvice(annotations = RestController.class) @@ -41,8 +44,9 @@ annotation, as the following example shows: public class ExampleAdvice3 {} ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Target all Controllers annotated with @RestController @ControllerAdvice(annotations = [RestController::class]) @@ -56,6 +60,7 @@ annotation, as the following example shows: @ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class]) public class ExampleAdvice3 {} ---- +====== The selectors in the preceding example are evaluated at runtime and may negatively impact performance if used extensively. See the diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-exceptions.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-exceptions.adoc index ab7c3ea2e36..078942e9c9c 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-exceptions.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-exceptions.adoc @@ -7,8 +7,11 @@ `@ExceptionHandler` methods to handle exceptions from controller methods. The following example includes such a handler method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class SimpleController { @@ -21,6 +24,7 @@ example includes such a handler method: } } ---- +====== <1> Declaring an `@ExceptionHandler`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc index 2c523e925fd..c0a369778ad 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc @@ -22,8 +22,11 @@ with a `WebDataBinder` argument, for registrations, and a `void` return value. The following example uses the `@InitBinder` annotation: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class FormController { @@ -38,6 +41,7 @@ The following example uses the `@InitBinder` annotation: // ... } ---- +====== <1> Using the `@InitBinder` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -64,8 +68,11 @@ Alternatively, when using a `Formatter`-based setup through a shared controller-specific `Formatter` instances, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class FormController { @@ -78,6 +85,7 @@ controller-specific `Formatter` instances, as the following example shows: // ... } ---- +====== <1> Adding a custom formatter (a `DateFormatter`, in this case). [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/cookievalue.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/cookievalue.adoc index a90cf11ad71..639273e645a 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/cookievalue.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/cookievalue.adoc @@ -15,14 +15,18 @@ JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 The following code sample demonstrates how to get the cookie value: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/demo") public void handle(@CookieValue("JSESSIONID") String cookie) { // <1> //... } ---- +====== <1> Get the cookie value. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/httpentity.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/httpentity.adoc index 1b326645c8b..1c9d77d8494 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/httpentity.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/httpentity.adoc @@ -7,21 +7,27 @@ container object that exposes request headers and the body. The following example uses an `HttpEntity`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/accounts") public void handle(HttpEntity entity) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/accounts") fun handle(entity: HttpEntity) { // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/jackson.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/jackson.adoc index ee443c1d7b0..fe3db7c7d56 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/jackson.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/jackson.adoc @@ -13,8 +13,11 @@ which allows rendering only a subset of all fields in an `Object`. To use it wit `@ResponseBody` or `ResponseEntity` controller methods, you can use Jackson's `@JsonView` annotation to activate a serialization view class, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController public class UserController { @@ -54,8 +57,9 @@ which allows rendering only a subset of all fields in an `Object`. To use it wit } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController class UserController { @@ -75,6 +79,7 @@ which allows rendering only a subset of all fields in an `Object`. To use it wit interface WithPasswordView : WithoutPasswordView } ---- +====== NOTE: `@JsonView` allows an array of view classes but you can only specify only one per controller method. Use a composite interface if you need to activate multiple views. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/matrix-variables.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/matrix-variables.adoc index f37d3830f56..b5a5bd9753c 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/matrix-variables.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/matrix-variables.adoc @@ -19,8 +19,11 @@ to mask variable content. That said, if you want to access matrix variables from controller method, you need to add a URI variable to the path segment where matrix variables are expected. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /pets/42;q=11;r=22 @@ -31,8 +34,10 @@ variables are expected. The following example shows how to do so: // q == 11 } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // GET /pets/42;q=11;r=22 @@ -43,14 +48,18 @@ variables are expected. The following example shows how to do so: // q == 11 } ---- +====== Given that all path segments can contain matrix variables, you may sometimes need to disambiguate which path variable the matrix variable is expected to be in, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /owners/42;q=11/pets/21;q=22 @@ -63,8 +72,10 @@ as the following example shows: // q2 == 22 } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/owners/{ownerId}/pets/{petId}") fun findPet( @@ -75,12 +86,16 @@ as the following example shows: // q2 == 22 } ---- +====== You can define a matrix variable may be defined as optional and specify a default value as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /pets/42 @@ -90,8 +105,10 @@ as the following example shows: // q == 1 } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // GET /pets/42 @@ -101,11 +118,15 @@ as the following example shows: // q == 1 } ---- +====== To get all matrix variables, use a `MultiValueMap`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @@ -118,8 +139,10 @@ To get all matrix variables, use a `MultiValueMap`, as the following example sho // petMatrixVars: ["q" : 22, "s" : 23] } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @@ -132,5 +155,6 @@ To get all matrix variables, use a `MultiValueMap`, as the following example sho // petMatrixVars: ["q" : 22, "s" : 23] } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc index f2d234a0b1d..cf92220ce28 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc @@ -9,12 +9,16 @@ the values of query parameters and form fields whose names match to field names. referred to as data binding, and it saves you from having to deal with parsing and converting individual query parameters and form fields. The following example binds an instance of `Pet`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute Pet pet) { } // <1> ---- +====== <1> Bind an instance of `Pet`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -45,8 +49,11 @@ Data binding can result in errors. By default, a `WebExchangeBindException` is r to check for such errors in the controller method, you can add a `BindingResult` argument immediately next to the `@ModelAttribute`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { <1> @@ -56,6 +63,7 @@ immediately next to the `@ModelAttribute`, as the following example shows: // ... } ---- +====== <1> Adding a `BindingResult`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -76,8 +84,11 @@ You can automatically apply validation after data binding by adding the xref:core/validation/beanvalidation.adoc[Bean Validation] and xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following example uses the `@Valid` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { // <1> @@ -87,6 +98,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following ex // ... } ---- +====== <1> Using `@Valid` on a model attribute argument. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -110,8 +122,11 @@ argument, you must declare the `@ModelAttribute` argument before it without a re type wrapper, as shown earlier. Alternatively, you can handle any errors through the reactive type, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public Mono processSubmit(@Valid @ModelAttribute("pet") Mono petMono) { @@ -124,8 +139,10 @@ reactive type, as the following example shows: }); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") fun processSubmit(@Valid @ModelAttribute("pet") petMono: Mono): Mono { @@ -138,6 +155,7 @@ reactive type, as the following example shows: } } ---- +====== Note that use of `@ModelAttribute` is optional -- for example, to set its attributes. By default, any argument that is not a simple value type (as determined by diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc index 489c6ec2d8f..18eb97cf45d 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc @@ -9,8 +9,11 @@ is through data binding to a xref:web/webflux/controller/ann-methods/modelattrib as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class MyForm { @@ -32,8 +35,10 @@ as the following example shows: } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyForm( val name: String, @@ -49,6 +54,7 @@ as the following example shows: } ---- +====== -- You can also submit multipart requests from non-browser clients in a RESTful service @@ -77,8 +83,11 @@ Content-Transfer-Encoding: 8bit You can access individual parts with `@RequestPart`, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/") public String handle(@RequestPart("meta-data") Part metadata, // <1> @@ -86,6 +95,7 @@ You can access individual parts with `@RequestPart`, as the following example sh // ... } ---- +====== <1> Using `@RequestPart` to get the metadata. <2> Using `@RequestPart` to get the file. @@ -107,14 +117,18 @@ To deserialize the raw part content (for example, to JSON -- similar to `@Reques you can declare a concrete target `Object`, instead of `Part`, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/") public String handle(@RequestPart("meta-data") MetaData metadata) { // <1> // ... } ---- +====== <1> Using `@RequestPart` to get the metadata. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -136,8 +150,11 @@ in the controller method by declaring the argument with an async wrapper and the error related operators: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/") public String handle(@Valid @RequestPart("meta-data") Mono metadata) { @@ -145,28 +162,34 @@ error related operators: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/") fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String { // ... } ---- +====== -- To access all multipart data as a `MultiValueMap`, you can use `@RequestBody`, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/") public String handle(@RequestBody Mono> parts) { // <1> // ... } ---- +====== <1> Using `@RequestBody`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -196,8 +219,11 @@ when uploading. If the file is large enough to be split across multiple buffers, For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/") public void handle(@RequestBody Flux allPartsEvents) { <1> @@ -224,6 +250,7 @@ For example: })); } ---- +====== <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. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestattrib.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestattrib.adoc index b91d1d18bcc..7eec57f6970 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestattrib.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestattrib.adoc @@ -7,14 +7,18 @@ Similarly to `@SessionAttribute`, you can use the `@RequestAttribute` annotation access pre-existing request attributes created earlier (for example, by a `WebFilter`), as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/") public String handle(@RequestAttribute Client client) { <1> // ... } ---- +====== <1> Using `@RequestAttribute`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc index 40e79bf6ba2..8190c281125 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc @@ -7,8 +7,11 @@ You can use the `@RequestBody` annotation to have the request body read and dese `Object` through an xref:web/webflux/reactive-spring.adoc#webflux-codecs[HttpMessageReader]. The following example uses a `@RequestBody` argument: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/accounts") public void handle(@RequestBody Account account) { @@ -16,34 +19,42 @@ The following example uses a `@RequestBody` argument: } ---- +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/accounts") fun handle(@RequestBody account: Account) { // ... } ---- +====== Unlike Spring MVC, in WebFlux, the `@RequestBody` method argument supports reactive types and fully non-blocking reading and (client-to-server) streaming. +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/accounts") public void handle(@RequestBody Mono account) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/accounts") fun handle(@RequestBody accounts: Flow) { // ... } ---- +====== You can use the xref:web/webflux/config.adoc#webflux-config-message-codecs[HTTP message codecs] option of the xref:web/webflux/dispatcher-handler.adoc#webflux-framework-config[WebFlux Config] to configure or customize message readers. @@ -55,21 +66,27 @@ The exception contains a `BindingResult` with error details and can be handled i controller method by declaring the argument with an async wrapper and then using error related operators: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/accounts") public void handle(@Valid @RequestBody Mono account) { // use one of the onError* operators... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/accounts") fun handle(@Valid @RequestBody account: Mono) { // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestheader.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestheader.adoc index cf08bc24531..e625a46c0f7 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestheader.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestheader.adoc @@ -21,8 +21,11 @@ Keep-Alive 300 The following example gets the value of the `Accept-Encoding` and `Keep-Alive` headers: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/demo") public void handle( @@ -31,6 +34,7 @@ The following example gets the value of the `Accept-Encoding` and `Keep-Alive` h //... } ---- +====== <1> Get the value of the `Accept-Encoding` header. <2> Get the value of the `Keep-Alive` header. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestparam.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestparam.adoc index 64bad1fd03a..b1da2d825ed 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestparam.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestparam.adoc @@ -6,8 +6,11 @@ You can use the `@RequestParam` annotation to bind query parameters to a method argument in a controller. The following code snippet shows the usage: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @RequestMapping("/pets") @@ -25,6 +28,7 @@ controller. The following code snippet shows the usage: // ... } ---- +====== <1> Using `@RequestParam`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responsebody.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responsebody.adoc index ce69b696b01..d6befe47fdc 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responsebody.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responsebody.adoc @@ -7,8 +7,11 @@ You can use the `@ResponseBody` annotation on a method to have the return serial to the response body through an xref:web/webflux/reactive-spring.adoc#webflux-codecs[HttpMessageWriter]. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/accounts/{id}") @ResponseBody @@ -16,8 +19,10 @@ example shows how to do so: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/accounts/{id}") @ResponseBody @@ -25,6 +30,7 @@ example shows how to do so: // ... } ---- +====== `@ResponseBody` is also supported at the class level, in which case it is inherited by all controller methods. This is the effect of `@RestController`, which is nothing more diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responseentity.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responseentity.adoc index 06c68e11125..00de86dad57 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responseentity.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/responseentity.adoc @@ -5,8 +5,11 @@ `ResponseEntity` is like xref:web/webflux/controller/ann-methods/responsebody.adoc[`@ResponseBody`] but with status and headers. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/something") public ResponseEntity handle() { @@ -15,8 +18,10 @@ return ResponseEntity.ok().eTag(etag).body(body); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/something") fun handle(): ResponseEntity { @@ -25,6 +30,7 @@ return ResponseEntity.ok().eTag(etag).build(body) } ---- +====== WebFlux supports using a single value xref:web-reactive.adoc#webflux-reactive-libraries[reactive type] to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive types diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattribute.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattribute.adoc index a205bd26c44..f34751f91b4 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattribute.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattribute.adoc @@ -7,14 +7,18 @@ If you need access to pre-existing session attributes that are managed globally (that is, outside the controller -- for example, by a filter) and may or may not be present, you can use the `@SessionAttribute` annotation on a method parameter, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/") public String handle(@SessionAttribute User user) { // <1> // ... } ---- +====== <1> Using `@SessionAttribute`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattributes.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattributes.adoc index 72fd242c82d..05426026206 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattributes.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/sessionattributes.adoc @@ -11,8 +11,11 @@ requests to access. Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @SessionAttributes("pet") <1> @@ -20,6 +23,7 @@ Consider the following example: // ... } ---- +====== <1> Using the `@SessionAttributes` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -38,8 +42,11 @@ it is automatically promoted to and saved in the `WebSession`. It remains there another controller method uses a `SessionStatus` method argument to clear the storage, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @SessionAttributes("pet") // <1> @@ -58,6 +65,7 @@ as the following example shows: } } ---- +====== <1> Using the `@SessionAttributes` annotation. <2> Using a `SessionStatus` variable. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-modelattrib-methods.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-modelattrib-methods.adoc index 654d0fc9a94..bf8d7afa04e 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-modelattrib-methods.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-modelattrib-methods.adoc @@ -24,8 +24,11 @@ related to the request body). The following example uses a `@ModelAttribute` method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ModelAttribute public void populateModel(@RequestParam String number, Model model) { @@ -33,8 +36,10 @@ The following example uses a `@ModelAttribute` method: // add more ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ModelAttribute fun populateModel(@RequestParam number: String, model: Model) { @@ -42,25 +47,32 @@ The following example uses a `@ModelAttribute` method: // add more ... } ---- +====== The following example adds one attribute only: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ModelAttribute public Account addAccount(@RequestParam String number) { return accountRepository.findAccount(number); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ModelAttribute fun addAccount(@RequestParam number: String): Account { return accountRepository.findAccount(number); } ---- +====== NOTE: When a name is not explicitly specified, a default name is chosen based on the type, as explained in the javadoc for {api-spring-framework}/core/Conventions.html[`Conventions`]. @@ -73,8 +85,11 @@ attributes can be transparently resolved (and the model updated) to their actual at the time of `@RequestMapping` invocation, provided a `@ModelAttribute` argument is declared without a wrapper, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ModelAttribute public void addAccount(@RequestParam String number) { @@ -87,8 +102,10 @@ declared without a wrapper, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.ui.set @@ -103,6 +120,7 @@ declared without a wrapper, as the following example shows: // ... } ---- +====== In addition, any model attributes that have a reactive type wrapper are resolved to their @@ -115,8 +133,11 @@ controllers, unless the return value is a `String` that would otherwise be inter as a view name. `@ModelAttribute` can also help to customize the model attribute name, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/accounts/{id}") @ModelAttribute("myAccount") @@ -125,8 +146,10 @@ as the following example shows: return account; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/accounts/{id}") @ModelAttribute("myAccount") @@ -135,6 +158,7 @@ as the following example shows: return account } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc index 053b7956591..80db7ea5047 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc @@ -23,8 +23,11 @@ using `@RequestMapping`, which, by default, matches to all HTTP methods. At the The following example uses type and method level mappings: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController @RequestMapping("/persons") @@ -42,8 +45,10 @@ The following example uses type and method level mappings: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController @RequestMapping("/persons") @@ -61,6 +66,7 @@ The following example uses type and method level mappings: } } ---- +====== [[webflux-ann-requestmapping-uri-templates]] @@ -106,29 +112,38 @@ You can map requests by using glob patterns and wildcards: Captured URI variables can be accessed with `@PathVariable`, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/owners/{ownerId}/pets/{petId}") public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/owners/{ownerId}/pets/{petId}") fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet { // ... } ---- +====== -- You can declare URI variables at the class and method levels, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @RequestMapping("/owners/{ownerId}") // <1> @@ -140,6 +155,7 @@ You can declare URI variables at the class and method levels, as the following e } } ---- +====== <1> Class-level URI mapping. <2> Method-level URI mapping. @@ -179,22 +195,28 @@ syntax: `{varName:regex}`. For example, given a URL of `/spring-web-3.0.5.jar`, extracts the name, version, and file extension: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String ext) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") fun handle(@PathVariable version: String, @PathVariable ext: String) { // ... } ---- +====== -- URI path patterns can also have embedded `${...}` placeholders that are resolved on startup @@ -234,22 +256,28 @@ sorted last instead. If two patterns are both catch-all, the longer is chosen. You can narrow the request mapping based on the `Content-Type` of the request, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping(path = "/pets", consumes = "application/json") public void addPet(@RequestBody Pet pet) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/pets", consumes = ["application/json"]) fun addPet(@RequestBody pet: Pet) { // ... } ---- +====== The consumes attribute also supports negation expressions -- for example, `!text/plain` means any content type other than `text/plain`. @@ -269,8 +297,11 @@ TIP: `MediaType` provides constants for commonly used media types -- for example You can narrow the request mapping based on the `Accept` request header and the list of content types that a controller method produces, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping(path = "/pets/{petId}", produces = "application/json") @ResponseBody @@ -278,8 +309,10 @@ content types that a controller method produces, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/pets/{petId}", produces = ["application/json"]) @ResponseBody @@ -287,6 +320,7 @@ content types that a controller method produces, as the following example shows: // ... } ---- +====== The media type can specify a character set. Negated expressions are supported -- for example, `!text/plain` means any content type other than `text/plain`. @@ -307,14 +341,18 @@ You can narrow request mappings based on query parameter conditions. You can tes presence of a query parameter (`myParam`), for its absence (`!myParam`), or for a specific value (`myParam=myValue`). The following examples tests for a parameter with a value: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") // <1> public void findPet(@PathVariable String petId) { // ... } ---- +====== <1> Check that `myParam` equals `myValue`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -329,14 +367,18 @@ specific value (`myParam=myValue`). The following examples tests for a parameter You can also use the same with request header conditions, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") // <1> public void findPet(@PathVariable String petId) { // ... } ---- +====== <1> Check that `myHeader` equals `myValue`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -401,8 +443,11 @@ You can programmatically register Handler methods, which can be used for dynamic registrations or for advanced cases, such as different instances of the same handler under different URLs. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class MyConfig { @@ -421,6 +466,7 @@ under different URLs. The following example shows how to do so: } ---- +====== <1> Inject target handlers and the handler mapping for controllers. <2> Prepare the request mapping metadata. <3> Get the handler method. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann.adoc index c44f42c332d..8ace15c6577 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann.adoc @@ -12,8 +12,11 @@ a web component. To enable auto-detection of such `@Controller` beans, you can add component scanning to your Java configuration, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan("org.example.web") // <1> @@ -22,6 +25,7 @@ your Java configuration, as the following example shows: // ... } ---- +====== <1> Scan the `org.example.web` package. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/dispatcher-handler.adoc b/framework-docs/modules/ROOT/pages/web/webflux/dispatcher-handler.adoc index 46250c6f689..b0919fd8fda 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/dispatcher-handler.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/dispatcher-handler.adoc @@ -25,18 +25,24 @@ Spring configuration in a WebFlux application typically contains: The configuration is given to `WebHttpHandlerBuilder` to build the processing chain, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- ApplicationContext context = ... HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val context: ApplicationContext = ... val handler = WebHttpHandlerBuilder.applicationContext(context).build() ---- +====== The resulting `HttpHandler` is ready for use with a xref:web/webflux/reactive-spring.adoc#webflux-httphandler[server adapter]. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc index 052378f29a9..495dc89ab86 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc @@ -84,42 +84,57 @@ https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-the-Spr The code snippets below show using the `HttpHandler` adapters with each server API: *Reactor Netty* +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpHandler handler = ... ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); HttpServer.create().host(host).port(port).handle(adapter).bindNow(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val handler: HttpHandler = ... val adapter = ReactorHttpHandlerAdapter(handler) HttpServer.create().host(host).port(port).handle(adapter).bindNow() ---- +====== *Undertow* +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpHandler handler = ... UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler); Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build(); server.start(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val handler: HttpHandler = ... val adapter = UndertowHttpHandlerAdapter(handler) val server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build() server.start() ---- +====== *Tomcat* +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpHandler handler = ... Servlet servlet = new TomcatHttpHandlerAdapter(handler); @@ -133,8 +148,10 @@ The code snippets below show using the `HttpHandler` adapters with each server A server.setPort(port); server.start(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val handler: HttpHandler = ... val servlet = TomcatHttpHandlerAdapter(handler) @@ -148,11 +165,15 @@ The code snippets below show using the `HttpHandler` adapters with each server A server.setPort(port) server.start() ---- +====== *Jetty* +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpHandler handler = ... Servlet servlet = new JettyHttpHandlerAdapter(handler); @@ -168,8 +189,10 @@ The code snippets below show using the `HttpHandler` adapters with each server A server.addConnector(connector); server.start(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val handler: HttpHandler = ... val servlet = JettyHttpHandlerAdapter(handler) @@ -185,6 +208,7 @@ The code snippets below show using the `HttpHandler` adapters with each server A server.addConnector(connector) server.start() ---- +====== *Servlet Container* @@ -277,16 +301,22 @@ Spring ApplicationContext, or that can be registered directly with it: `ServerWebExchange` exposes the following method for accessing form data: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono> getFormData(); ---- + +Kotlin:: ++ [source,Kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- suspend fun getFormData(): MultiValueMap ---- +====== The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse form data (`application/x-www-form-urlencoded`) into a `MultiValueMap`. By default, @@ -300,16 +330,22 @@ The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse `ServerWebExchange` exposes the following method for accessing multipart data: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Mono> getMultipartData(); ---- + +Kotlin:: ++ [source,Kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- suspend fun getMultipartData(): MultiValueMap ---- +====== The `DefaultServerWebExchange` uses the configured `HttpMessageReader>` to parse `multipart/form-data`, @@ -621,8 +657,11 @@ headers are masked by default and you must explicitly enable their logging in fu The following example shows how to do so for server-side requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebFlux @@ -634,8 +673,10 @@ The following example shows how to do so for server-side requests: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebFlux @@ -646,11 +687,15 @@ The following example shows how to do so for server-side requests: } } ---- +====== The following example shows how to do so for client-side requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- Consumer consumer = configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true); @@ -659,8 +704,10 @@ The following example shows how to do so for client-side requests: .exchangeStrategies(strategies -> strategies.codecs(consumer)) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) } @@ -668,6 +715,7 @@ The following example shows how to do so for client-side requests: .exchangeStrategies({ strategies -> strategies.codecs(consumer) }) .build() ---- +====== [[webflux-logging-appenders]] @@ -693,8 +741,11 @@ or xref:web/webflux/reactive-spring.adoc#webflux-logging-sensitive-data[logging The following example shows how to do so for client-side requests: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- WebClient webClient = WebClient.builder() .codecs(configurer -> { @@ -703,8 +754,10 @@ The following example shows how to do so for client-side requests: }) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val webClient = WebClient.builder() .codecs({ configurer -> @@ -713,4 +766,5 @@ The following example shows how to do so for client-side requests: }) .build() ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc index c41e79bfcb7..e959c141a1f 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc @@ -83,8 +83,11 @@ The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`] annotation enables cross-origin requests on annotated controller methods, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController @RequestMapping("/account") @@ -102,8 +105,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController @RequestMapping("/account") @@ -121,6 +126,7 @@ as the following example shows: } } ---- +====== By default, `@CrossOrigin` allows: @@ -139,8 +145,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig `@CrossOrigin` is supported at the class level, too, and is inherited by all methods, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @CrossOrigin(origins = "https://domain2.com", maxAge = 3600) @RestController @@ -158,8 +167,10 @@ public class AccountController { } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600) @RestController @@ -176,12 +187,16 @@ public class AccountController { // ... } ---- +====== You can use `@CrossOrigin` at both the class level and the method level, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @CrossOrigin(maxAge = 3600) @RestController @@ -200,8 +215,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @CrossOrigin(maxAge = 3600) @RestController @@ -220,6 +237,7 @@ as the following example shows: } } ---- +====== @@ -257,8 +275,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig To enable CORS in the MVC Java config, you can use the `CorsRegistry` callback, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -278,8 +299,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -298,6 +321,7 @@ as the following example shows: } } ---- +====== @@ -341,8 +365,11 @@ CORS. To configure the filter, pass a `CorsConfigurationSource` to its constructor, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- CorsConfiguration config = new CorsConfiguration(); @@ -359,8 +386,10 @@ following example shows: CorsFilter filter = new CorsFilter(source); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- val config = CorsConfiguration() @@ -377,3 +406,4 @@ following example shows: val filter = CorsFilter(source) ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc index 988a1d795f7..6f93344081f 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc @@ -30,8 +30,11 @@ difference that router functions provide not just data, but also behavior. `RouterFunctions.route()` provides a router builder that facilitates the creation of routers, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.servlet.function.RequestPredicates.*; @@ -64,6 +67,7 @@ as the following example shows: } } ---- +====== <1> Create router using `route()`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -125,44 +129,62 @@ while access to the body is provided through the `body` methods. The following example extracts the request body to a `String`: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- String string = request.body(String.class); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val string = request.body() ---- +====== The following example extracts the body to a `List`, where `Person` objects are decoded from a serialized form, such as JSON or XML: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- List people = request.body(new ParameterizedTypeReference>() {}); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val people = request.body() ---- +====== The following example shows how to access parameters: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- MultiValueMap params = request.params(); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val map = request.params() ---- +====== [[webmvc-fn-response]] @@ -173,69 +195,94 @@ a `build` method to create it. You can use the builder to set the response statu headers, or to provide a body. The following example creates a 200 (OK) response with JSON content: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Person person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val person: Person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person) ---- +====== The following example shows how to build a 201 (CREATED) response with a `Location` header and no body: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- URI location = ... ServerResponse.created(location).build(); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val location: URI = ... ServerResponse.created(location).build() ---- +====== You can also use an asynchronous result as the body, in the form of a `CompletableFuture`, `Publisher`, or any other type supported by the `ReactiveAdapterRegistry`. For instance: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Mono person = webClient.get().retrieve().bodyToMono(Person.class); ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person); ---- + +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- val person = webClient.get().retrieve().awaitBody() ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person) ---- +====== If not just the body, but also the status or headers are based on an asynchronous type, you can use the static `async` method on `ServerResponse`, which accepts `CompletableFuture`, `Publisher`, or any other asynchronous type supported by the `ReactiveAdapterRegistry`. For instance: +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- Mono asyncResponse = webClient.get().retrieve().bodyToMono(Person.class) .map(p -> ServerResponse.ok().header("Name", p.name()).body(p)); ServerResponse.async(asyncResponse); ---- +====== https://www.w3.org/TR/eventsource/[Server-Sent Events] can be provided via the static `sse` method on `ServerResponse`. The builder provided by that method allows you to send Strings, or other objects as JSON. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public RouterFunction sse() { return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> { @@ -258,8 +305,10 @@ allows you to send Strings, or other objects as JSON. For example: // and done at some point sseBuilder.complete(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- fun sse(): RouterFunction = router { GET("/sse") { request -> ServerResponse.sse { sseBuilder -> @@ -282,6 +331,7 @@ allows you to send Strings, or other objects as JSON. For example: // and done at some point sseBuilder.complete() ---- +====== @@ -291,18 +341,24 @@ allows you to send Strings, or other objects as JSON. For example: We can write a handler function as a lambda, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HandlerFunction helloWorld = request -> ServerResponse.ok().body("Hello World"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val helloWorld: (ServerRequest) -> ServerResponse = { ServerResponse.ok().body("Hello World") } ---- +====== -- That is convenient, but in an application we need multiple functions, and multiple inline @@ -312,8 +368,11 @@ has a similar role as `@Controller` in an annotation-based application. For example, the following class exposes a reactive `Person` repository: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.ServerResponse.ok; @@ -350,6 +409,7 @@ 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. @@ -397,8 +457,11 @@ A functional endpoint can use Spring's xref:web/webmvc/mvc-config/validation.ado apply validation to the request body. For example, given a custom Spring xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Person`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class PersonHandler { @@ -422,6 +485,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Pers } } ---- +====== <1> Create `Validator` instance. <2> Apply validation. <3> Raise exception for a 400 response. @@ -492,15 +556,20 @@ and so on. The following example uses a request predicate to create a constraint based on the `Accept` header: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = RouterFunctions.route() .GET("/hello-world", accept(MediaType.TEXT_PLAIN), request -> ServerResponse.ok().body("Hello World")).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.servlet.function.router @@ -510,6 +579,7 @@ header: } } ---- +====== You can compose multiple request predicates together by using: @@ -547,8 +617,11 @@ There are also other ways to compose multiple router functions together: The following example shows the composition of four routes: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.servlet.function.RequestPredicates.*; @@ -565,6 +638,7 @@ The following example shows the composition of four routes: .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 @@ -611,8 +685,11 @@ When using annotations, you would remove this duplication by using a type-level In WebMvc.fn, path predicates can be shared through the `path` method on the router function builder. For instance, the last few lines of the example above can be improved in the following way by using nested routes: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = route() .path("/person", builder -> builder // <1> @@ -621,6 +698,7 @@ RouterFunction route = route() .POST(handler::createPerson)) .build(); ---- +====== <1> Note that second parameter of `path` is a consumer that takes the router builder. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -643,8 +721,11 @@ the `nest` method on the builder. The above still contains some duplication in the form of the shared `Accept`-header predicate. We can further improve by using the `nest` method together with `accept`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = route() .path("/person", b1 -> b1 @@ -654,8 +735,10 @@ We can further improve by using the `nest` method together with `accept`: .POST(handler::createPerson)) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.servlet.function.router @@ -669,6 +752,7 @@ We can further improve by using the `nest` method together with `accept`: } } ---- +====== [[webmvc-fn-running]] @@ -693,8 +777,11 @@ starter. The following example shows a WebFlux Java configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableMvc @@ -728,8 +815,10 @@ The following example shows a WebFlux Java configuration: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableMvc @@ -760,6 +849,7 @@ The following example shows a WebFlux Java configuration: } } ---- +====== @@ -775,8 +865,11 @@ The filter will apply to all routes that are built by the builder. This means that filters defined in nested routes do not apply to "top-level" routes. For instance, consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- RouterFunction route = route() .path("/person", b1 -> b1 @@ -790,6 +883,7 @@ For instance, consider the following example: .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. @@ -827,8 +921,11 @@ Now we can add a simple security filter to our route, assuming that we have a `S can determine whether a particular path is allowed. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- SecurityManager securityManager = ... @@ -848,8 +945,10 @@ The following example shows how to do so: }) .build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.servlet.function.router @@ -871,6 +970,7 @@ The following example shows how to do so: } } ---- +====== The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional. We only let the handler function be run when access is allowed. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc index 627885398d3..64a49310d12 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc @@ -32,8 +32,11 @@ A simple PDF view for a word list could extend `org.springframework.web.servlet.view.document.AbstractPdfView` and implement the `buildPdfDocument()` method, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class PdfWordList extends AbstractPdfView { @@ -47,8 +50,10 @@ A simple PDF view for a word list could extend } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class PdfWordList : AbstractPdfView() { @@ -62,6 +67,7 @@ A simple PDF view for a word list could extend } } ---- +====== A controller can return such a view either from an external view definition (referencing it by name) or as a `View` instance from the handler method. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc index e9cdbfbe3b6..68714b3dfe5 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc @@ -10,8 +10,11 @@ package `org.springframework.web.servlet.view.feed`. optionally override the `buildFeedMetadata()` method (the default implementation is empty). The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SampleContentAtomView extends AbstractAtomFeedView { @@ -28,8 +31,10 @@ empty). The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SampleContentAtomView : AbstractAtomFeedView() { @@ -44,11 +49,15 @@ empty). The following example shows how to do so: } } ---- +====== Similar requirements apply for implementing `AbstractRssFeedView`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class SampleContentRssView extends AbstractRssFeedView { @@ -65,8 +74,10 @@ Similar requirements apply for implementing `AbstractRssFeedView`, as the follow } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class SampleContentRssView : AbstractRssFeedView() { @@ -81,6 +92,7 @@ Similar requirements apply for implementing `AbstractRssFeedView`, as the follow } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-freemarker.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-freemarker.adoc index dbd74817065..e0e95083b98 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-freemarker.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-freemarker.adoc @@ -15,8 +15,11 @@ integration for using Spring MVC with FreeMarker templates. The following example shows how to configure FreeMarker as a view technology: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -37,8 +40,10 @@ The following example shows how to configure FreeMarker as a view technology: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -56,6 +61,7 @@ The following example shows how to configure FreeMarker as a view technology: } } ---- +====== The following example shows how to configure the same in XML: @@ -364,8 +370,11 @@ and a default value in the form backing object, the HTML resembles the following If your application expects to handle cities by internal codes (for example), you can create the map of codes with suitable keys, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- protected Map referenceData(HttpServletRequest request) throws Exception { Map cityMap = new LinkedHashMap<>(); @@ -378,8 +387,10 @@ codes with suitable keys, as the following example shows: return model; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- protected fun referenceData(request: HttpServletRequest): Map { val cityMap = linkedMapOf( @@ -390,6 +401,7 @@ codes with suitable keys, as the following example shows: return hashMapOf("cityMap" to cityMap) } ---- +====== The code now produces output where the radio values are the relevant codes, but the user still sees the more user-friendly city names, as follows: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-groovymarkup.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-groovymarkup.adoc index 60292b26f09..7793e08c104 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-groovymarkup.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-groovymarkup.adoc @@ -15,8 +15,11 @@ NOTE: The Groovy Markup Template engine requires Groovy 2.3.1+. The following example shows how to configure the Groovy Markup Template Engine: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -37,8 +40,10 @@ The following example shows how to configure the Groovy Markup Template Engine: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -56,6 +61,7 @@ The following example shows how to configure the Groovy Markup Template Engine: } } ---- +====== The following example shows how to configure the same in XML: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc index 5284dc01260..1e37619d393 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc @@ -185,8 +185,11 @@ This tag renders an HTML `input` tag with the `type` set to `checkbox`. Assume that our `User` has preferences such as newsletter subscription and a list of hobbies. The following example shows the `Preferences` class: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class Preferences { @@ -219,8 +222,10 @@ hobbies. The following example shows the `Preferences` class: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class Preferences( var receiveNewsletter: Boolean, @@ -228,6 +233,7 @@ hobbies. The following example shows the `Preferences` class: var favouriteWord: String ) ---- +====== The corresponding `form.jsp` could then resemble the following: @@ -582,8 +588,11 @@ Assume that we want to display all error messages for the `firstName` and `lastN fields once we submit the form. We have a validator for instances of the `User` class called `UserValidator`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class UserValidator implements Validator { @@ -597,8 +606,10 @@ called `UserValidator`, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class UserValidator : Validator { @@ -612,6 +623,7 @@ called `UserValidator`, as the following example shows: } } ---- +====== The `form.jsp` could be as follows: @@ -785,8 +797,11 @@ web.xml, as the following example shows: The following example shows the corresponding `@Controller` method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RequestMapping(method = RequestMethod.DELETE) public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { @@ -794,8 +809,10 @@ The following example shows the corresponding `@Controller` method: return "redirect:/owners/" + ownerId; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RequestMapping(method = [RequestMethod.DELETE]) fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String { @@ -803,6 +820,7 @@ The following example shows the corresponding `@Controller` method: return "redirect:/owners/$ownerId" } ---- +====== [[mvc-view-jsp-formtaglib-html5]] === HTML5 Tags diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-script.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-script.adoc index 8cb6e1b1249..27fac970a19 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-script.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-script.adoc @@ -53,8 +53,11 @@ You can declare a `ScriptTemplateConfigurer` bean to specify the script engine t the script files to load, what function to call to render templates, and so on. The following example uses Mustache templates and the Nashorn JavaScript engine: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -76,8 +79,10 @@ The following example uses Mustache templates and the Nashorn JavaScript engine: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -96,6 +101,7 @@ The following example uses Mustache templates and the Nashorn JavaScript engine: } } ---- +====== The following example shows the same arrangement in XML: @@ -114,8 +120,11 @@ The following example shows the same arrangement in XML: The controller would look no different for the Java and XML configurations, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class SampleController { @@ -128,8 +137,10 @@ The controller would look no different for the Java and XML configurations, as t } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class SampleController { @@ -142,6 +153,7 @@ The controller would look no different for the Java and XML configurations, as t } } ---- +====== The following example shows the Mustache template: @@ -176,8 +188,11 @@ browser facilities that are not available in the server-side script engine. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -199,8 +214,10 @@ The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -219,6 +236,7 @@ The following example shows how to do so: } } ---- +====== NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe script engines with templating libraries not designed for concurrency, such as Handlebars or diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-xslt.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-xslt.adoc index b139bad0dee..255c899eb6e 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-xslt.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-xslt.adoc @@ -22,8 +22,11 @@ Configuration is standard for a simple Spring web application: The MVC configura has to define an `XsltViewResolver` bean and regular MVC annotation configuration. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @EnableWebMvc @ComponentScan @@ -39,8 +42,10 @@ The following example shows how to do so: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @EnableWebMvc @ComponentScan @@ -54,6 +59,7 @@ The following example shows how to do so: } } ---- +====== [[mvc-view-xslt-controllercode]] @@ -64,8 +70,11 @@ We also need a Controller that encapsulates our word-generation logic. The controller logic is encapsulated in a `@Controller` class, with the handler method being defined as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class XsltController { @@ -88,8 +97,10 @@ handler method being defined as follows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.ui.set @@ -114,6 +125,7 @@ handler method being defined as follows: } } ---- +====== So far, we have only created a DOM document and added it to the Model map. Note that you can also load an XML file as a `Resource` and use it instead of a custom DOM document. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc index 8cad1a50b91..83276621eba 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc @@ -21,8 +21,11 @@ Once the asynchronous request processing feature is xref:web/webmvc/mvc-ann-asyn in the Servlet container, controller methods can wrap any supported controller method return value with `DeferredResult`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/quotes") @ResponseBody @@ -35,8 +38,10 @@ return value with `DeferredResult`, as the following example shows: // From some other thread... deferredResult.setResult(result); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/quotes") @ResponseBody @@ -49,6 +54,7 @@ return value with `DeferredResult`, as the following example shows: // From some other thread... deferredResult.setResult(result) ---- +====== The controller can produce the return value asynchronously, from a different thread -- for example, in response to an external event (JMS message), a scheduled task, or other event. @@ -61,16 +67,21 @@ example, in response to an external event (JMS message), a scheduled task, or ot A controller can wrap any supported return value with `java.util.concurrent.Callable`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping public Callable processUpload(final MultipartFile file) { return () -> "someView"; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping fun processUpload(file: MultipartFile) = Callable { @@ -78,6 +89,7 @@ as the following example shows: "someView" } ---- +====== The return value can then be obtained by running the given task through the xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] `TaskExecutor`. @@ -211,8 +223,11 @@ each object is serialized with an xref:integration/rest-clients.adoc#rest-message-conversion[`HttpMessageConverter`] and written to the response, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/events") public ResponseBodyEmitter handle() { @@ -230,8 +245,10 @@ response, as the following example shows: // and done at some point emitter.complete(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/events") fun handle() = ResponseBodyEmitter().apply { @@ -247,6 +264,7 @@ response, as the following example shows: // and done at some point emitter.complete() ---- +====== You can also use `ResponseBodyEmitter` as the body in a `ResponseEntity`, letting you customize the status and headers of the response. @@ -267,8 +285,11 @@ https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from t are formatted according to the W3C SSE specification. To produce an SSE stream from a controller, return `SseEmitter`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter handle() { @@ -286,8 +307,10 @@ stream from a controller, return `SseEmitter`, as the following example shows: // and done at some point emitter.complete(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) fun handle() = SseEmitter().apply { @@ -303,6 +326,7 @@ stream from a controller, return `SseEmitter`, as the following example shows: // and done at some point emitter.complete() ---- +====== While SSE is the main option for streaming into browsers, note that Internet Explorer does not support Server-Sent Events. Consider using Spring's @@ -320,8 +344,11 @@ Sometimes, it is useful to bypass message conversion and stream directly to the `OutputStream` (for example, for a file download). You can use the `StreamingResponseBody` return value type to do so, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/download") public StreamingResponseBody handle() { @@ -333,14 +360,17 @@ return value type to do so, as the following example shows: }; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/download") fun handle() = StreamingResponseBody { // write... } ---- +====== You can use `StreamingResponseBody` as the body in a `ResponseEntity` to customize the status and headers of the response. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-caching.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-caching.adoc index da2ed1757f1..758f82cfd76 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-caching.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-caching.adoc @@ -32,8 +32,11 @@ While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234] describes all directives for the `Cache-Control` response header, the `CacheControl` type takes a use case-oriented approach that focuses on the common scenarios: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Cache for an hour - "Cache-Control: max-age=3600" CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); @@ -46,8 +49,10 @@ use case-oriented approach that focuses on the common scenarios: // "Cache-Control: max-age=864000, public, no-transform" CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Cache for an hour - "Cache-Control: max-age=3600" val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS) @@ -60,6 +65,7 @@ use case-oriented approach that focuses on the common scenarios: // "Cache-Control: max-age=864000, public, no-transform" val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic() ---- +====== `WebContentGenerator` also accepts a simpler `cachePeriod` property (defined in seconds) that works as follows: @@ -81,8 +87,11 @@ against conditional request headers. A controller can add an `ETag` header and ` settings to a `ResponseEntity`, as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/book/{id}") public ResponseEntity showBook(@PathVariable Long id) { @@ -97,8 +106,10 @@ settings to a `ResponseEntity`, as the following example shows: .body(book); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/book/{id}") fun showBook(@PathVariable id: Long): ResponseEntity { @@ -113,6 +124,7 @@ settings to a `ResponseEntity`, as the following example shows: .body(book) } ---- +====== -- The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison @@ -123,8 +135,11 @@ You can also make the check against conditional request headers in the controlle as the following example shows: -- +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RequestMapping public String myHandleMethod(WebRequest request, Model model) { @@ -139,6 +154,7 @@ as the following example shows: 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. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-java.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-java.adoc index edf120d6013..dbcdcaccb01 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-java.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-java.adoc @@ -12,8 +12,11 @@ For advanced mode, you can remove `@EnableWebMvc` and extend directly from `DelegatingWebMvcConfiguration` instead of implementing `WebMvcConfigurer`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class WebConfig extends DelegatingWebMvcConfiguration { @@ -21,8 +24,10 @@ as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration class WebConfig : DelegatingWebMvcConfiguration() { @@ -30,6 +35,7 @@ as the following example shows: // ... } ---- +====== You can keep existing methods in `WebConfig`, but you can now also override bean declarations from the base class, and you can still have any number of other `WebMvcConfigurer` implementations on diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-xml.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-xml.adoc index c203f2ee64d..bb720337264 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-xml.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/advanced-xml.adoc @@ -5,8 +5,11 @@ The MVC namespace does not have an advanced mode. If you need to customize a pro a bean that you cannot change otherwise, you can use the `BeanPostProcessor` lifecycle hook of the Spring `ApplicationContext`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Component public class MyPostProcessor implements BeanPostProcessor { @@ -16,8 +19,10 @@ hook of the Spring `ApplicationContext`, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Component class MyPostProcessor : BeanPostProcessor { @@ -27,6 +32,7 @@ hook of the Spring `ApplicationContext`, as the following example shows: } } ---- +====== Note that you need to declare `MyPostProcessor` as a bean, either explicitly in XML or diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/content-negotiation.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/content-negotiation.adoc index 2848ad42b11..c209826dcbf 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/content-negotiation.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/content-negotiation.adoc @@ -16,8 +16,11 @@ more details. In Java configuration, you can customize requested content type resolution, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -30,8 +33,10 @@ following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -43,6 +48,7 @@ following example shows: } } ---- +====== The following example shows how to achieve the same configuration in XML: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/conversion.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/conversion.adoc index 2de0ddc856e..35d0998934d 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/conversion.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/conversion.adoc @@ -8,8 +8,11 @@ for customization via `@NumberFormat` and `@DateTimeFormat` on fields. To register custom formatters and converters in Java config, use the following: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -21,8 +24,10 @@ To register custom formatters and converters in Java config, use the following: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -33,6 +38,7 @@ To register custom formatters and converters in Java config, use the following: } } ---- +====== To do the same in XML config, use the following: @@ -78,8 +84,11 @@ values. This works for forms where dates are represented as Strings with "input" fields. For "date" and "time" form fields, however, browsers use a fixed format defined in the HTML spec. For such cases date and time formatting can be customized as follows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -93,8 +102,10 @@ in the HTML spec. For such cases date and time formatting can be customized as f } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -107,6 +118,7 @@ in the HTML spec. For such cases date and time formatting can be customized as f } } ---- +====== NOTE: See xref:core/validation/format.adoc#format-FormatterRegistrar-SPI[the `FormatterRegistrar` SPI] and the `FormattingConversionServiceFactoryBean` for more information on when to use diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/customize.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/customize.adoc index f9126edd1f2..dd7cf7e635e 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/customize.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/customize.adoc @@ -6,8 +6,11 @@ In Java configuration, you can implement the `WebMvcConfigurer` interface, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -16,8 +19,10 @@ following example shows: // Implement configuration methods... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -26,6 +31,7 @@ following example shows: // Implement configuration methods... } ---- +====== In XML, you can check attributes and sub-elements of ``. You can diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/default-servlet-handler.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/default-servlet-handler.adoc index 1fea95fc807..8251cd53528 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/default-servlet-handler.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/default-servlet-handler.adoc @@ -15,8 +15,11 @@ lower than that of the `DefaultServletHttpRequestHandler`, which is `Integer.MAX The following example shows how to enable the feature by using the default setup: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -28,8 +31,10 @@ The following example shows how to enable the feature by using the default setup } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -40,6 +45,7 @@ The following example shows how to enable the feature by using the default setup } } ---- +====== The following example shows how to achieve the same configuration in XML: @@ -57,8 +63,11 @@ If the default Servlet has been custom-configured with a different name, or if a different Servlet container is being used where the default Servlet name is unknown, then you must explicitly provide the default Servlet's name, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -70,8 +79,10 @@ then you must explicitly provide the default Servlet's name, as the following ex } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -82,6 +93,7 @@ then you must explicitly provide the default Servlet's name, as the following ex } } ---- +====== The following example shows how to achieve the same configuration in XML: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/enable.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/enable.adoc index e7300178a1e..bec619f91f3 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/enable.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/enable.adoc @@ -6,21 +6,27 @@ In Java configuration, you can use the `@EnableWebMvc` annotation to enable MVC configuration, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc public class WebConfig { } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc class WebConfig ---- +====== In XML configuration, you can use the `` element to enable MVC configuration, as the following example shows: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc index 2bdaf5effb6..ccbf3433b2e 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc @@ -4,8 +4,11 @@ In Java configuration, you can register interceptors to apply to incoming requests, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -18,8 +21,10 @@ the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -31,6 +36,7 @@ the following example shows: } } ---- +====== The following example shows how to achieve the same configuration in XML: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/message-converters.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/message-converters.adoc index c8c86115793..0f3d79bc71c 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/message-converters.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/message-converters.adoc @@ -12,8 +12,11 @@ You can customize `HttpMessageConverter` in Java configuration by overriding The following example adds XML and Jackson JSON converters with a customized `ObjectMapper` instead of the default ones: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -30,8 +33,10 @@ The following example adds XML and Jackson JSON converters with a customized } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -45,6 +50,7 @@ The following example adds XML and Jackson JSON converters with a customized converters.add(MappingJackson2HttpMessageConverter(builder.build())) converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build())) ---- +====== In the preceding example, {api-spring-framework}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/path-matching.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/path-matching.adoc index a3318bc15b9..eff9db98d66 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/path-matching.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/path-matching.adoc @@ -9,8 +9,11 @@ For details on the individual options, see the The following example shows how to customize path matching in Java configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -26,8 +29,10 @@ The following example shows how to customize path matching in Java configuration } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -42,6 +47,7 @@ The following example shows how to customize path matching in Java configuration } } ---- +====== The following example shows how to customize path matching in XML configuration: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc index 7620f00ec71..adb887831e9 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc @@ -15,8 +15,11 @@ so that HTTP conditional requests are supported with `"Last-Modified"` headers. The following listing shows how to do so with Java configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -30,8 +33,10 @@ The following listing shows how to do so with Java configuration: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -44,6 +49,7 @@ The following listing shows how to do so with Java configuration: } } ---- +====== The following example shows how to achieve the same configuration in XML: @@ -69,8 +75,11 @@ JavaScript resources used with a module loader. The following example shows how to use `VersionResourceResolver` in Java configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -85,8 +94,10 @@ The following example shows how to use `VersionResourceResolver` in Java configu } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -100,6 +111,7 @@ The following example shows how to use `VersionResourceResolver` in Java configu } } ---- +====== The following example shows how to achieve the same configuration in XML: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc index 82a89a8e130..833914f4ab3 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc @@ -11,8 +11,11 @@ registered as a global xref:core/validation/validator.adoc[Validator] for use wi In Java configuration, you can customize the global `Validator` instance, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -24,8 +27,10 @@ following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -36,6 +41,7 @@ following example shows: } } ---- +====== The following example shows how to achieve the same configuration in XML: @@ -59,8 +65,11 @@ The following example shows how to achieve the same configuration in XML: Note that you can also register `Validator` implementations locally, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class MyController { @@ -71,8 +80,10 @@ example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class MyController { @@ -83,6 +94,7 @@ example shows: } } ---- +====== TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and mark it with `@Primary` in order to avoid conflict with the one declared in the MVC configuration. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-controller.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-controller.adoc index 0df943d14e0..91811b7f415 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-controller.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-controller.adoc @@ -7,8 +7,11 @@ logic to run before the view generates the response. The following example of Java configuration forwards a request for `/` to a view called `home`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -20,8 +23,10 @@ The following example of Java configuration forwards a request for `/` to a view } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -32,6 +37,7 @@ The following example of Java configuration forwards a request for `/` to a view } } ---- +====== The following example achieves the same thing as the preceding example, but with XML, by using the `` element: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-resolvers.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-resolvers.adoc index da2227b20ec..cea23436efd 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-resolvers.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/view-resolvers.adoc @@ -8,8 +8,11 @@ The MVC configuration simplifies the registration of view resolvers. The following Java configuration example configures content negotiation view resolution by using JSP and Jackson as a default `View` for JSON rendering: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -22,8 +25,10 @@ resolution by using JSP and Jackson as a default `View` for JSON rendering: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -35,6 +40,7 @@ resolution by using JSP and Jackson as a default `View` for JSON rendering: } } ---- +====== The following example shows how to achieve the same configuration in XML: @@ -75,8 +81,11 @@ The MVC namespace provides dedicated elements. The following example works with In Java configuration, you can add the respective `Configurer` bean, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @EnableWebMvc @@ -96,8 +105,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @EnableWebMvc @@ -114,6 +125,7 @@ as the following example shows: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller.adoc index 7af5f14a558..112361f7dba 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller.adoc @@ -9,8 +9,11 @@ exception handling, and more. Annotated controllers have flexible method signatu do not have to extend base classes nor implement specific interfaces. The following example shows a controller defined by annotations: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class HelloController { @@ -22,8 +25,10 @@ The following example shows a controller defined by annotations: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.ui.set @@ -37,6 +42,7 @@ The following example shows a controller defined by annotations: } } ---- +====== In the preceding example, the method accepts a `Model` and returns a view name as a `String`, but many other options exist and are explained later in this chapter. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-advice.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-advice.adoc index 7cda8894f6a..46726f71ad2 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-advice.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-advice.adoc @@ -23,8 +23,11 @@ By contrast, global `@ModelAttribute` and `@InitBinder` methods are applied _bef The `@ControllerAdvice` annotation has attributes that let you narrow the set of controllers and handlers that they apply to. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // Target all Controllers annotated with @RestController @ControllerAdvice(annotations = RestController.class) @@ -38,8 +41,10 @@ and handlers that they apply to. For example: @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class ExampleAdvice3 {} ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // Target all Controllers annotated with @RestController @ControllerAdvice(annotations = [RestController::class]) @@ -53,6 +58,7 @@ and handlers that they apply to. For example: @ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class]) class ExampleAdvice3 ---- +====== The selectors in the preceding example are evaluated at runtime and may negatively impact performance if used extensively. See the diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-exceptionhandler.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-exceptionhandler.adoc index 89f7aeff9a0..73af9fa6ce4 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-exceptionhandler.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-exceptionhandler.adoc @@ -6,8 +6,11 @@ `@Controller` and xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] classes can have `@ExceptionHandler` methods to handle exceptions from controller methods, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class SimpleController { @@ -20,8 +23,10 @@ } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class SimpleController { @@ -34,6 +39,7 @@ } } ---- +====== The exception may match against a top-level exception being propagated (e.g. a direct `IOException` being thrown) or against a nested cause within a wrapper exception (e.g. @@ -48,42 +54,54 @@ is used to sort exceptions based on their depth from the thrown exception type. Alternatively, the annotation declaration may narrow the exception types to match, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExceptionHandler({FileSystemException.class, RemoteException.class}) public ResponseEntity handle(IOException ex) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExceptionHandler(FileSystemException::class, RemoteException::class) fun handle(ex: IOException): ResponseEntity { // ... } ---- +====== You can even use a list of specific exception types with a very generic argument signature, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ExceptionHandler({FileSystemException.class, RemoteException.class}) public ResponseEntity handle(Exception ex) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ExceptionHandler(FileSystemException::class, RemoteException::class) fun handle(ex: Exception): ResponseEntity { // ... } ---- +====== [NOTE] ==== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc index 2c2a5ecce64..d4149a76a91 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc @@ -21,8 +21,11 @@ do, except for `@ModelAttribute` (command object) arguments. Typically, they are with a `WebDataBinder` argument (for registrations) and a `void` return value. The following listing shows an example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class FormController { @@ -37,6 +40,7 @@ The following listing shows an example: // ... } ---- +====== <1> Defining an `@InitBinder` method. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -61,8 +65,11 @@ Alternatively, when you use a `Formatter`-based setup through a shared `FormattingConversionService`, you can re-use the same approach and register controller-specific `Formatter` implementations, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class FormController { @@ -75,6 +82,7 @@ controller-specific `Formatter` implementations, as the following example shows: // ... } ---- +====== <1> Defining an `@InitBinder` method on a custom formatter. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/cookievalue.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/cookievalue.adoc index 953953f51ee..0d680ce43e8 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/cookievalue.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/cookievalue.adoc @@ -15,14 +15,18 @@ JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 The following example shows how to get the cookie value: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/demo") public void handle(@CookieValue("JSESSIONID") String cookie) { <1> //... } ---- +====== <1> Get the value of the `JSESSIONID` cookie. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/httpentity.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/httpentity.adoc index 9ed54e0d6f2..024f99b1491 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/httpentity.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/httpentity.adoc @@ -6,22 +6,28 @@ `HttpEntity` is more or less identical to using xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[`@RequestBody`] but is based on a container object that exposes request headers and body. The following listing shows an example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/accounts") public void handle(HttpEntity entity) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/accounts") fun handle(entity: HttpEntity) { // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/jackson.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/jackson.adoc index c8a44324c28..3ea43c3e971 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/jackson.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/jackson.adoc @@ -13,8 +13,11 @@ which allow rendering only a subset of all fields in an `Object`. To use it with `@ResponseBody` or `ResponseEntity` controller methods, you can use Jackson's `@JsonView` annotation to activate a serialization view class, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController public class UserController { @@ -53,8 +56,10 @@ which allow rendering only a subset of all fields in an `Object`. To use it with } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController class UserController { @@ -72,6 +77,7 @@ which allow rendering only a subset of all fields in an `Object`. To use it with interface WithPasswordView : WithoutPasswordView } ---- +====== NOTE: `@JsonView` allows an array of view classes, but you can specify only one per controller method. If you need to activate multiple views, you can use a composite interface. @@ -79,8 +85,11 @@ controller method. If you need to activate multiple views, you can use a composi If you want to do the above programmatically, instead of declaring an `@JsonView` annotation, wrap the return value with `MappingJacksonValue` and use it to supply the serialization view: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController public class UserController { @@ -94,8 +103,10 @@ wrap the return value with `MappingJacksonValue` and use it to supply the serial } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController class UserController { @@ -108,12 +119,16 @@ wrap the return value with `MappingJacksonValue` and use it to supply the serial } } ---- +====== For controllers that rely on view resolution, you can add the serialization view class to the model, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class UserController extends AbstractController { @@ -126,8 +141,10 @@ to the model, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class UserController : AbstractController() { @@ -140,6 +157,7 @@ to the model, as the following example shows: } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/matrix-variables.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/matrix-variables.adoc index 927cf90a718..83171fc4f03 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/matrix-variables.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/matrix-variables.adoc @@ -18,8 +18,11 @@ method must use a URI variable to mask that variable content and ensure the requ be matched successfully independent of matrix variable order and presence. The following example uses a matrix variable: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /pets/42;q=11;r=22 @@ -30,8 +33,10 @@ The following example uses a matrix variable: // q == 11 } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // GET /pets/42;q=11;r=22 @@ -42,13 +47,17 @@ The following example uses a matrix variable: // q == 11 } ---- +====== Given that all path segments may contain matrix variables, you may sometimes need to disambiguate which path variable the matrix variable is expected to be in. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /owners/42;q=11/pets/21;q=22 @@ -61,8 +70,10 @@ The following example shows how to do so: // q2 == 22 } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // GET /owners/42;q=11/pets/21;q=22 @@ -75,12 +86,16 @@ The following example shows how to do so: // q2 == 22 } ---- +====== A matrix variable may be defined as optional and a default value specified, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /pets/42 @@ -90,8 +105,10 @@ following example shows: // q == 1 } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // GET /pets/42 @@ -101,11 +118,15 @@ following example shows: // q == 1 } ---- +====== To get all matrix variables, you can use a `MultiValueMap`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @@ -118,8 +139,10 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa // petMatrixVars: ["q" : 22, "s" : 23] } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @@ -132,6 +155,7 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa // petMatrixVars: ["q" : 22, "s" : 23] } ---- +====== Note that you need to enable the use of matrix variables. In the MVC Java configuration, you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc index 8140ea15dc2..06a8de2dfd4 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc @@ -9,14 +9,18 @@ values from HTTP Servlet request parameters whose names match to field names. Th to as data binding, and it saves you from having to deal with parsing and converting individual query parameters and form fields. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute Pet pet) { // <1> // method logic... } ---- +====== <1> Bind an instance of `Pet`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -51,14 +55,18 @@ In the following example, the model attribute name is `account` which matches th path variable `account`, and there is a registered `Converter` which could load the `Account` from a data store: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PutMapping("/accounts/{account}") public String save(@ModelAttribute("account") Account account) { // <1> // ... } ---- +====== <1> Bind an instance of `Account` using an explicit attribute name. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -82,8 +90,11 @@ Data binding can result in errors. By default, a `BindException` is raised. Howe for such errors in the controller method, you can add a `BindingResult` argument immediately next to the `@ModelAttribute`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { // <1> @@ -93,6 +104,7 @@ to the `@ModelAttribute`, as the following example shows: // ... } ---- +====== <1> Adding a `BindingResult` next to the `@ModelAttribute`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -112,8 +124,11 @@ In some cases, you may want access to a model attribute without data binding. Fo cases, you can inject the `Model` into the controller and access it directly or, alternatively, set `@ModelAttribute(binding=false)`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ModelAttribute public AccountForm setUpForm() { @@ -131,6 +146,7 @@ alternatively, set `@ModelAttribute(binding=false)`, as the following example sh // ... } ---- +====== <1> Setting `@ModelAttribute(binding=false)`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -159,8 +175,11 @@ You can automatically apply validation after data binding by adding the (xref:core/validation/beanvalidation.adoc[Bean Validation] and xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { // <1> @@ -170,6 +189,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following ex // ... } ---- +====== <1> Validate the `Pet` instance. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc index 5348bc47508..de91ecad48d 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc @@ -8,8 +8,11 @@ requests with `multipart/form-data` is parsed and accessible as regular request parameters. The following example accesses one regular form field and one uploaded file: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller public class FileUploadController { @@ -27,8 +30,10 @@ file: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller class FileUploadController { @@ -46,6 +51,7 @@ file: } } ---- +====== Declaring the argument type as a `List` allows for resolving multiple files for the same parameter name. @@ -62,8 +68,11 @@ xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[command and file from the preceding example could be fields on a form object, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- class MyForm { @@ -88,8 +97,10 @@ as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyForm(val name: String, val file: MultipartFile, ...) @@ -107,6 +118,7 @@ as the following example shows: } } ---- +====== Multipart requests can also be submitted from non-browser clients in a RESTful service @@ -137,8 +149,11 @@ probably want it deserialized from JSON (similar to `@RequestBody`). Use the `@RequestPart` annotation to access a multipart after converting it with an xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter]: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/") public String handle(@RequestPart("meta-data") MetaData metadata, @@ -146,8 +161,10 @@ xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter] // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/") fun handle(@RequestPart("meta-data") metadata: MetaData, @@ -155,6 +172,7 @@ xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter] // ... } ---- +====== You can use `@RequestPart` in combination with `jakarta.validation.Valid` or use Spring's `@Validated` annotation, both of which cause Standard Bean Validation to be applied. @@ -163,8 +181,11 @@ into a 400 (BAD_REQUEST) response. Alternatively, you can handle validation erro within the controller through an `Errors` or `BindingResult` argument, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/") public String handle(@Valid @RequestPart("meta-data") MetaData metadata, @@ -172,8 +193,10 @@ as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/") fun handle(@Valid @RequestPart("meta-data") metadata: MetaData, @@ -181,6 +204,7 @@ as the following example shows: // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/redirecting-passing-data.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/redirecting-passing-data.adoc index d32331c410f..5ed5b89b4d1 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/redirecting-passing-data.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/redirecting-passing-data.adoc @@ -26,8 +26,11 @@ Note that URI template variables from the present request are automatically made available when expanding a redirect URL, and you don't need to explicitly add them through `Model` or `RedirectAttributes`. The following example shows how to define a redirect: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/files/{path}") public String upload(...) { @@ -35,8 +38,10 @@ through `Model` or `RedirectAttributes`. The following example shows how to defi return "redirect:files/{path}"; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/files/{path}") fun upload(...): String { @@ -44,6 +49,7 @@ through `Model` or `RedirectAttributes`. The following example shows how to defi return "redirect:files/{path}" } ---- +====== Another way of passing data to the redirect target is by using flash attributes. Unlike other redirect attributes, flash attributes are saved in the HTTP session (and, hence, do diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestattrib.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestattrib.adoc index 30858af1c96..40986e90c97 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestattrib.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestattrib.adoc @@ -7,14 +7,18 @@ Similar to `@SessionAttribute`, you can use the `@RequestAttribute` annotations access pre-existing request attributes created earlier (for example, by a Servlet `Filter` or `HandlerInterceptor`): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/") public String handle(@RequestAttribute Client client) { // <1> // ... } ---- +====== <1> Using the `@RequestAttribute` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc index b5cc27f47ec..2c4f316feb9 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc @@ -7,22 +7,28 @@ You can use the `@RequestBody` annotation to have the request body read and dese `Object` through an xref:integration/rest-clients.adoc#rest-message-conversion[`HttpMessageConverter`]. The following example uses a `@RequestBody` argument: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/accounts") public void handle(@RequestBody Account account) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/accounts") fun handle(@RequestBody account: Account) { // ... } ---- +====== You can use the xref:web/webmvc/mvc-config/message-converters.adoc[Message Converters] option of the xref:web/webmvc/mvc-config.adoc[MVC Config] to @@ -35,21 +41,27 @@ into a 400 (BAD_REQUEST) response. Alternatively, you can handle validation erro within the controller through an `Errors` or `BindingResult` argument, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping("/accounts") public void handle(@Valid @RequestBody Account account, BindingResult result) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @PostMapping("/accounts") fun handle(@Valid @RequestBody account: Account, result: BindingResult) { // ... } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestheader.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestheader.adoc index d4ce20b0f8b..0e2f952569c 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestheader.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestheader.adoc @@ -21,8 +21,11 @@ Keep-Alive 300 The following example gets the value of the `Accept-Encoding` and `Keep-Alive` headers: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/demo") public void handle( @@ -31,6 +34,7 @@ The following example gets the value of the `Accept-Encoding` and `Keep-Alive` h //... } ---- +====== <1> Get the value of the `Accept-Encoding` header. <2> Get the value of the `Keep-Alive` header. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestparam.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestparam.adoc index 41b66fe9fa1..9017bd2284b 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestparam.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestparam.adoc @@ -8,8 +8,11 @@ query parameters or form data) to a method argument in a controller. The following example shows how to do so: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @RequestMapping("/pets") @@ -28,6 +31,7 @@ The following example shows how to do so: } ---- +====== <1> Using `@RequestParam` to bind `petId`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responsebody.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responsebody.adoc index 7462334c47c..1cd1816e5d3 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responsebody.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responsebody.adoc @@ -8,8 +8,11 @@ to the response body through an xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter]. The following listing shows an example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/accounts/{id}") @ResponseBody @@ -17,8 +20,10 @@ The following listing shows an example: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/accounts/{id}") @ResponseBody @@ -26,6 +31,7 @@ The following listing shows an example: // ... } ---- +====== `@ResponseBody` is also supported at the class level, in which case it is inherited by all controller methods. This is the effect of `@RestController`, which is nothing more diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responseentity.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responseentity.adoc index 9fb12986a29..f311386cabb 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responseentity.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/responseentity.adoc @@ -5,8 +5,11 @@ `ResponseEntity` is like xref:web/webmvc/mvc-controller/ann-methods/responsebody.adoc[`@ResponseBody`] but with status and headers. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/something") public ResponseEntity handle() { @@ -15,8 +18,10 @@ return ResponseEntity.ok().eTag(etag).body(body); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/something") fun handle(): ResponseEntity { @@ -25,6 +30,7 @@ return ResponseEntity.ok().eTag(etag).build(body) } ---- +====== Spring MVC supports using a single value xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type] to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattribute.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattribute.adoc index 3e5fcad06f6..2ecb0bb8deb 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattribute.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattribute.adoc @@ -8,14 +8,18 @@ If you need access to pre-existing session attributes that are managed globally you can use the `@SessionAttribute` annotation on a method parameter, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RequestMapping("/") public String handle(@SessionAttribute User user) { <1> // ... } ---- +====== <1> Using a `@SessionAttribute` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattributes.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattributes.adoc index 3dcbf3a6d83..30a606d4cca 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattributes.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/sessionattributes.adoc @@ -11,8 +11,11 @@ requests to access. The following example uses the `@SessionAttributes` annotation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @SessionAttributes("pet") // <1> @@ -20,6 +23,7 @@ The following example uses the `@SessionAttributes` annotation: // ... } ---- +====== <1> Using the `@SessionAttributes` annotation. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -38,8 +42,11 @@ it is automatically promoted to and saved in the HTTP Servlet session. It remain until another controller method uses a `SessionStatus` method argument to clear the storage, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @SessionAttributes("pet") // <1> @@ -57,6 +64,7 @@ storage, as the following example shows: } } ---- +====== <1> Storing the `Pet` value in the Servlet session. <2> Clearing the `Pet` value from the Servlet session. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-modelattrib-methods.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-modelattrib-methods.adoc index fec596077f6..0f58a97dd8d 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-modelattrib-methods.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-modelattrib-methods.adoc @@ -24,8 +24,11 @@ related to the request body. The following example shows a `@ModelAttribute` method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ModelAttribute public void populateModel(@RequestParam String number, Model model) { @@ -33,8 +36,10 @@ The following example shows a `@ModelAttribute` method: // add more ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ModelAttribute fun populateModel(@RequestParam number: String, model: Model) { @@ -42,25 +47,32 @@ The following example shows a `@ModelAttribute` method: // add more ... } ---- +====== The following example adds only one attribute: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @ModelAttribute public Account addAccount(@RequestParam String number) { return accountRepository.findAccount(number); } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @ModelAttribute fun addAccount(@RequestParam number: String): Account { return accountRepository.findAccount(number) } ---- +====== NOTE: When a name is not explicitly specified, a default name is chosen based on the `Object` @@ -74,8 +86,11 @@ attribute. This is typically not required, as it is the default behavior in HTML unless the return value is a `String` that would otherwise be interpreted as a view name. `@ModelAttribute` can also customize the model attribute name, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/accounts/{id}") @ModelAttribute("myAccount") @@ -84,8 +99,10 @@ unless the return value is a `String` that would otherwise be interpreted as a v return account; } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/accounts/{id}") @ModelAttribute("myAccount") @@ -94,6 +111,7 @@ unless the return value is a `String` that would otherwise be interpreted as a v return account } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc index 36b81824db3..afa7de56fb0 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc @@ -23,8 +23,11 @@ A `@RequestMapping` is still needed at the class level to express shared mapping The following example has type and method level mappings: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController @RequestMapping("/persons") @@ -42,8 +45,10 @@ The following example has type and method level mappings: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController @RequestMapping("/persons") @@ -61,6 +66,7 @@ The following example has type and method level mappings: } } ---- +====== @@ -102,28 +108,37 @@ Some example patterns: Captured URI variables can be accessed with `@PathVariable`. For example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/owners/{ownerId}/pets/{petId}") public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/owners/{ownerId}/pets/{petId}") fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet { // ... } ---- +====== You can declare URI variables at the class and method levels, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @RequestMapping("/owners/{ownerId}") @@ -135,8 +150,10 @@ You can declare URI variables at the class and method levels, as the following e } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller @RequestMapping("/owners/{ownerId}") @@ -148,6 +165,7 @@ You can declare URI variables at the class and method levels, as the following e } } ---- +====== URI variables are automatically converted to the appropriate type, or `TypeMismatchException` is raised. Simple types (`int`, `long`, `Date`, and so on) are supported by default and you can @@ -162,22 +180,28 @@ The syntax `{varName:regex}` declares a URI variable with a regular expression t syntax of `{varName:regex}`. For example, given URL `"/spring-web-3.0.5.jar"`, the following method extracts the name, version, and file extension: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) { // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") fun handle(@PathVariable name: String, @PathVariable version: String, @PathVariable ext: String) { // ... } ---- +====== URI path patterns can also have embedded `${...}` placeholders that are resolved on startup by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and @@ -274,14 +298,18 @@ recommendations related to RFD. You can narrow the request mapping based on the `Content-Type` of the request, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @PostMapping(path = "/pets", consumes = "application/json") // <1> public void addPet(@RequestBody Pet pet) { // ... } ---- +====== <1> Using a `consumes` attribute to narrow the mapping by the content type. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -312,8 +340,11 @@ TIP: `MediaType` provides constants for commonly used media types, such as You can narrow the request mapping based on the `Accept` request header and the list of content types that a controller method produces, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping(path = "/pets/{petId}", produces = "application/json") // <1> @ResponseBody @@ -321,6 +352,7 @@ content types that a controller method produces, as the following example shows: // ... } ---- +====== <1> Using a `produces` attribute to narrow the mapping by the content type. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -353,14 +385,18 @@ You can narrow request mappings based on request parameter conditions. You can t presence of a request parameter (`myParam`), for the absence of one (`!myParam`), or for a specific value (`myParam=myValue`). The following example shows how to test for a specific value: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") // <1> public void findPet(@PathVariable String petId) { // ... } ---- +====== <1> Testing whether `myParam` equals `myValue`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -375,14 +411,18 @@ specific value (`myParam=myValue`). The following example shows how to test for You can also use the same with request header conditions, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") // <1> public void findPet(@PathVariable String petId) { // ... } ---- +====== <1> Testing whether `myHeader` equals `myValue`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -455,8 +495,11 @@ You can programmatically register handler methods, which you can use for dynamic registrations or for advanced cases, such as different instances of the same handler under different URLs. The following example registers a handler method: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration public class MyConfig { @@ -474,6 +517,7 @@ under different URLs. The following example registers a handler method: } } ---- +====== <1> Inject the target handler and the handler mapping for controllers. <2> Prepare the request mapping meta data. <3> Get the handler method. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann.adoc index 502a3587ad4..b6495f54dd4 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann.adoc @@ -12,8 +12,11 @@ annotated class, indicating its role as a web component. To enable auto-detection of such `@Controller` beans, you can add component scanning to your Java configuration, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Configuration @ComponentScan("org.example.web") @@ -22,8 +25,10 @@ your Java configuration, as the following example shows: // ... } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Configuration @ComponentScan("org.example.web") @@ -32,6 +37,7 @@ your Java configuration, as the following example shows: // ... } ---- +====== The following example shows the XML configuration equivalent of the preceding example: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet.adoc index 388084fda2f..173ee07e679 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet.adoc @@ -18,8 +18,11 @@ The following example of the Java configuration registers and initializes the `DispatcherServlet`, which is auto-detected by the Servlet container (see xref:web/webmvc/mvc-servlet/container-config.adoc[Servlet Config]): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyWebApplicationInitializer implements WebApplicationInitializer { @@ -38,8 +41,10 @@ the `DispatcherServlet`, which is auto-detected by the Servlet container } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyWebApplicationInitializer : WebApplicationInitializer { @@ -57,6 +62,7 @@ the `DispatcherServlet`, which is auto-detected by the Servlet container } } ---- +====== NOTE: In addition to using the ServletContext API directly, you can also extend `AbstractAnnotationConfigDispatcherServletInitializer` and override specific methods diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/container-config.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/container-config.adoc index d5624854e6f..a6a4755c023 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/container-config.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/container-config.adoc @@ -5,8 +5,11 @@ In a Servlet environment, you have the option of configuring the Servlet contain programmatically as an alternative or in combination with a `web.xml` file. The following example registers a `DispatcherServlet`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- import org.springframework.web.WebApplicationInitializer; @@ -23,8 +26,10 @@ The following example registers a `DispatcherServlet`: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- import org.springframework.web.WebApplicationInitializer @@ -40,6 +45,7 @@ The following example registers a `DispatcherServlet`: } } ---- +====== `WebApplicationInitializer` is an interface provided by Spring MVC that ensures your @@ -52,8 +58,11 @@ location of the `DispatcherServlet` configuration. This is recommended for applications that use Java-based Spring configuration, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -73,8 +82,10 @@ following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { @@ -91,12 +102,16 @@ following example shows: } } ---- +====== If you use XML-based Spring configuration, you should extend directly from `AbstractDispatcherServletInitializer`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { @@ -118,8 +133,10 @@ If you use XML-based Spring configuration, you should extend directly from } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyWebAppInitializer : AbstractDispatcherServletInitializer() { @@ -138,13 +155,17 @@ If you use XML-based Spring configuration, you should extend directly from } } ---- +====== `AbstractDispatcherServletInitializer` also provides a convenient way to add `Filter` instances and have them be automatically mapped to the `DispatcherServlet`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { @@ -157,8 +178,10 @@ following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyWebAppInitializer : AbstractDispatcherServletInitializer() { @@ -169,6 +192,7 @@ following example shows: } } ---- +====== Each filter is added with a default name based on its concrete type and automatically mapped to the `DispatcherServlet`. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/context-hierarchy.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/context-hierarchy.adoc index 8884cfacc89..5e18f2e21ea 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/context-hierarchy.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/context-hierarchy.adoc @@ -24,8 +24,11 @@ image::mvc-context-hierarchy.png[] The following example configures a `WebApplicationContext` hierarchy: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -45,8 +48,10 @@ The following example configures a `WebApplicationContext` hierarchy: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { @@ -63,6 +68,7 @@ The following example configures a `WebApplicationContext` hierarchy: } } ---- +====== TIP: If an application context hierarchy is not required, applications can return all configuration through `getRootConfigClasses()` and `null` from `getServletConfigClasses()`. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/exceptionhandlers.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/exceptionhandlers.adoc index c839fb11152..23e8587b3bd 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/exceptionhandlers.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/exceptionhandlers.adoc @@ -74,8 +74,11 @@ Servlet container makes an ERROR dispatch within the container to the configured to a `@Controller`, which could be implemented to return an error view name with a model or to render a JSON response, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RestController public class ErrorController { @@ -89,8 +92,10 @@ or to render a JSON response, as the following example shows: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RestController class ErrorController { @@ -104,6 +109,7 @@ or to render a JSON response, as the following example shows: } } ---- +====== TIP: The Servlet API does not provide a way to create error page mappings in Java. You can, however, use both a `WebApplicationInitializer` and a minimal `web.xml`. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/logging.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/logging.adoc index d3ab01eb37e..26e4193d81d 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/logging.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/logging.adoc @@ -25,8 +25,11 @@ through the `enableLoggingRequestDetails` property on `DispatcherServlet`. The following example shows how to do so by using Java configuration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class MyInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -53,8 +56,10 @@ public class MyInitializer } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class MyInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { @@ -75,6 +80,7 @@ public class MyInitializer } } ---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc index a0dfe886c6d..44844ba9a06 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc @@ -28,8 +28,11 @@ To do so: The following example shows how to set a `MultipartConfigElement` on the Servlet registration: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @@ -44,8 +47,10 @@ The following example shows how to set a `MultipartConfigElement` on the Servlet } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- class AppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { @@ -59,6 +64,7 @@ The following example shows how to set a `MultipartConfigElement` on the Servlet } ---- +====== Once the Servlet multipart configuration is in place, you can add a bean of type `StandardServletMultipartResolver` with a name of `multipartResolver`. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-uri-building.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-uri-building.adoc index ea5a7699957..3e18dae861a 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-uri-building.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-uri-building.adoc @@ -15,8 +15,11 @@ include::partial$web/web-uris.adoc[leveloffset=+1] You can use `ServletUriComponentsBuilder` to create URIs relative to the current request, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpServletRequest request = ... @@ -26,8 +29,10 @@ as the following example shows: .replaceQueryParam("accountId", "{id}") .build("123"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val request: HttpServletRequest = ... @@ -37,11 +42,15 @@ as the following example shows: .replaceQueryParam("accountId", "{id}") .build("123") ---- +====== You can create URIs relative to the context path, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpServletRequest request = ... @@ -52,8 +61,10 @@ You can create URIs relative to the context path, as the following example shows .build() .toUri(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val request: HttpServletRequest = ... @@ -64,12 +75,16 @@ You can create URIs relative to the context path, as the following example shows .build() .toUri() ---- +====== You can create URIs relative to a Servlet (for example, `/main/{asterisk}`), as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- HttpServletRequest request = ... @@ -80,8 +95,10 @@ as the following example shows: .build() .toUri(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val request: HttpServletRequest = ... @@ -92,6 +109,7 @@ as the following example shows: .build() .toUri() ---- +====== NOTE: As of 5.1, `ServletUriComponentsBuilder` ignores information from the `Forwarded` and `X-Forwarded-*` headers, which specify the client-originated address. Consider using the @@ -106,8 +124,11 @@ such headers. Spring MVC provides a mechanism to prepare links to controller methods. For example, the following MVC controller allows for link creation: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @Controller @RequestMapping("/hotels/{hotel}") @@ -119,8 +140,10 @@ the following MVC controller allows for link creation: } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @Controller @RequestMapping("/hotels/{hotel}") @@ -132,25 +155,32 @@ the following MVC controller allows for link creation: } } ---- +====== You can prepare a link by referring to the method by name, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42); URI uri = uriComponents.encode().toUri(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uriComponents = MvcUriComponentsBuilder .fromMethodName(BookingController::class.java, "getBooking", 21).buildAndExpand(42) val uri = uriComponents.encode().toUri() ---- +====== In the preceding example, we provide actual method argument values (in this case, the long value: `21`) to be used as a path variable and inserted into the URL. Furthermore, we provide the @@ -163,22 +193,28 @@ There are additional ways to use `MvcUriComponentsBuilder`. For example, you can akin to mock testing through proxies to avoid referring to the controller method by name, as the following example shows (the example assumes static import of `MvcUriComponentsBuilder.on`): +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uriComponents = MvcUriComponentsBuilder .fromMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42) val uri = uriComponents.encode().toUri() ---- +====== NOTE: Controller method signatures are limited in their design when they are supposed to be usable for link creation with `fromMethodCall`. Aside from needing a proper parameter signature, @@ -200,8 +236,11 @@ For such cases, you can use the static `fromXxx` overloaded methods that accept with a base URL and then use the instance-based `withXxx` methods. For example, the following listing uses `withMethodCall`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en"); MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base); @@ -209,8 +248,10 @@ following listing uses `withMethodCall`: URI uri = uriComponents.encode().toUri(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en") val builder = MvcUriComponentsBuilder.relativeTo(base) @@ -218,6 +259,7 @@ following listing uses `withMethodCall`: val uri = uriComponents.encode().toUri() ---- +====== NOTE: As of 5.1, `MvcUriComponentsBuilder` ignores information from the `Forwarded` and `X-Forwarded-*` headers, which specify the client-originated address. Consider using the @@ -234,8 +276,11 @@ by referring to the implicitly or explicitly assigned name for each request mapp Consider the following example: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- @RequestMapping("/people/{id}/addresses") public class PersonAddressController { @@ -244,8 +289,10 @@ Consider the following example: public HttpEntity getAddress(@PathVariable String country) { ... } } ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- @RequestMapping("/people/{id}/addresses") class PersonAddressController { @@ -254,6 +301,7 @@ Consider the following example: fun getAddress(@PathVariable country: String): HttpEntity { ... } } ---- +====== Given the preceding controller, you can prepare a link from a JSP, as follows: diff --git a/framework-docs/modules/ROOT/partials/web/web-uris.adoc b/framework-docs/modules/ROOT/partials/web/web-uris.adoc index b644cd2e170..a0e8218b4a4 100644 --- a/framework-docs/modules/ROOT/partials/web/web-uris.adoc +++ b/framework-docs/modules/ROOT/partials/web/web-uris.adoc @@ -4,8 +4,11 @@ `UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- UriComponents uriComponents = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") // <1> @@ -15,6 +18,7 @@ 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. @@ -41,8 +45,11 @@ The preceding example can be consolidated into one chain and shortened with `buildAndExpand`, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") @@ -51,8 +58,10 @@ as the following example shows: .buildAndExpand("Westin", "123") .toUri(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") @@ -61,43 +70,56 @@ as the following example shows: .buildAndExpand("Westin", "123") .toUri() ---- +====== You can shorten it further by going directly to a URI (which implies encoding), as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123") ---- +====== You can shorten it further still with a full URI template, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}?q={q}") .build("Westin", "123"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}?q={q}") .build("Westin", "123") ---- +====== @@ -117,8 +139,11 @@ exposes shared configuration options. The following example shows how to configure a `RestTemplate`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; @@ -129,8 +154,10 @@ The following example shows how to configure a `RestTemplate`: RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(factory); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode @@ -141,11 +168,15 @@ The following example shows how to configure a `RestTemplate`: val restTemplate = RestTemplate() restTemplate.uriTemplateHandler = factory ---- +====== The following example configures a `WebClient`: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; @@ -155,8 +186,10 @@ The following example configures a `WebClient`: WebClient client = WebClient.builder().uriBuilderFactory(factory).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode @@ -166,13 +199,17 @@ The following example configures a `WebClient`: val client = WebClient.builder().uriBuilderFactory(factory).build() ---- +====== In addition, you can also use `DefaultUriBuilderFactory` directly. It is similar to using `UriComponentsBuilder` but, instead of static factory methods, it is an actual instance that holds configuration and preferences, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String baseUrl = "https://example.com"; DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl); @@ -181,8 +218,10 @@ that holds configuration and preferences, as the following example shows: .queryParam("q", "{q}") .build("Westin", "123"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val baseUrl = "https://example.com" val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl) @@ -191,6 +230,7 @@ that holds configuration and preferences, as the following example shows: .queryParam("q", "{q}") .build("Westin", "123") ---- +====== [[uri-encoding]] @@ -219,8 +259,11 @@ incidentally looks like a URI variable. The following example uses the first option: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") @@ -230,8 +273,10 @@ The following example uses the first option: // Result is "/hotel%20list/New%20York?q=foo%2Bbar" ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") @@ -241,46 +286,62 @@ The following example uses the first option: // Result is "/hotel%20list/New%20York?q=foo%2Bbar" ---- +====== You can shorten the preceding example by going directly to the URI (which implies encoding), as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .build("New York", "foo+bar"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .build("New York", "foo+bar") ---- +====== You can shorten it further still with a full URI template, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}") .build("New York", "foo+bar"); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}") .build("New York", "foo+bar") ---- +====== The `WebClient` and the `RestTemplate` expand and encode URI templates internally through the `UriBuilderFactory` strategy. Both can be configured with a custom strategy, as the following example shows: +[tabs] +====== +Java:: ++ [source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ---- String baseUrl = "https://example.com"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl) @@ -293,8 +354,10 @@ as the following example shows: // Customize the WebClient.. WebClient client = WebClient.builder().uriBuilderFactory(factory).build(); ---- + +Kotlin:: ++ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ---- val baseUrl = "https://example.com" val factory = DefaultUriBuilderFactory(baseUrl).apply { @@ -309,6 +372,7 @@ as the following example shows: // Customize the WebClient.. val client = WebClient.builder().uriBuilderFactory(factory).build() ---- +====== The `DefaultUriBuilderFactory` implementation uses `UriComponentsBuilder` internally to expand and encode URI templates. As a factory, it provides a single place to configure