From 96658235c8b44bd195cafd1894378e8e2a4678c4 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Mon, 12 Aug 2019 15:39:49 +0200 Subject: [PATCH] Add Kotlin code snippets to core refdoc This commit introduces Kotlin code snippets, for now in the core reference documentation. Other sections will follow, as well as improvements like global language switch. See gh-21778 --- build.gradle | 2 + gradle/docs.gradle | 2 +- src/docs/asciidoc/core/core-aop-api.adoc | 423 ++- src/docs/asciidoc/core/core-aop.adoc | 1219 ++++-- src/docs/asciidoc/core/core-appendix.adoc | 449 ++- src/docs/asciidoc/core/core-beans.adoc | 3276 +++++++++++++---- .../asciidoc/core/core-databuffer-codec.adoc | 19 +- src/docs/asciidoc/core/core-expressions.adoc | 854 ++++- src/docs/asciidoc/core/core-resources.adoc | 220 +- src/docs/asciidoc/core/core-validation.adoc | 676 +++- 10 files changed, 5758 insertions(+), 1382 deletions(-) diff --git a/build.gradle b/build.gradle index 63b499dcd3..7ac9fe21cb 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { classpath("io.spring.gradle:propdeps-plugin:0.0.9.RELEASE") classpath("io.spring.nohttp:nohttp-gradle:0.0.3.RELEASE") classpath("org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.16") + classpath("io.spring.asciidoctor:spring-asciidoctor-extensions:0.1.3.RELEASE") } } @@ -303,6 +304,7 @@ configure(rootProject) { testCompile("org.aspectj:aspectjweaver:${aspectjVersion}") testCompile("org.hsqldb:hsqldb:${hsqldbVersion}") testCompile("org.hibernate:hibernate-core:5.1.17.Final") + asciidoctor("io.spring.asciidoctor:spring-asciidoctor-extensions:0.1.3.RELEASE") } artifacts { diff --git a/gradle/docs.gradle b/gradle/docs.gradle index a3c49032c3..ebd51bd56a 100644 --- a/gradle/docs.gradle +++ b/gradle/docs.gradle @@ -129,7 +129,7 @@ asciidoctor { 'highlightjsdir=js/highlight', 'highlightjs-theme=atom-one-dark-reasonable', stylesdir: "css/", - stylesheet: 'spring.css', + stylesheet: 'stylesheet.css', 'spring-version': project.version } diff --git a/src/docs/asciidoc/core/core-aop-api.adoc b/src/docs/asciidoc/core/core-aop-api.adoc index ec6a5a5ef2..570fc830b6 100644 --- a/src/docs/asciidoc/core/core-aop-api.adoc +++ b/src/docs/asciidoc/core/core-aop-api.adoc @@ -25,8 +25,8 @@ target different advice with the same pointcut. The `org.springframework.aop.Pointcut` interface is the central interface, used to target advices to particular classes and methods. The complete interface follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface Pointcut { @@ -36,6 +36,17 @@ target advices to particular classes and methods. The complete interface follows } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface Pointcut { + + fun getClassFilter(): ClassFilter + + fun getMethodMatcher(): MethodMatcher + + } +---- Splitting the `Pointcut` interface into two parts allows reuse of class and method matching parts and fine-grained composition operations (such as performing a "`union`" @@ -45,19 +56,27 @@ The `ClassFilter` interface is used to restrict the pointcut to a given set of t classes. If the `matches()` method always returns true, all target classes are matched. The following listing shows the `ClassFilter` interface definition: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface ClassFilter { boolean matches(Class clazz); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface ClassFilter { + + fun matches(clazz: Class<*>): Boolean + } +---- The `MethodMatcher` interface is normally more important. The complete interface follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface MethodMatcher { @@ -68,6 +87,18 @@ The `MethodMatcher` interface is normally more important. The complete interface boolean matches(Method m, Class targetClass, Object[] args); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface MethodMatcher { + + val isRuntime: Boolean + + fun matches(m: Method, targetClass: Class<*>): Boolean + + fun matches(m: Method, targetClass: Class<*>, args: Array): Boolean + } +---- The `matches(Method, Class)` method is used to test whether this pointcut ever matches a given method on a target class. This evaluation can be performed when an AOP @@ -143,8 +174,7 @@ effectively the union of these pointcuts.) The following example shows how to use `JdkRegexpMethodPointcut`: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -163,8 +193,7 @@ throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPoi Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both pointcut and advice, as the following example shows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -225,8 +254,8 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- class TestStaticPointcut extends StaticMethodMatcherPointcut { @@ -235,6 +264,16 @@ following example shows how to subclass `StaticMethodMatcherPointcut`: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class TestStaticPointcut : StaticMethodMatcherPointcut() { + + override fun matches(method: Method, targetClass: Class<*>): Boolean { + // return true if custom criteria match + } + } +---- There are also superclasses for dynamic pointcuts. You can use custom pointcuts with any advice type. @@ -296,14 +335,22 @@ Spring is compliant with the AOP `Alliance` interface for around advice that use interception. Classes that implement `MethodInterceptor` and that implement around advice should also implement the following interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface MethodInterceptor : Interceptor { + + fun invoke(invocation: MethodInvocation) : Any + } +---- The `MethodInvocation` argument to the `invoke()` method exposes the method being invoked, the target join point, the AOP proxy, and the arguments to the method. The @@ -312,8 +359,8 @@ point. The following example shows a simple `MethodInterceptor` implementation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class DebugInterceptor implements MethodInterceptor { @@ -325,6 +372,19 @@ The following example shows a simple `MethodInterceptor` implementation: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class DebugInterceptor : MethodInterceptor { + + override fun invoke(invocation: MethodInvocation): Any { + println("Before: invocation=[$invocation]") + val rval = invocation.proceed() + println("Invocation returned") + return rval + } + } +---- 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 @@ -353,14 +413,22 @@ interceptor chain. The following listing shows the `MethodBeforeAdvice` interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +interface MethodBeforeAdvice : BeforeAdvice { + + fun before(m: Method, args: Array, target: Any) +} +---- (Spring's API design would allow for field before advice, although the usual objects apply to field interception and it is @@ -375,8 +443,8 @@ wrapped in an unchecked exception by the AOP proxy. The following example shows a before advice in Spring, which counts all method invocations: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class CountingBeforeAdvice implements MethodBeforeAdvice { @@ -391,6 +459,18 @@ The following example shows a before advice in Spring, which counts all method i } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class CountingBeforeAdvice : MethodBeforeAdvice { + + var count: Int = 0 + + override fun before(m: Method, args: Array, target: Any?) { + ++count + } + } +---- TIP: Before advice can be used with any pointcut. @@ -404,8 +484,7 @@ an exception. Spring offers typed throws advice. Note that this means that the tag interface identifying that the given object implements one or more typed throws advice methods. These should be in the following form: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes"] ---- afterThrowing([Method, args, target], subclassOfThrowable) ---- @@ -416,8 +495,8 @@ 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): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class RemoteThrowsAdvice implements ThrowsAdvice { @@ -426,13 +505,23 @@ The following advice is invoked if a `RemoteException` is thrown (including from } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class RemoteThrowsAdvice : ThrowsAdvice { + + fun afterThrowing(ex: RemoteException) { + // Do something with remote exception + } + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class ServletThrowsAdviceWithArguments implements ThrowsAdvice { @@ -441,13 +530,23 @@ arguments, and target object. The following advice is invoked if a `ServletExcep } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class ServletThrowsAdviceWithArguments : ThrowsAdvice { + + fun afterThrowing(m: Method, args: Array, target: Any, ex: ServletException) { + // Do something with all arguments + } + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public static class CombinedThrowsAdvice implements ThrowsAdvice { @@ -460,6 +559,20 @@ methods can be combined in a single class. The following listing shows the final } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class CombinedThrowsAdvice : ThrowsAdvice { + + fun afterThrowing(ex: RemoteException) { + // Do something with remote exception + } + + fun afterThrowing(m: Method, args: Array, target: Any, ex: ServletException) { + // Do something with all arguments + } + } +---- 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 @@ -478,8 +591,8 @@ TIP: Throws advice can be used with any pointcut. An after returning advice in Spring must implement the `org.springframework.aop.AfterReturningAdvice` interface, which the following listing shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface AfterReturningAdvice extends Advice { @@ -487,6 +600,14 @@ An after returning advice in Spring must implement the throws Throwable; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface AfterReturningAdvice : Advice { + + fun afterReturning(returnValue: Any, m: Method, args: Array, target: Any) + } +---- An after returning advice has access to the return value (which it cannot modify), the invoked method, the method's arguments, and the target. @@ -494,8 +615,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class CountingAfterReturningAdvice implements AfterReturningAdvice { @@ -511,6 +632,19 @@ not thrown exceptions: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class CountingAfterReturningAdvice : AfterReturningAdvice { + + var count: Int = 0 + private set + + override fun afterReturning(returnValue: Any?, m: Method, args: Array, target: Any?) { + ++count + } + } +---- 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. @@ -526,14 +660,22 @@ Spring treats introduction advice as a special kind of interception advice. Introduction requires an `IntroductionAdvisor` and an `IntroductionInterceptor` that implement the following interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface IntroductionInterceptor : MethodInterceptor { + + fun implementsInterface(intf: Class<*>): Boolean + } +---- The `invoke()` method inherited from the AOP Alliance `MethodInterceptor` interface must implement the introduction. That is, if the invoked method is on an introduced @@ -544,8 +686,8 @@ Introduction advice cannot be used with any pointcut, as it applies only at the rather than the method, level. You can only use introduction advice with the `IntroductionAdvisor`, which has the following methods: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface IntroductionAdvisor extends Advisor, IntroductionInfo { @@ -559,6 +701,22 @@ rather than the method, level. You can only use introduction advice with the Class[] getInterfaces(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface IntroductionAdvisor : Advisor, IntroductionInfo { + + val classFilter: ClassFilter + + @Throws(IllegalArgumentException::class) + fun validateInterfaces() + } + + interface IntroductionInfo { + + val interfaces: Array> + } +---- There is no `MethodMatcher` and, hence, no `Pointcut` associated with introduction advice. Only class filtering is logical. @@ -571,8 +729,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface Lockable { void lock(); @@ -580,6 +738,15 @@ introduce the following interface to one or more objects: boolean locked(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface Lockable { + fun lock() + fun unlock() + 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 @@ -616,8 +783,8 @@ to that held in the target object. The following example shows the example `LockMixin` class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable { @@ -644,6 +811,34 @@ The following example shows the example `LockMixin` class: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class LockMixin : DelegatingIntroductionInterceptor(), Lockable { + + private var locked: Boolean = false + + fun lock() { + this.locked = true + } + + fun unlock() { + this.locked = false + } + + fun locked(): Boolean { + return this.locked + } + + override fun invoke(invocation: MethodInvocation): Any? { + if (locked() && invocation.method.name.indexOf("set") == 0) { + throw LockedException() + } + return super.invoke(invocation) + } + + } +---- Often, you need not override the `invoke()` method. The `DelegatingIntroductionInterceptor` implementation (which calls the `delegate` method if @@ -658,8 +853,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class LockMixinAdvisor extends DefaultIntroductionAdvisor { @@ -668,6 +863,11 @@ The following example shows our `LockMixinAdvisor` class: } } ---- +[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 @@ -857,8 +1057,7 @@ Consider a simple example of `ProxyFactoryBean` in action. This example involves The following listing shows the example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -899,17 +1098,21 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Person person = (Person) factory.getBean("person"); ---- +[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: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -926,8 +1129,7 @@ inner bean. Only the `ProxyFactoryBean` definition is different. The advice is included only for completeness. The following example shows how to use an anonymous inner bean: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1004,8 +1206,7 @@ the part before the asterisk are added to the advisor chain. This can come in ha if you need to add a standard set of "`global`" advisors. The following example defines two global advisors: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1032,8 +1233,7 @@ definitions, can result in much cleaner and more concise proxy definitions. First, we create a parent, template, bean definition for the proxy, as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1051,8 +1251,7 @@ that needs to be created is a child bean definition, which wraps the target of t proxy as an inner bean definition, since the target is never used on its own anyway. The following example shows such a child bean: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1065,8 +1264,7 @@ The following example shows such a child bean: You can override properties from the parent template. In the following example, we override the transaction propagation settings: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1107,14 +1305,22 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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(); ---- +[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 @@ -1145,8 +1351,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Advisor[] getAdvisors(); @@ -1168,6 +1374,36 @@ following methods: boolean isFrozen(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun getAdvisors(): Array + + @Throws(AopConfigException::class) + fun addAdvice(advice: Advice) + + @Throws(AopConfigException::class) + fun addAdvice(pos: Int, advice: Advice) + + @Throws(AopConfigException::class) + fun addAdvisor(advisor: Advisor) + + @Throws(AopConfigException::class) + fun addAdvisor(pos: Int, advisor: Advisor) + + fun indexOf(advisor: Advisor): Int + + @Throws(AopConfigException::class) + fun removeAdvisor(advisor: Advisor): Boolean + + @Throws(AopConfigException::class) + fun removeAdvisor(index: Int) + + @Throws(AopConfigException::class) + fun replaceAdvisor(a: Advisor, b: Advisor): Boolean + + 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 @@ -1189,8 +1425,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Advised advised = (Advised) myObject; Advisor[] advisors = advised.getAdvisors(); @@ -1207,6 +1443,24 @@ manipulating its advice: assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val advised = myObject as Advised + val advisors = advised.advisors + val oldAdvisorCount = advisors.size + println("$oldAdvisorCount advisors") + + // Add an advice like an interceptor without a pointcut + // Will match all proxied methods + // Can use for interceptors, before, after returning or throws advice + advised.addAdvice(DebugInterceptor()) + + // Add selective advice using a pointcut + advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)) + + 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. @@ -1261,8 +1515,7 @@ The `BeanNameAutoProxyCreator` class is a `BeanPostProcessor` that automatically AOP proxies for beans with names that match literal values or wildcards. The following example shows how to create a `BeanNameAutoProxyCreator` bean: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1324,8 +1577,7 @@ bean`" idiom shown earlier also offers this benefit.) The following example creates a `DefaultAdvisorAutoProxyCreator` bean and the other elements discussed in this section: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1394,17 +1646,22 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); Object oldTarget = swapper.swap(newTarget); ---- +[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: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1448,8 +1705,7 @@ NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framewo The following listing shows an example configuration: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1483,8 +1739,7 @@ You can configure Spring to be able to cast any pooled object to the about the configuration and current size of the pool through an introduction. You need to define an advisor similar to the following: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1499,12 +1754,18 @@ the `ProxyFactoryBean` that exposes the pooled object. The cast is defined as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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()); ---- +[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 @@ -1527,8 +1788,7 @@ use this approach without very good reason. To do this, you could modify the `poolTargetSource` definition shown earlier as follows (we also changed the name, for clarity): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1550,8 +1810,7 @@ facility to transparently store a resource alongside a thread. Setting up a `ThreadLocalTargetSource` is pretty much the same as was explained for the other types of target source, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- diff --git a/src/docs/asciidoc/core/core-aop.adoc b/src/docs/asciidoc/core/core-aop.adoc index 3e4c64b1da..a342ab9421 100644 --- a/src/docs/asciidoc/core/core-aop.adoc +++ b/src/docs/asciidoc/core/core-aop.adoc @@ -233,8 +233,8 @@ classpath of your application (version 1.8 or later). This library is available To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy` annotation, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableAspectJAutoProxy @@ -242,7 +242,13 @@ annotation, as the following example shows: } ---- - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableAspectJAutoProxy + class AppConfig +---- [[aop-enable-aspectj-xml]] ==== Enabling @AspectJ Support with XML Configuration @@ -250,8 +256,7 @@ annotation, as the following example shows: To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy` element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -274,8 +279,7 @@ minimal definition required for a not-very-useful aspect. The first of the two example shows a regular bean definition in the application context that points to a bean class that has the `@Aspect` annotation: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -285,8 +289,8 @@ context that points to a bean class that has the `@Aspect` annotation: The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is annotated with the `org.aspectj.lang.annotation.Aspect` annotation; -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.xyz; import org.aspectj.lang.annotation.Aspect; @@ -296,6 +300,16 @@ which is annotated with the `org.aspectj.lang.annotation.Aspect` annotation; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.xyz + + import org.aspectj.lang.annotation.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) @@ -333,11 +347,17 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @Pointcut("execution(* transfer(..))")// the pointcut expression - private void anyOldTransfer() {}// the pointcut signature + @Pointcut("execution(* transfer(..))") // the pointcut expression + private void anyOldTransfer() {} // the pointcut signature +---- +[source,kotlin,indent=0,subs="verbatim,quotes",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 @@ -421,8 +441,13 @@ Spring AOP also supports an additional PCD named `bean`. This PCD lets you limit the matching of join points to a particular named Spring bean or to a set of named Spring beans (when using wildcards). The `bean` PCD has the following form: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + bean(idOrNameOfBean) +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- bean(idOrNameOfBean) ---- @@ -453,23 +478,36 @@ 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: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- + // matches if a method execution join point represents the execution of any public method @Pointcut("execution(public * *(..))") - private void anyPublicOperation() {} <1> + private void anyPublicOperation() {} + // matches if a method execution is in the trading module @Pointcut("within(com.xyz.someapp.trading..*)") - private void inTrading() {} <2> + private void inTrading() {} + // matches if a method execution represents any public method in the trading module @Pointcut("anyPublicOperation() && inTrading()") - private void tradingOperation() {} <3> + private void tradingOperation() {} +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // matches if a method execution join point represents the execution of any public method. + @Pointcut("execution(public * *(..))") + private fun anyPublicOperation() {} + + // matches if a method execution is in the trading module + @Pointcut("within(com.xyz.someapp.trading..*)") + private fun inTrading() {} + + // matches if a method execution represents any public method in the trading module + @Pointcut("anyPublicOperation() && inTrading()") + private fun tradingOperation() {} ---- -<1> `anyPublicOperation` matches if a method execution join point represents the execution -of any public method. -<2> `inTrading` matches if a method execution is in the trading module. -<3> `tradingOperation` matches if a method execution represents any public method in the -trading module. It is a best practice to build more complex pointcut expressions out of smaller named components, as shown earlier. When referring to pointcuts by name, normal Java visibility @@ -486,8 +524,8 @@ application and particular sets of operations from within several aspects. We re defining a "`SystemArchitecture`" aspect that captures common pointcut expressions for this purpose. Such an aspect typically resembles the following example: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.xyz.someapp; @@ -548,13 +586,81 @@ this purpose. Such an aspect typically resembles the following example: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.xyz.someapp + + import org.aspectj.lang.annotation.Aspect + import org.aspectj.lang.annotation.Pointcut + + import org.springframework.aop.Pointcut + + @Aspect + class SystemArchitecture { + + /** + * A join point is in the web layer if the method is defined + * in a type in the com.xyz.someapp.web package or any sub-package + * under that. + */ + @Pointcut("within(com.xyz.someapp.web..*)") + fun inWebLayer() { + } + + /** + * A join point is in the service layer if the method is defined + * in a type in the com.xyz.someapp.service package or any sub-package + * under that. + */ + @Pointcut("within(com.xyz.someapp.service..*)") + fun inServiceLayer() { + } + + /** + * A join point is in the data access layer if the method is defined + * in a type in the com.xyz.someapp.dao package or any sub-package + * under that. + */ + @Pointcut("within(com.xyz.someapp.dao..*)") + fun inDataAccessLayer() { + } + + /** + * A business service is the execution of any method defined on a service + * interface. This definition assumes that interfaces are placed in the + * "service" package, and that implementation types are in sub-packages. + * + * If you group service interfaces by functional area (for example, + * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then + * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))" + * could be used instead. + * + * Alternatively, you can write the expression using the 'bean' + * PCD, like so "bean(*Service)". (This assumes that you have + * named your Spring service beans in a consistent fashion.) + */ + @Pointcut("execution(* com.xyz.someapp..service.*.*(..))") + fun businessService() { + } + + /** + * A data access operation is the execution of any method defined on a + * dao interface. This definition assumes that interfaces are placed in the + * "dao" package, and that implementation types are in sub-packages. + */ + @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") + fun dataAccessOperation() { + } + + } +---- You can refer to the pointcuts defined in such an aspect anywhere you need a pointcut expression. For example, to make the service layer transactional, you could write the following: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- { void sampleGenericMethod(T param); void sampleGenericCollectionMethod(Collection param); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",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 typing the advice parameter to the parameter type for which you want to intercept the method: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") public void beforeSampleMethod(MyType param) { // Advice implementation } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",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: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)") public void beforeSampleMethod(Collection param) { // Advice implementation } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",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 @@ -1219,9 +1499,9 @@ following strategy to determine parameter names: an optional `argNames` attribute that you can use to specify the argument names of the annotated method. These argument names are available at runtime. The following example shows how to use the `argNames` attribute: -+ -[source,java,indent=0] -[subs="verbatim,quotes"] + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") @@ -1230,14 +1510,24 @@ following strategy to determine parameter names: // ... use code and bean } ---- -+ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable") + fun audit(bean: Any, auditable: Auditable) { + val code = auditable.value() + // ... use code and bean + } +---- + + If the first parameter is of the `JoinPoint`, `ProceedingJoinPoint`, or `JoinPoint.StaticPart` type, you can leave out the name of the parameter from the value of the `argNames` attribute. For example, if you modify the preceding advice to receive the join point object, the `argNames` attribute need not include it: -+ -[source,java,indent=0] -[subs="verbatim,quotes"] + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") @@ -1246,21 +1536,38 @@ the join point object, the `argNames` attribute need not include it: // ... use code, bean, and jp } ---- -+ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable") + fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) { + val code = auditable.value() + // ... use code, bean, and jp + } +---- + The special treatment given to the first parameter of the `JoinPoint`, `ProceedingJoinPoint`, and `JoinPoint.StaticPart` types is particularly convenient for advice instances that do not collect any other join point context. In such situations, you may omit the `argNames` attribute. For example, the following advice need not declare the `argNames` attribute: -+ -[source,java,indent=0] -[subs="verbatim,quotes"] + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Before("com.xyz.lib.Pointcuts.anyPublicMethod()") public void audit(JoinPoint jp) { // ... use jp } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Before("com.xyz.lib.Pointcuts.anyPublicMethod()") + fun audit(jp: JoinPoint) { + // ... use jp + } +---- * Using the `'argNames'` attribute is a little clumsy, so if the `'argNames'` attribute has not been specified, Spring AOP looks at the debug information for the @@ -1292,8 +1599,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Around("execution(List find*(..)) && " + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + @@ -1304,6 +1611,18 @@ The following example shows how to do so: return pjp.proceed(new Object[] {newPattern}); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Around("execution(List find*(..)) && " + + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + + "args(accountHolderNamePattern)") + fun preProcessQueryPattern(pjp: ProceedingJoinPoint, + accountHolderNamePattern: String): Any { + val newPattern = preProcess(accountHolderNamePattern) + return pjp.proceed(arrayOf(newPattern)) + } +---- In many cases, you do this binding anyway (as in the preceding example). @@ -1347,8 +1666,8 @@ interface named `UsageTracked` and an implementation of that interface named `De the following aspect declares that all implementors of service interfaces also implement the `UsageTracked` interface (to expose statistics via JMX for example): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Aspect public class UsageTracking { @@ -1363,6 +1682,23 @@ the `UsageTracked` interface (to expose statistics via JMX for example): } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Aspect + class UsageTracking { + + companion object { + @DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class) + lateinit var mixin: UsageTracked + } + + @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)") + fun recordUsage(usageTracked: UsageTracked) { + usageTracked.incrementUseCount() + } + } +---- 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 @@ -1371,12 +1707,16 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- UsageTracked usageTracked = (UsageTracked) context.getBean("myService"); ---- - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val usageTracked = context.getBean("myService") as UsageTracked +---- [[aop-instantiation-models]] @@ -1394,8 +1734,8 @@ supported). You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect` annotation. Consider the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") public class MyAspect { @@ -1409,6 +1749,21 @@ annotation. Consider the following example: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") + class MyAspect { + + private val someState: Int = 0 + + @Before(com.xyz.myapp.SystemArchitecture.businessService()) + fun recordServiceUsage() { + // ... + } + + } +---- In the preceding example, the effect of the `'perthis'` clause is that one aspect instance is created for each unique service object that executes a business service (each unique object bound to @@ -1443,8 +1798,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Aspect public class ConcurrentOperationExecutor implements Ordered { @@ -1484,6 +1839,45 @@ call `proceed` multiple times. The following listing shows the basic aspect impl } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Aspect + class ConcurrentOperationExecutor : Ordered { + + private val DEFAULT_MAX_RETRIES = 2 + private var maxRetries = DEFAULT_MAX_RETRIES + private var order = 1 + + fun setMaxRetries(maxRetries: Int) { + this.maxRetries = maxRetries + } + + override fun getOrder(): Int { + return this.order + } + + fun setOrder(order: Int) { + this.order = order + } + + @Around("com.xyz.myapp.SystemArchitecture.businessService()") + fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any { + var numAttempts = 0 + var lockFailureException: PessimisticLockingFailureException + do { + numAttempts++ + try { + return pjp.proceed() + } catch (ex: PessimisticLockingFailureException) { + lockFailureException = ex + } + + } while (numAttempts <= this.maxRetries) + throw lockFailureException + } + } +---- 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 @@ -1495,8 +1889,7 @@ we have exhausted all of our retry attempts. The corresponding Spring configuration follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1509,29 +1902,42 @@ The corresponding Spring configuration follows: To refine the aspect so that it retries only idempotent operations, we might define the following `Idempotent` annotation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Retention(AnnotationRetention.RUNTIME) + annotation class Idempotent// marker annotation +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") + fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any { + // ... } ---- - @@ -1574,8 +1980,7 @@ methods of the object, and the pointcut and advice information are captured in t You can declare an aspect by using the element, and reference the backing bean by using the `ref` attribute, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1602,8 +2007,7 @@ definition be shared across several aspects and advisors. A pointcut that represents the execution of any business service in the service layer can be defined as follows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -1618,8 +2022,7 @@ language as described in <>. If you use the schema based declarat style, you can refer to named pointcuts defined in types (@Aspects) within the pointcut expression. Another way of defining the above pointcut would be as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1634,8 +2037,7 @@ Assume that you have a `SystemArchitecture` aspect as described in < @@ -1655,8 +2057,7 @@ In much the same way as an @AspectJ aspect, pointcuts declared by using the sche definition style can collect join point context. For example, the following pointcut collects the `this` object as the join point context and passes it to the advice: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -1677,11 +2078,18 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public void monitor(Object service) { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun monitor(service: Any) { + // ... } ---- @@ -1689,8 +2097,7 @@ When combining pointcut sub-expressions, `&&` is awkward within an XML document, you can use the `and`, `or`, and `not` keywords in place of `&&`, `||`, and `!`, respectively. For example, the previous pointcut can be better written as follows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -1726,8 +2133,7 @@ exactly the same semantics. Before advice runs before a matched method execution. It is declared inside an `` by using the element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1744,8 +2150,7 @@ Here, `dataAccessOperation` is the `id` of a pointcut defined at the top (` @@ -1775,8 +2180,7 @@ After returning advice runs when a matched method execution completes normally. declared inside an `` in the same way as before advice. The following example shows how to declare it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1793,8 +2197,7 @@ As in the @AspectJ style, you can get the return value within the advice body. To do so, use the returning attribute to specify the name of the parameter to which the return value should be passed, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1812,11 +2215,16 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public void doAccessCheck(Object retVal) {... ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun doAccessCheck(retVal: Any) {... +---- [[aop-schema-advice-after-throwing]] @@ -1826,8 +2234,7 @@ After throwing advice executes when a matched method execution exits by throwing exception. It is declared inside an `` by using the after-throwing element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1844,8 +2251,7 @@ As in the @AspectJ style, you can get the thrown exception within the advice body. To do so, use the throwing attribute to specify the name of the parameter to which the exception should be passed as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1863,11 +2269,16 @@ The `doRecoveryActions` method must declare a parameter named `dataAccessEx`. Th this parameter constrains matching in the same way as described for `@AfterThrowing`. For example, the method signature may be declared as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public void doRecoveryActions(DataAccessException dataAccessEx) {... ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun doRecoveryActions(dataAccessEx: DataAccessException) {... +---- [[aop-schema-advice-after-finally]] @@ -1876,8 +2287,7 @@ example, the method signature may be declared as follows: After (finally) advice runs no matter how a matched method execution exits. You can declare it by using the `after` element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1910,8 +2320,7 @@ in the array are used as the arguments to the method execution when it proceeds. <> for notes on calling `proceed` with an `Object[]`. The following example shows how to declare around advice in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1927,8 +2336,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch @@ -1937,6 +2346,16 @@ The implementation of the `doBasicProfiling` advice can be exactly the same as i return retVal; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun doBasicProfiling(pjp: ProceedingJoinPoint): Any { + // start stopwatch + val retVal = pjp.proceed() + // stop stopwatch + return pjp.proceed() + } +---- [[aop-schema-params]] @@ -1951,8 +2370,7 @@ attribute of the advice element, which is treated in the same manner as the `arg attribute in an advice annotation (as described in <>). The following example shows how to specify an argument name in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2122,13 +2583,20 @@ through JMX for example.) The class that backs the `usageTracking` bean would then contain the following method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",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 @@ -2137,11 +2605,16 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- UsageTracked usageTracked = (UsageTracked) context.getBean("myService"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val usageTracked = context.getBean("myService") as UsageTracked +---- @@ -2166,8 +2639,7 @@ Spring supports the advisor concept with the `` element. You most commonly see it used in conjunction with transactional advice, which also has its own namespace support in Spring. The following example shows an advisor: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -2214,8 +2686,8 @@ 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): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class ConcurrentOperationExecutor implements Ordered { @@ -2253,6 +2725,44 @@ call `proceed` multiple times. The following listing shows the basic aspect impl } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class ConcurrentOperationExecutor : Ordered { + + private val DEFAULT_MAX_RETRIES = 2 + + private var maxRetries = DEFAULT_MAX_RETRIES + private var order = 1 + + fun setMaxRetries(maxRetries: Int) { + this.maxRetries = maxRetries + } + + override fun getOrder(): Int { + return this.order + } + + fun setOrder(order: Int) { + this.order = order + } + + fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any { + var numAttempts = 0 + var lockFailureException: PessimisticLockingFailureException + do { + numAttempts++ + try { + return pjp.proceed() + } catch (ex: PessimisticLockingFailureException) { + lockFailureException = ex + } + + } while (numAttempts <= this.maxRetries) + throw lockFailureException + } + } +---- 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 @@ -2266,8 +2776,7 @@ annotations removed. The corresponding Spring configuration is as follows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -2296,21 +2805,28 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Retention(AnnotationRetention.RUNTIME) + annotation class Idempotent { + // marker annotation + } +---- The change to the aspect to retry only idempotent operations involves refining the pointcut expression so that only `@Idempotent` operations match, as follows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -2453,8 +2980,7 @@ you can do so. However, you should consider the following issues: To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute of the `` element to true, as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2465,8 +2991,7 @@ To force CGLIB proxying when you use the @AspectJ auto-proxy support, set the `proxy-target-class` attribute of the `` element to `true`, as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -2497,8 +3022,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimplePojo implements Pojo { @@ -2512,14 +3037,29 @@ code snippet shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimplePojo : Pojo { + + fun foo() { + // this next method invocation is a direct call on the 'this' reference + this.bar() + } + + fun bar() { + // some logic... + } + } +---- 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::images/aop-proxy-plain-pojo-call.png[] -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Main { @@ -2530,14 +3070,23 @@ image::images/aop-proxy-plain-pojo-call.png[] } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun main() { + val pojo = SimplePojo() + // this is a direct method call on the 'pojo' reference + pojo.foo() + } +---- Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet: image::images/aop-proxy-call.png[] -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Main { @@ -2552,6 +3101,19 @@ image::images/aop-proxy-call.png[] } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +fun main() { + val factory = ProxyFactory(SimplePojo()) + factory.addInterface(Pojo::class.java) + factory.addAdvice(RetryAdvice()) + + val pojo = factory.proxy as Pojo + // this is a method call on the proxy! + 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 @@ -2570,8 +3132,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimplePojo implements Pojo { @@ -2585,14 +3147,29 @@ within your class to Spring AOP, as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimplePojo : Pojo { + + fun foo() { + // this works, but... gah! + (AopContext.currentProxy() as Pojo).bar() + } + + fun bar() { + // some logic... + } + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Main { @@ -2608,6 +3185,20 @@ following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun main() { + val factory = ProxyFactory(SimplePojo()) + factory.adddInterface(Pojo::class.java) + factory.addAdvice(RetryAdvice()) + factory.isExposeProxy = true + + val pojo = factory.proxy as Pojo + // this is a method call on the proxy! + 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. @@ -2628,8 +3219,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // create a factory that can generate a proxy for the given target object AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); @@ -2644,6 +3235,22 @@ The basic usage for this class is very simple, as the following example shows: // now get the proxy object... MyInterfaceType proxy = factory.getProxy(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // create a factory that can generate a proxy for the given target object + val factory = AspectJProxyFactory(targetObject) + + // add an aspect, the class must be an @AspectJ aspect + // you can call this as many times as you need with different aspects + factory.addAspect(SecurityManager::class.java) + + // you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect + factory.addAspect(usageTracker) + + // now get the proxy object... + val proxy = factory.getProxy() +---- See the {api-spring-framework}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information. @@ -2684,8 +3291,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.xyz.myapp.domain; @@ -2696,6 +3303,18 @@ following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.xyz.myapp.domain + + import org.springframework.beans.factory.annotation.Configurable + + @Configurable + class Account { + // ... + } +---- 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 @@ -2704,8 +3323,7 @@ prototype-scoped) with the same name as the fully-qualified type name fully-qualified name of its type, a convenient way to declare the prototype definition is to omit the `id` attribute, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2715,8 +3333,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.xyz.myapp.domain; @@ -2727,6 +3345,18 @@ can do so directly in the annotation, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.xyz.myapp.domain + + import org.springframework.beans.factory.annotation.Configurable + + @Configurable("account") + class Account { + // ... + } +---- Spring now looks for a bean definition named `account` and uses that as the definition to configure new `Account` instances. @@ -2766,8 +3396,13 @@ dependencies to be injected before the constructor bodies execute and thus be available for use in the body of the constructors, you need to define this on the `@Configurable` declaration, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configurable(preConstruction = true) +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- @Configurable(preConstruction = true) ---- @@ -2788,21 +3423,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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableSpringConfigured public class AppConfig { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableSpringConfigured + class AppConfig { + } +---- If you prefer XML based configuration, the Spring <> defines a convenient `context:spring-configured` element, which you can use as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -2814,8 +3456,7 @@ domain objects when it is initialized by Spring. In this case, you can use the `depends-on` bean attribute to manually specify that the bean depends on the configuration aspect. The following example shows how to use the `depends-on` attribute: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- <1> @@ -2972,8 +3611,7 @@ declaration. Each `` element specifies a name pattern, and only beans names matched by at least one of the patterns are used for Spring AOP auto-proxy configuration. The following example shows how to use `` elements: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -3038,8 +3676,8 @@ use @AspectJ with <>. Specifically, you can use 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: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package foo; @@ -3069,14 +3707,45 @@ It is a time-based profiler that uses the @AspectJ-style of aspect declaration: public void methodsToBeProfiled(){} } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package foo + + import org.aspectj.lang.ProceedingJoinPoint + import org.aspectj.lang.annotation.Aspect + import org.aspectj.lang.annotation.Around + import org.aspectj.lang.annotation.Pointcut + import org.springframework.util.StopWatch + import org.springframework.core.annotation.Order + + @Aspect + class ProfilingAspect { + + @Around("methodsToBeProfiled()") + fun profile(pjp: ProceedingJoinPoint): Any { + val sw = StopWatch(javaClass.simpleName) + try { + sw.start(pjp.getSignature().getName()) + return pjp.proceed() + } finally { + sw.stop() + println(sw.prettyPrint()) + } + } + + @Pointcut("execution(public * foo..*.*(..))") + fun methodsToBeProfiled() { + } + } +---- 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 the presence of a file (or files) on the Java classpath called `META-INF/aop.xml` is standard AspectJ. The following example shows the `aop.xml` file: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -3102,8 +3771,7 @@ thing is that it does not require a lot of configuration (there are some more options that you can specify, but these are detailed later), as can be seen in the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ) { + ClassPathXmlApplicationContext("beans.xml") + + val entitlementCalculationService = StubEntitlementCalculationService() + + // the profiling aspect will be 'woven' around this method execution + entitlementCalculationService.calculateEntitlement() + } +---- Notice how, in the preceding program, we bootstrap the Spring container and then create a new instance of the `StubEntitlementCalculationService` totally outside @@ -3282,21 +3980,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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableLoadTimeWeaving public class AppConfig { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",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 `context` namespace. The following example shows how to use ``: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ` element. Again, the following example specifies a `ReflectiveLoadTimeWeaver`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- diff --git a/src/docs/asciidoc/core/core-appendix.adoc b/src/docs/asciidoc/core/core-appendix.adoc index 1c0e59e836..3a27c330e6 100644 --- a/src/docs/asciidoc/core/core-appendix.adoc +++ b/src/docs/asciidoc/core/core-appendix.adoc @@ -22,8 +22,7 @@ To use the tags in the `util` schema, you need to have the following preamble at of your Spring XML configuration file (the text in the snippet references the correct schema so that the tags in the `util` namespace are available to you): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -64,8 +62,7 @@ plumbing to the end user. The following XML Schema-based version is more concise, clearly expresses the developer's intent ("`inject this constant value`"), and it reads better: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -86,8 +83,7 @@ The following example shows how a `static` field is exposed, by using the {api-spring-framework}/beans/factory/config/FieldRetrievingFactoryBean.html#setStaticField(java.lang.String)[`staticField`] property: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -98,8 +94,7 @@ property: There is also a convenience usage form where the `static` field is specified as the bean name, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -110,8 +105,7 @@ bean that refers to it also has to use this longer name), but this form is very concise to define and very convenient to use as an inner bean since the `id` does not have to be specified for the bean reference, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -131,8 +125,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package javax.persistence; @@ -142,11 +136,22 @@ The following example enumeration shows how easy injecting an enum value is: EXTENDED } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package javax.persistence + + enum class PersistenceContextType { + + TRANSACTION, + EXTENDED + } +---- Now consider the following setter of type `PersistenceContextType` and the corresponding bean definition: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package example; @@ -159,9 +164,18 @@ Now consider the following setter of type `PersistenceContextType` and the corre } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package example -[source,xml,indent=0] -[subs="verbatim,quotes"] + class Client { + + lateinit var persistenceContextType: PersistenceContextType + } +---- + +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -174,8 +188,7 @@ Now consider the following setter of type `PersistenceContextType` and the corre Consider the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -197,8 +210,7 @@ has a value equal to the `age` property of the `testBean` bean. Now consider the following example, which adds a `` element: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -228,8 +240,7 @@ argument. The following example shows a path being used against another bean, by name: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- // target bean to be referenced by name @@ -251,8 +262,7 @@ The following example shows a path being used against another bean, by name: In the following example, a path is evaluated against an inner bean: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -302,8 +310,7 @@ this feature. Consider the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -317,8 +324,7 @@ loaded from the supplied <> location). The following example uses a `util:properties` element to make a more concise representation: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -330,8 +336,7 @@ The following example uses a `util:properties` element to make a more concise re Consider the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -352,8 +357,7 @@ from the supplied `sourceList`. The following example uses a `` element to make a more concise representation: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -369,8 +373,7 @@ populated by using the `list-class` attribute on the `` element. For example, if we really need a `java.util.LinkedList` to be instantiated, we could use the following configuration: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- jackshaftoe@vagabond.org @@ -388,8 +391,7 @@ If no `list-class` attribute is supplied, the container chooses a `List` impleme Consider the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -410,8 +412,7 @@ taken from the supplied `'sourceMap'`. The following example uses a `` element to make a more concise representation: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -427,8 +428,7 @@ populated by using the `'map-class'` attribute on the `` element. For example, if we really need a `java.util.TreeMap` to be instantiated, we could use the following configuration: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -446,8 +446,7 @@ If no `'map-class'` attribute is supplied, the container chooses a `Map` impleme Consider the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -468,8 +467,7 @@ from the supplied `sourceSet`. The following example uses a `` element to make a more concise representation: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -485,8 +483,7 @@ populated by using the `set-class` attribute on the `` element. For example, if we really need a `java.util.TreeSet` to be instantiated, we could use the following configuration: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- pechorin@hero.org @@ -513,8 +510,7 @@ the following preamble at the top of your Spring XML configuration file (the tex snippet references the correct schema so that the tags in the `aop` namespace are available to you): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ` element in the context of a surroundin (note that, without any logic to interpret it, the metadata is effectively useless as it stands). -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -745,8 +738,7 @@ The preceding schema lets us configure `SimpleDateFormat` objects directly in an XML application context file by using the `` element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -806,8 +797,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.samples.xml; @@ -818,7 +809,20 @@ use the `NamespaceHandlerSupport` class: public void init() { registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser()); } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.samples.xml + import org.springframework.beans.factory.xml.NamespaceHandlerSupport + + class MyNamespaceHandler : NamespaceHandlerSupport { + + override fun init() { + registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser()) + } } ---- @@ -844,7 +848,8 @@ 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.samples.xml; @@ -855,16 +860,20 @@ we can parse our custom XML content, as you can see in the following example: import java.text.SimpleDateFormat; - public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { // <1> + // We use the Spring-provided AbstractSingleBeanDefinitionParser to handle a lot of + // the basic grunt work of creating a single BeanDefinition. + public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { protected Class getBeanClass(Element element) { - return SimpleDateFormat.class; // <2> + // We supply the AbstractSingleBeanDefinitionParser superclass with the type that our + // single BeanDefinition represents. + return SimpleDateFormat.class; } protected void doParse(Element element, BeanDefinitionBuilder bean) { // this will never be null since the schema explicitly requires that a value be supplied String pattern = element.getAttribute("pattern"); - bean.addConstructorArg(pattern); + bean.addConstructorArgValue(pattern); // this however is an optional property String lenient = element.getAttribute("lenient"); @@ -875,12 +884,41 @@ we can parse our custom XML content, as you can see in the following example: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.samples.xml -<1> We use the Spring-provided `AbstractSingleBeanDefinitionParser` to handle a lot of -the basic grunt work of creating a single `BeanDefinition`. + import org.springframework.beans.factory.support.BeanDefinitionBuilder + import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser + import org.springframework.util.StringUtils + import org.w3c.dom.Element -<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our -single `BeanDefinition` represents. + import java.text.SimpleDateFormat + + // We use the Spring-provided AbstractSingleBeanDefinitionParser to handle a lot of + // the basic grunt work of creating a single BeanDefinition. + class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { + + override fun getBeanClass(element: Element): Class<*>? { + // We supply the AbstractSingleBeanDefinitionParser superclass with the type that our + // single BeanDefinition represents. + return SimpleDateFormat::class.java + } + + override fun doParse(element: Element, bean: BeanDefinitionBuilder) { + // this will never be null since the schema explicitly requires that a value be supplied + val pattern = element.getAttribute("pattern") + bean.addConstructorArgValue(pattern) + + // this however is an optional property + val lenient = element.getAttribute("lenient") + if (StringUtils.hasText(lenient)) { + bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient)) + } + } + } +---- In this simple case, this is all that we need to do. The creation of our single `BeanDefinition` is handled by the `AbstractSingleBeanDefinitionParser` superclass, as @@ -906,8 +944,7 @@ these special properties files, the formats of which are detailed in the next tw The properties file called `spring.handlers` contains a mapping of XML Schema URIs to namespace handler classes. For our example, we need to write the following: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler ---- @@ -932,8 +969,7 @@ properties file, Spring searches for the schema (in this case, `myns.xsd` in the `org.springframework.samples.xml` package) on the classpath. The following snippet shows the line we need to add for our custom schema: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd ---- @@ -953,8 +989,7 @@ one of the "`custom`" extensions that Spring provides. The following example uses the custom `` element developed in the previous steps in a Spring XML configuration file: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- () + + // mmm, there is no setter method for the 'components' + fun addComponent(component: Component) { + this.components.add(component) + } + + fun getComponents(): List { + return components + } + } +---- 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.foo; @@ -1097,6 +1153,45 @@ setter property for the `components` property. The following listing shows such } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.foo + + import org.springframework.beans.factory.FactoryBean + import org.springframework.stereotype.Component + + class ComponentFactoryBean : FactoryBean { + + private var parent: Component? = null + private var children: List? = null + + fun setParent(parent: Component) { + this.parent = parent + } + + fun setChildren(children: List) { + this.children = children + } + + override fun getObject(): Component? { + if (this.children != null && this.children!!.isNotEmpty()) { + for (child in children!!) { + this.parent!!.addComponent(child) + } + } + return this.parent + } + + override fun getObjectType(): Class? { + return Component::class.java + } + + override fun isSingleton(): Boolean { + return true + } + } +---- 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. @@ -1104,8 +1199,7 @@ If we stick to <>, we s by creating the XSD schema to define the structure of our custom tag, as the following listing shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1131,8 +1225,8 @@ listing shows: Again following <>, we then create a custom `NamespaceHandler`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.foo; @@ -1145,13 +1239,27 @@ we then create a custom `NamespaceHandler`: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.foo + + import org.springframework.beans.factory.xml.NamespaceHandlerSupport + + class ComponentNamespaceHandler : NamespaceHandlerSupport() { + + override fun init() { + registerBeanDefinitionParser("component", ComponentBeanDefinitionParser()) + } + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.foo; @@ -1199,19 +1307,66 @@ listing shows our custom `BeanDefinitionParser` implementation: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.foo + + import org.springframework.beans.factory.config.BeanDefinition + import org.springframework.beans.factory.support.AbstractBeanDefinition + import org.springframework.beans.factory.support.BeanDefinitionBuilder + import org.springframework.beans.factory.support.ManagedList + import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser + import org.springframework.beans.factory.xml.ParserContext + import org.springframework.util.xml.DomUtils + import org.w3c.dom.Element + + import java.util.List + + class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() { + + override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? { + return parseComponentElement(element) + } + + private fun parseComponentElement(element: Element): AbstractBeanDefinition { + val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java) + factory.addPropertyValue("parent", parseComponent(element)) + + val childElements = DomUtils.getChildElementsByTagName(element, "component") + if (childElements != null && childElements.size > 0) { + parseChildComponents(childElements, factory) + } + + return factory.getBeanDefinition() + } + + private fun parseComponent(element: Element): BeanDefinition { + val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java) + component.addPropertyValue("name", element.getAttribute("name")) + return component.beanDefinition + } + + private fun parseChildComponents(childElements: List, factory: BeanDefinitionBuilder) { + val children = ManagedList(childElements.size) + for (element in childElements) { + children.add(parseComponentElement(element)) + } + factory.addPropertyValue("children", children) + } + } +---- 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: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- # in 'META-INF/spring.handlers' http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler ---- -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- # in 'META-INF/spring.schemas' http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd @@ -1233,8 +1388,7 @@ https://jcp.org/en/jsr/detail?id=107[JCache], and you want to ensure that the named JCache instance is eagerly started within the surrounding cluster. The following listing shows such a definition: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1248,8 +1402,8 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.foo; @@ -1266,12 +1420,23 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.foo + + class JCacheInitializer(private val name: String) { + + fun initialize() { + // lots of JCache API calls to initialize the named cache... + } + } +---- Now we can move onto the custom extension. First, we need to author the XSD schema that describes the custom attribute, as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1287,8 +1452,8 @@ the XSD schema that describes the custom attribute, as follows: Next, we need to create the associated `NamespaceHandler`, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.foo; @@ -1303,13 +1468,29 @@ Next, we need to create the associated `NamespaceHandler`, as follows: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.foo + + import org.springframework.beans.factory.xml.NamespaceHandlerSupport + + class JCacheNamespaceHandler : NamespaceHandlerSupport() { + + override fun init() { + super.registerBeanDefinitionDecoratorForAttribute("cache-name", + JCacheInitializingBeanDefinitionDecorator()) + } + + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.foo; @@ -1362,19 +1543,67 @@ The following listing shows our `BeanDefinitionDecorator` implementation: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.foo + + import org.springframework.beans.factory.config.BeanDefinitionHolder + import org.springframework.beans.factory.support.AbstractBeanDefinition + import org.springframework.beans.factory.support.BeanDefinitionBuilder + import org.springframework.beans.factory.xml.BeanDefinitionDecorator + import org.springframework.beans.factory.xml.ParserContext + import org.w3c.dom.Attr + import org.w3c.dom.Node + + import java.util.ArrayList + + class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator { + + override fun decorate(source: Node, holder: BeanDefinitionHolder, + ctx: ParserContext): BeanDefinitionHolder { + val initializerBeanName = registerJCacheInitializer(source, ctx) + createDependencyOnJCacheInitializer(holder, initializerBeanName) + return holder + } + + private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder, + initializerBeanName: String) { + val definition = holder.beanDefinition as AbstractBeanDefinition + var dependsOn = definition.dependsOn + dependsOn = if (dependsOn == null) { + arrayOf(initializerBeanName) + } else { + val dependencies = ArrayList(listOf(*dependsOn)) + dependencies.add(initializerBeanName) + dependencies.toTypedArray() + } + definition.setDependsOn(*dependsOn) + } + + private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String { + val cacheName = (source as Attr).value + val beanName = "$cacheName-initializer" + if (!ctx.registry.containsBeanDefinition(beanName)) { + val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java) + initializer.addConstructorArg(cacheName) + ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition()) + } + return beanName + } + } +---- 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: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- # in 'META-INF/spring.handlers' http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler ---- -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- # in 'META-INF/spring.schemas' http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd diff --git a/src/docs/asciidoc/core/core-beans.adoc b/src/docs/asciidoc/core/core-beans.adoc index 37208b760c..1de0952542 100644 --- a/src/docs/asciidoc/core/core-beans.adoc +++ b/src/docs/asciidoc/core/core-beans.adoc @@ -176,11 +176,16 @@ 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. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); ---- +.Kotlin +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + val context = ClassPathXmlApplicationContext("services.xml", "daos.xml") +---- [NOTE] ==== @@ -192,8 +197,7 @@ mechanism for reading an InputStream from locations defined in a URI syntax. In The following example shows the service layer objects `(services.xml)` configuration file: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ` element to load bean definitions from another file or files. The following example shows how to do so: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -313,8 +315,7 @@ be expressed in Spring's Groovy Bean Definition DSL, as known from the Grails fr Typically, such configuration live in a ".groovy" file with the structure shown in the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,groovy,indent=0,subs="verbatim,quotes"] ---- beans { dataSource(BasicDataSource) { @@ -351,8 +352,8 @@ name, Class requiredType)`, you can retrieve instances of your beans. The `ApplicationContext` lets you read bean definitions and access them, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); @@ -363,39 +364,70 @@ example shows: // use configured instance List userList = service.getUsernameList(); ---- +.Kotlin +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + // create and configure beans + val context = ClassPathXmlApplicationContext("services.xml", "daos.xml") + + // retrieve configured instance + val service = context.getBean("petStore", PetStoreService::class.java) + + // 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy"); ---- +.Kotlin +[source,java,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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- GenericApplicationContext context = new GenericApplicationContext(); new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml"); context.refresh(); ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- GenericApplicationContext context = new GenericApplicationContext(); new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy"); context.refresh(); ---- +[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. @@ -550,8 +582,7 @@ amongst each subsystem, with each subsystem having its own set of object definit In XML-based configuration metadata, you can use the `` element to accomplish this. The following example shows how to do so: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -566,8 +597,7 @@ that uses both these subsystems, the main application refers to the DataSource b name of `myApp-dataSource`. To have all three names refer to the same object, you can add the following alias definitions to the configuration metadata: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -641,8 +671,7 @@ well. With XML-based configuration metadata you can specify your bean class as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -669,8 +698,7 @@ factory method. The definition does not specify the type (class) of the returned only the class containing the factory method. In this example, the `createInstance()` method must be a static method. The following example shows how to specify a factory method: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -723,10 +760,10 @@ how to configure such a bean: factory-method="createClientServiceInstance"/> ---- -The following example shows the corresponding Java class: +The following example shows the corresponding class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class DefaultServiceLocator { @@ -737,11 +774,22 @@ The following example shows the corresponding Java class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class DefaultServiceLocator { + companion object { + private val clientService = ClientServiceImpl() + } + fun createClientServiceInstance(): ClientService { + return clientService + } + } +---- One factory class can also hold more than one factory method, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -756,10 +804,10 @@ One factory class can also hold more than one factory method, as the following e factory-method="createAccountServiceInstance"/> ---- -The following example shows the corresponding Java class: +The following example shows the corresponding class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class DefaultServiceLocator { @@ -776,6 +824,24 @@ The following example shows the corresponding Java class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class DefaultServiceLocator { + companion object { + private val clientService = ClientServiceImpl() + private val accountService = AccountServiceImpl() + } + + fun createClientServiceInstance(): ClientService { + return clientService + } + + fun createAccountServiceInstance(): AccountService { + return accountService + } + } +---- This approach shows that the factory bean itself can be managed and configured through dependency injection (DI). See <` element. -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -901,8 +981,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package examples; @@ -920,6 +1000,16 @@ by type without help. Consider the following class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package examples + + class ExampleBean( + private val years: Int, // Number of years to calculate the Ultimate Answer + private val ultimateAnswer: String// The Answer to Life, the Universe, and Everything + ) +---- .[[beans-factory-ctor-arguments-type]]Constructor argument type matching -- @@ -927,8 +1017,7 @@ In the preceding scenario, the container can use type matching with simple types you explicitly specify the type of the constructor argument by using the `type` attribute. as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -942,8 +1031,7 @@ as the following example shows: You can use the `index` attribute to specify explicitly the index of constructor arguments, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -962,8 +1050,7 @@ NOTE: The index is 0-based. You can also use the constructor parameter name for value disambiguation, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -978,8 +1065,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package examples; @@ -994,6 +1081,15 @@ then have to look as follows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package examples + + class ExampleBean + @ConstructorProperties("years", "ultimateAnswer") + constructor(val years: Int, val ultimateAnswer: String) +---- -- @@ -1008,8 +1104,8 @@ 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. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { @@ -1024,6 +1120,18 @@ on container specific interfaces, base classes, or annotations. // business logic that actually uses the injected MovieFinder is omitted... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +class SimpleMovieLister { + + // a late-initialized property so that the Spring container can inject a MovieFinder + lateinit var movieFinder: MovieFinder + + // business logic that actually uses the injected MovieFinder is omitted... +} +---- + The `ApplicationContext` supports constructor-based and setter-based DI for the beans it manages. It also supports setter-based DI after some dependencies have already been @@ -1145,8 +1253,7 @@ are invoked. The following example uses XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifies some bean definitions as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1165,8 +1272,8 @@ part of a Spring XML configuration file specifies some bean definitions as follo The following example shows the corresponding `ExampleBean` class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class ExampleBean { @@ -1189,12 +1296,20 @@ The following example shows the corresponding `ExampleBean` class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +class ExampleBean { + lateinit var beanOne: AnotherBean + lateinit var beanTwo: YetAnotherBean + 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: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1214,8 +1329,8 @@ in the XML file. The following example uses constructor-based DI: The following example shows the corresponding `ExampleBean` class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class ExampleBean { @@ -1233,6 +1348,14 @@ The following example shows the corresponding `ExampleBean` class: } } ---- +[source,java,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`. @@ -1240,8 +1363,7 @@ the constructor of the `ExampleBean`. Now consider a variant of this example, where, instead of using a constructor, Spring is told to call a `static` factory method to return an instance of the object: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1255,8 +1377,8 @@ told to call a `static` factory method to return an instance of the object: The following example shows the corresponding `ExampleBean` class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class ExampleBean { @@ -1277,6 +1399,22 @@ The following example shows the corresponding `ExampleBean` class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class ExampleBean private constructor() { + companion object { + // a static factory method; the arguments to this method can be + // considered the dependencies of the bean that is returned, + // regardless of how those arguments are actually used. + fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean { + val eb = ExampleBean (...) + // some other operations... + return eb + } + } + } +---- 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 @@ -1307,8 +1445,7 @@ argument as a human-readable string representation. Spring's values from a `String` to the actual type of the property or argument. The following example shows various values being set: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1322,8 +1459,7 @@ The following example shows various values being set: The following example uses the <> for even more succinct XML configuration: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1377,8 +1512,7 @@ The `idref` element is simply an error-proof way to pass the `id` (a string valu a reference) of another bean in the container to a `` or `` element. The following example shows how to use it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1392,8 +1526,7 @@ element. The following example shows how to use it: The preceding bean definition snippet is exactly equivalent (at runtime) to the following snippet: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1439,8 +1572,7 @@ parent container, regardless of whether it is in the same XML file. The value of as one of the values in the `name` attribute of the target bean. The following example shows how to use a `ref` element: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -1454,8 +1586,7 @@ when you have a hierarchy of containers and you want to wrap an existing bean in container with a proxy that has the same name as the parent bean. The following pair of listings shows how to use the `parent` attribute: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1463,8 +1594,7 @@ listings shows how to use the `parent` attribute: ---- -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1487,8 +1617,7 @@ your existing `ref local` references to `ref bean` when upgrading to the 4.0 sch A `` element inside the `` or `` elements defines an inner bean, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1521,8 +1650,7 @@ The ``, ``, ``, and `` elements set the properties and arguments of the Java `Collection` types `List`, `Set`, `Map`, and `Properties`, respectively. The following example shows how to use them: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1560,8 +1688,7 @@ respectively. The following example shows how to use them: The value of a map key or value, or a set value, can also be any of the following elements: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- bean | ref | idref | list | set | map | props | value | null ---- @@ -1582,8 +1709,7 @@ with parent and child bean definitions may wish to read the The following example demonstrates collection merging: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1613,8 +1739,7 @@ and instantiated by the container, the resulting instance has an `adminEmails` `adminEmails` collection with the parent's `adminEmails` collection. The following listing shows the result: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- administrator=administrator@example.com sales=sales@example.com @@ -1653,8 +1778,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SomeClass { @@ -1665,9 +1790,15 @@ The following Java class and bean definition show how to do so: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +class SomeClass { + lateinit var accounts: Map +} +---- -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1696,8 +1827,7 @@ Spring treats empty arguments for properties and the like as empty `Strings`. Th following XML-based configuration metadata snippet sets the `email` property to the empty `String` value (""). -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1706,16 +1836,21 @@ following XML-based configuration metadata snippet sets the `email` property to The preceding example is equivalent to the following Java code: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- exampleBean.setEmail(""); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + exampleBean.email = "" +---- + The `` element handles `null` values. The following listing shows an example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1726,11 +1861,16 @@ The `` element handles `null` values. The following listing shows an exam The preceding configuration is equivalent to the following Java code: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- exampleBean.setEmail(null); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + exampleBean.email = null +---- [[beans-p-namespace]] @@ -1747,8 +1887,7 @@ in an XSD file and exists only in the core of Spring. The following example shows two XML snippets (the first uses standard XML format and the second uses the p-namespace) that resolve to the same result: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1911,8 +2047,7 @@ explicitly force one or more beans to be initialized before the bean using this is initialized. The following example uses the `depends-on` attribute to express a dependency on a single bean: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1922,8 +2057,7 @@ To express a dependency on multiple beans, supply a list of bean names as the va the `depends-on` attribute (commas, whitespace, and semicolons are valid delimiters): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1956,8 +2090,7 @@ instance when it is first requested, rather than at startup. In XML, this behavior is controlled by the `lazy-init` attribute on the `` element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1975,8 +2108,7 @@ is injected into a singleton bean elsewhere that is not lazy-initialized. You can also control lazy-initialization at the container level by using the `default-lazy-init` attribute on the `` element, a the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2131,8 +2263,8 @@ and by <> as typically new) bean B instance every time bean A needs it. The following example shows this approach: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // a class that uses a stateful Command-style class to perform some processing package fiona.apple; @@ -2165,6 +2297,37 @@ shows this approach: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // a class that uses a stateful Command-style class to perform some processing + package fiona.apple + + // Spring-API imports + import org.springframework.context.ApplicationContext + import org.springframework.context.ApplicationContextAware + + class CommandManager : ApplicationContextAware { + + private lateinit var applicationContext: ApplicationContext + + fun process(commandState: Map<*, *>): Any { + // grab a new instance of the appropriate Command + val command = createCommand() + // set the state on the (hopefully brand new) Command instance + command.state = commandState + return command.execute() + } + + // notice the Spring API dependency! + protected fun createCommand() = + applicationContext.getBean("command", Command::class.java) + + override fun setApplicationContext(applicationContext: ApplicationContext) { + this.applicationContext = applicationContext + } + } +---- 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 @@ -2206,8 +2369,8 @@ Spring container dynamically overrides the implementation of the `createCommand( method. The `CommandManager` class does not have any Spring dependencies, as the reworked example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package fiona.apple; @@ -2227,12 +2390,32 @@ the reworked example shows: protected abstract Command createCommand(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package fiona.apple + + // no more Spring imports! + + abstract class CommandManager { + + fun process(commandState: Any): Any { + // grab a new instance of the appropriate Command interface + val command = createCommand() + // set the state on the (hopefully brand new) Command instance + command.state = commandState + return command.execute() + } + + // okay... but where is the implementation of this method? + 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: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- [abstract] theMethodName(no-arguments); ---- @@ -2241,8 +2424,7 @@ If the method is `abstract`, the dynamically-generated subclass implements the m Otherwise, the dynamically-generated subclass overrides the concrete method defined in the original class. Consider the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2264,8 +2446,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public abstract class CommandManager { @@ -2279,12 +2461,27 @@ method through the `@Lookup` annotation, as the following example shows: protected abstract Command createCommand(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + abstract class CommandManager { + + fun process(commandState: Any): Any { + val command = createCommand() + command.state = commandState + return command.execute() + } + + @Lookup("myCommand") + 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public abstract class CommandManager { @@ -2298,6 +2495,21 @@ declared return type of the lookup method: protected abstract MyCommand createCommand(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + abstract class CommandManager { + + fun process(commandState: Any): Any { + val command = createCommand() + command.state = commandState + return command.execute() + } + + @Lookup + 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 @@ -2326,8 +2538,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyValueCalculator { @@ -2338,12 +2550,24 @@ the following class, which has a method called `computeValue` that we want to ov // some other methods... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyValueCalculator { + + fun computeValue(input: String): String { + // some real code... + } + + // 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- /** * meant to be used to override the existing computeValue(String) @@ -2359,12 +2583,30 @@ interface provides the new method definition, as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + /** + * meant to be used to override the existing computeValue(String) + * implementation in MyValueCalculator + */ + class ReplacementComputeValue : MethodReplacer { + + override fun reimplement(obj: Any, method: Method, args: Array): Any { + // get the input value, work with it, and return a computed result + val input = args[0] as String; + ... + return ...; + } + } +---- + + The bean definition to deploy the original class and specify the method override would resemble the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2383,8 +2625,7 @@ exist within the class. For convenience, the type string for an argument may be substring of the fully qualified type name. For example, the following all match `java.lang.String`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes"] ---- java.lang.String String @@ -2481,8 +2722,7 @@ of the class defined by that bean definition. The singleton scope is the default in Spring. To define a bean as a singleton in XML, you can define a bean as shown in the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2512,8 +2752,7 @@ singleton diagram.) The following example defines a bean as a prototype in XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -2586,8 +2825,7 @@ For Servlet 3.0+, this can be done programmatically by using the `WebApplication interface. Alternatively, or for older containers, add the following declaration to your web application's `web.xml` file: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ... @@ -2605,8 +2843,7 @@ Alternatively, if there are issues with your listener setup, consider using Spri application configuration, so you have to change it as appropriate. The following listing shows the filter part of a web application: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ... @@ -2634,8 +2871,7 @@ down the call chain. Consider the following XML configuration for a bean definition: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -2652,8 +2888,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RequestScope @Component @@ -2661,6 +2897,15 @@ to do so: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RequestScope + @Component + class LoginAction { + // ... + } +---- @@ -2669,8 +2914,7 @@ to do so: Consider the following XML configuration for a bean definition: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -2688,8 +2932,8 @@ 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. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SessionScope @Component @@ -2697,7 +2941,15 @@ When using annotation-driven components or Java configuration, you can use the // ... } ---- - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SessionScope + @Component + class UserPreferences { + // ... + } +---- [[beans-factory-scopes-application]] @@ -2705,8 +2957,7 @@ When using annotation-driven components or Java configuration, you can use the Consider the following XML configuration for a bean definition: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -2723,8 +2974,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ApplicationScope @Component @@ -2732,6 +2983,15 @@ following example shows how to do so: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ApplicationScope + @Component + class AppPreferences { + // ... + } +---- @@ -2773,8 +3033,7 @@ See <> for more details on JSR-330 overall. The configuration in the following example is only one line, but it is important to understand the "`why`" as well as the "`how`" behind it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2846,8 +3104,7 @@ Thus, you need the following (correct and complete) configuration when injecting `request-` and `session-scoped` beans into collaborating objects, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2879,8 +3136,7 @@ the scoped bean must implement at least one interface and that all collaborators into which the scoped bean is injected must reference the bean through one of its interfaces. The following example shows a proxy based on an interface: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -2923,10 +3179,15 @@ implementation, for example, returns the session-scoped bean (if it does not exi the method returns a new instance of the bean, after having bound it to the session for future reference). The following method returns the object from the underlying scope: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - Object get(String name, ObjectFactory objectFactory) + Object get(String name, ObjectFactory objectFactory) +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun get(name: String, objectFactory: ObjectFactory<*>): Any ---- The session scope @@ -2934,31 +3195,47 @@ implementation, for example, removes the session-scoped bean from the underlying 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Object remove(String name) ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun remove(name: String): Any +---- The following method registers the callbacks the scope should execute when it is destroyed or when the specified object in the scope is destroyed: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- void registerDestructionCallback(String name, Runnable destructionCallback) ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- String getConversationId() ---- +[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. @@ -2972,11 +3249,16 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- void registerScope(String scopeName, Scope scope); ---- +[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` @@ -2994,18 +3276,23 @@ 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. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope); ---- +[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: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -3014,8 +3301,7 @@ With a custom `Scope` implementation, you are not limited to programmatic regist of the scope. You can also do the `Scope` registration declaratively, by using the `CustomScopeConfigurer` class, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class ExampleBean { @@ -3135,26 +3426,46 @@ no-argument signature. With Java configuration, you can use the `initMethod` att } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class ExampleBean { + + fun init() { + // do some initialization work + } + } +---- The preceding example has almost exactly the same effect as the following example (which consists of two listings): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class AnotherExampleBean implements InitializingBean { + @Override public void afterPropertiesSet() { // do some initialization work } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class AnotherExampleBean : InitializingBean { + + override fun afterPropertiesSet() { + // do some initialization work + } + } +---- However, the first of the two preceding examples does not couple the code to Spring. @@ -3166,11 +3477,16 @@ Implementing the `org.springframework.beans.factory.DisposableBean` interface le bean get a callback when the container that contains it is destroyed. The `DisposableBean` interface specifies a single method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- void destroy() throws Exception; ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun destroy() +---- We recommend that you do not use the `DisposableBean` callback interface, because it unnecessarily couples the code to Spring. Alternatively, we suggest using @@ -3180,14 +3496,13 @@ configuration metadata, you can use the `destroy-method` attribute on the `>. Consider the following definition: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class ExampleBean { @@ -3196,25 +3511,45 @@ With Java configuration, you can use the `destroyMethod` attribute of `@Bean`. S } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class ExampleBean { + + fun cleanup() { + // do some destruction work (like releasing pooled connections) + } + } +---- The preceding definition has almost exactly the same effect as the following definition: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class AnotherExampleBean implements DisposableBean { + @Override public void destroy() { // do some destruction work (like releasing pooled connections) } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class AnotherExampleBean : DisposableBean { + + override fun destroy() { + // do some destruction work (like releasing pooled connections) + } + } +---- However, the first of the two preceding definitions does not couple the code to Spring. @@ -3249,8 +3584,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class DefaultBlogService implements BlogService { @@ -3268,11 +3603,25 @@ following example: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class DefaultBlogService : BlogService { + + private var blogDao: BlogDao? = null + + // this is (unsurprisingly) the initialization callback method + fun init() { + if (blogDao == null) { + throw IllegalStateException("The [blogDao] property must be set.") + } + } + } +---- You could then use that class in a bean resembling the following: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -3348,8 +3697,8 @@ Destroy methods are called in the same order: The `Lifecycle` interface defines the essential methods for any object that has its own lifecycle requirements (such as starting and stopping some background process): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface Lifecycle { @@ -3360,6 +3709,18 @@ lifecycle requirements (such as starting and stopping some background process): boolean isRunning(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface Lifecycle { + + fun start() + + fun stop() + + val isRunning: Boolean + } +---- Any Spring-managed object may implement the `Lifecycle` interface. Then, when the `ApplicationContext` itself receives start and stop signals (for example, for a stop/restart @@ -3367,8 +3728,8 @@ scenario at runtime), it cascades those calls to all `Lifecycle` implementations defined within that context. It does this by delegating to a `LifecycleProcessor`, shown in the following listing: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface LifecycleProcessor extends Lifecycle { @@ -3377,6 +3738,16 @@ in the following listing: void onClose(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface LifecycleProcessor : Lifecycle { + + fun onRefresh() + + fun onClose() + } +---- Notice that the `LifecycleProcessor` is itself an extension of the `Lifecycle` interface. It also adds two other methods for reacting to the context being refreshed @@ -3403,19 +3774,27 @@ prior to objects of another type. In those cases, the `SmartLifecycle` interface another option, namely the `getPhase()` method as defined on its super-interface, `Phased`. The following listing shows the definition of the `Phased` interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface Phased { int getPhase(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface Phased { + + val phase: Int + } +---- The following listing shows the definition of the `SmartLifecycle` interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface SmartLifecycle extends Lifecycle, Phased { @@ -3424,6 +3803,16 @@ The following listing shows the definition of the `SmartLifecycle` interface: void stop(Runnable callback); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface SmartLifecycle : Lifecycle, Phased { + + val isAutoStartup: Boolean + + fun stop(callback: Runnable) + } +---- When starting, the objects with the lowest phase start first. When stopping, the reverse order is followed. Therefore, an object that implements `SmartLifecycle` and @@ -3446,8 +3835,7 @@ You can override the default lifecycle processor instance by defining a bean nam `lifecycleProcessor` within the context. If you want only to modify the timeout, defining the following would suffice: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -3489,8 +3877,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -3509,6 +3897,22 @@ declared on the `ConfigurableApplicationContext` interface, as the following exa } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.context.support.ClassPathXmlApplicationContext + + fun main() { + val ctx = ClassPathXmlApplicationContext("beans.xml") + + // add a shutdown hook for the above context... + ctx.registerShutdownHook() + + // app runs here... + + // main method exits, hook is called prior to the app shutting down... + } +---- @@ -3520,14 +3924,23 @@ When an `ApplicationContext` creates an object instance that implements the with a reference to that `ApplicationContext`. The following listing shows the definition of the `ApplicationContextAware` interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface ApplicationContextAware { + + @Throws(BeansException::class) + fun setApplicationContext(applicationContext: ApplicationContext) + } +---- Thus, beans can programmatically manipulate the `ApplicationContext` that created them, through the `ApplicationContext` interface or by casting the reference to a known @@ -3556,14 +3969,23 @@ When an `ApplicationContext` creates a class that implements the a reference to the name defined in its associated object definition. The following listing shows the definition of the BeanNameAware interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface BeanNameAware { void setBeanName(String name) throws BeansException; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface BeanNameAware { + + @Throws(BeansException::class) + fun setBeanName(name: String) + } +---- The callback is invoked after population of normal bean properties but before an initialization callback such as `InitializingBean`, `afterPropertiesSet`, or a custom @@ -3661,8 +4083,7 @@ metadata, you can indicate a child bean definition by using the `parent` attribu specifying the parent bean as the value of this attribute. The following example shows how to do so: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -3696,8 +4117,7 @@ the `abstract` attribute. If the parent definition does not specify a class, exp marking the parent bean definition as `abstract` is required, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -3851,8 +4271,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package scripting; @@ -3871,11 +4291,28 @@ The following listing shows the custom `BeanPostProcessor` implementation class } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.beans.factory.config.BeanPostProcessor + + class InstantiationTracingBeanPostProcessor : BeanPostProcessor { + + // simply return the instantiated bean as-is + override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? { + return bean // we could potentially return any object reference here... + } + + override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? { + println("Bean '$beanName' created : $bean") + return bean + } + } +---- The following `beans` element uses the `InstantiationTracingBeanPostProcessor`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4038,8 +4482,7 @@ form `${property-name}`, which follows the Ant and log4j and JSP EL style. The actual values come from another file in the standard Java `Properties` format: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 @@ -4056,8 +4499,7 @@ With the `context` namespace introduced in Spring 2.5, you can configure propert with a dedicated configuration element. You can provide one or more locations as a comma-separated list in the `location` attribute, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -4072,8 +4514,7 @@ You can use the `PropertySourcesPlaceholderConfigurer` to substitute class names is sometimes useful when you have to pick a particular implementation class at runtime. The following example shows how to do so: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4109,16 +4550,14 @@ values for the same bean property, the last one wins, due to the overriding mech Properties file configuration lines take the following format: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- beanName.property=value ---- The following listing shows an example of the format: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb @@ -4132,8 +4571,7 @@ except the final property being overridden is already non-null (presumably initi by the constructors). In the following example, the `sammy` property of the `bob` property of the `fred` property of the `tom` bean is set to the scalar value `123`: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- tom.fred.bob.sammy=123 ---- @@ -4146,8 +4584,7 @@ definition specifies a bean reference. With the `context` namespace introduced in Spring 2.5, it is possible to configure property overriding with a dedicated configuration element, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -4238,8 +4675,7 @@ As always, you can register them as individual bean definitions, but they can al implicitly registered by including the following tag in an XML-based Spring configuration (notice the inclusion of the `context` namespace): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- > for m You can apply the `@Autowired` annotation to constructors, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4338,6 +4786,12 @@ You can apply the `@Autowired` annotation to constructors, as the following exam // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender @Autowired constructor( + private val customerPreferenceDao: CustomerPreferenceDao) +---- [NOTE] ==== @@ -4350,8 +4804,8 @@ with `@Autowired` in order to instruct the container which one to use. You can also apply the `@Autowired` annotation to _traditional_ setter methods, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { @@ -4365,12 +4819,24 @@ as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimpleMovieLister { + + @Autowired + lateinit var movieFinder: MovieFinder + + // ... + + } +---- You can also apply the annotation to methods with arbitrary names and multiple arguments, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4388,12 +4854,31 @@ arguments, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender { + + private lateinit var movieCatalog: MovieCatalog + + private lateinit var customerPreferenceDao: CustomerPreferenceDao + + @Autowired + fun prepare(movieCatalog: MovieCatalog, + customerPreferenceDao: CustomerPreferenceDao) { + this.movieCatalog = movieCatalog + this.customerPreferenceDao = customerPreferenceDao + } + + // ... + } +---- You can apply `@Autowired` to fields as well and even mix it with constructors, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4410,6 +4895,18 @@ following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender @Autowired constructor( + private val customerPreferenceDao: CustomerPreferenceDao) { + + @Autowired + private lateinit var movieCatalog: MovieCatalog + + // ... + } +---- [TIP] ==== @@ -4429,8 +4926,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4440,11 +4937,22 @@ expects an array of that type, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender { + + @Autowired + private lateinit var movieCatalogs: Array + + // ... + } +---- The same applies for typed collections, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4458,6 +4966,17 @@ The same applies for typed collections, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender { + + @Autowired + lateinit var movieCatalogs: Set + + // ... + } +---- [TIP] ==== @@ -4481,8 +5000,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4496,6 +5015,17 @@ corresponding bean names, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender { + + @Autowired + lateinit var movieCatalogs: Map + + // ... + } +---- 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 @@ -4506,8 +5036,8 @@ 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`): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { @@ -4521,6 +5051,17 @@ non-required (i.e., by setting the `required` attribute in `@Autowired` to `fals // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimpleMovieLister { + + @Autowired(required = false) + var movieFinder: MovieFinder? = null + + // ... + } +---- A non-required method will not be called at all if its dependency (or one of its dependencies, in case of multiple arguments) is not available. A non-required field will @@ -4560,8 +5101,7 @@ corresponding exception is raised. Alternatively, you can express the non-required nature of a particular dependency through Java 8's `java.util.Optional`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes"] ---- public class SimpleMovieLister { @@ -4573,10 +5113,11 @@ through Java 8's `java.util.Optional`, as the following example shows: ---- As of Spring Framework 5.0, you can also use a `@Nullable` annotation (of any kind -in any package -- for example, `javax.annotation.Nullable` from JSR-305): +in any package -- for example, `javax.annotation.Nullable` from JSR-305) or just leverage +Kotlin builtin null-safety support: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { @@ -4586,6 +5127,17 @@ in any package -- for example, `javax.annotation.Nullable` from JSR-305): } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimpleMovieLister { + + @Autowired + var movieFinder: MovieFinder? = null + + // ... + } +---- You can also use `@Autowired` for interfaces that are well-known resolvable dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`, @@ -4594,8 +5146,8 @@ interfaces, such as `ConfigurableApplicationContext` or `ResourcePatternResolver automatically resolved, with no special setup necessary. The following example autowires an `ApplicationContext` object: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4608,6 +5160,17 @@ an `ApplicationContext` object: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +class MovieRecommender { + + @Autowired + lateinit var context: ApplicationContext + + // ... +} +---- [NOTE] ==== @@ -4632,8 +5195,8 @@ autowired value. Consider the following configuration that defines `firstMovieCatalog` as the primary `MovieCatalog`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration public class MovieConfiguration { @@ -4648,12 +5211,28 @@ primary `MovieCatalog`: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class MovieConfiguration { + + @Bean + @Primary + fun firstMovieCatalog(): MovieCatalog { ... } + + @Bean + fun secondMovieCatalog(): MovieCatalog { ... } + + // ... + } +---- With the preceding configuration, the following `MovieRecommender` is autowired with the `firstMovieCatalog`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -4663,11 +5242,21 @@ With the preceding configuration, the following `MovieRecommender` is autowired // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +class MovieRecommender { + + @Autowired + private lateinit var movieCatalog: MovieCatalog + + // ... +} +---- The corresponding bean definitions follow: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ` tags as sub-elements of the `` tag and then specify the `type` and @@ -4884,8 +5530,7 @@ fully-qualified class name of the annotation. Alternately, as a convenience if n conflicting names exists, you can use the short class name. The following example demonstrates both approaches: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- + @Offline private MovieCatalog offlineCatalog; // ... } ---- -<1> This line adds the `@Offline` annotation. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +class MovieRecommender { + @Autowired + @Offline + private lateinit var offlineCatalog: MovieCatalog + + // ... +} +---- Now the bean definition only needs a qualifier `type`, as shown in the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- <1> @@ -4970,8 +5632,8 @@ then specified on a field or parameter to be autowired, a bean definition must m all such attribute values to be considered an autowire candidate. As an example, consider the following annotation definition: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @@ -4983,22 +5645,37 @@ consider the following annotation definition: Format format(); } ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public enum Format { VHS, DVD, BLURAY } ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -5021,6 +5698,30 @@ for both attributes: `genre` and `format`, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender { + + @Autowired + @MovieQualifier(format = Format.VHS, genre = "Action") + private lateinit var actionVhsCatalog: MovieCatalog + + @Autowired + @MovieQualifier(format = Format.VHS, genre = "Comedy") + private lateinit var comedyVhsCatalog: MovieCatalog + + @Autowired + @MovieQualifier(format = Format.DVD, genre = "Action") + private lateinit var actionDvdCatalog: MovieCatalog + + @Autowired + @MovieQualifier(format = Format.BLURAY, genre = "Comedy") + private lateinit var comedyBluRayCatalog: MovieCatalog + + // ... + } +---- Finally, the bean definitions should contain matching qualifier values. This example also demonstrates that you can use bean meta attributes instead of the @@ -5029,8 +5730,7 @@ precedence, but the autowiring mechanism falls back on the values provided withi `` tags if no such qualifier is present, as in the last two bean definitions in the following example: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ` and `Store`), you can `@Autowire` the `Store` interface and the generic is used as a qualifier, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Autowired private Store s1; // qualifier, injects the stringStore bean @@ -5114,18 +5827,35 @@ used as a qualifier, as the following example shows: @Autowired private Store s2; // qualifier, injects the integerStore bean ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Autowired + private lateinit var s1: Store // qualifier, injects the stringStore bean + + @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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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; ---- +[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> +---- @@ -5137,8 +5867,7 @@ is a `BeanFactoryPostProcessor` that lets you register your own custom qualifier annotation types, even if they are not annotated with Spring's `@Qualifier` annotation. The following example shows how to use `CustomAutowireConfigurer`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -5175,20 +5904,28 @@ endpoints. Spring supports this pattern for Spring-managed objects as well. the bean name to be injected. In other words, it follows by-name semantics, as demonstrated in the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { private MovieFinder movieFinder; - @Resource(name="myMovieFinder") <1> + @Resource(name="myMovieFinder") // This line injects a @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } ---- -<1> This line injects a `@Resource`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +class SimpleMovieLister { + + @Resource(name="myMovieFinder") // This line injects a @Resource + private lateinit var movieFinder:MovieFinder +} +---- If no name is explicitly specified, the default name is derived from the field name or @@ -5196,8 +5933,8 @@ setter method. In case of a field, it takes the field name. In case of a setter it takes the bean property name. The following example is going to have the bean named `movieFinder` injected into its setter method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { @@ -5209,6 +5946,16 @@ named `movieFinder` injected into its setter method: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimpleMovieLister { + + @Resource + private lateinit var movieFinder: MovieFinder + + } +---- NOTE: The name provided with the annotation is resolved as a bean name by the `ApplicationContext` of which the `CommonAnnotationBeanPostProcessor` is aware. @@ -5227,16 +5974,18 @@ Thus, in the following example, the `customerPreferenceDao` field first looks fo named "customerPreferenceDao" and then falls back to a primary type match for the type `CustomerPreferenceDao`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; + // The context field is injected based on the known resolvable dependency + // type: ApplicationContext @Resource - private ApplicationContext context; <1> + private ApplicationContext context; public MovieRecommender() { } @@ -5244,17 +5993,30 @@ 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`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender { + @Resource + private lateinit var customerPreferenceDao: CustomerPreferenceDao + + // The context field is injected based on the known resolvable dependency + // type: ApplicationContext + @Resource + private lateinit var context: ApplicationContext + + // ... + } +---- [[beans-value-annotations]] === Using `@Value` `@Value` is typically used to inject externalized properties: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component public class MovieRecommender { @@ -5266,21 +6028,33 @@ named "customerPreferenceDao" and then falls back to a primary type match for th } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Component + class MovieRecommender(@Value("\${catalog.name}") private val catalog: String) +---- With the following configuration: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @PropertySource("classpath:application.properties") public class AppConfig { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @PropertySource("classpath:application.properties") + class AppConfig +---- And the following `application.properties` file: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes"] ---- catalog.name=MovieCatalog ---- @@ -5293,8 +6067,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration public class AppConfig { @@ -5305,6 +6079,16 @@ example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class AppConfig { + + @Bean + fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer() + } +---- NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig, the `@Bean` method must be `static`. @@ -5323,8 +6107,8 @@ automatically converted to String array without extra effort. It is possible to provide a default value as following: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component public class MovieRecommender { @@ -5336,14 +6120,20 @@ It is possible to provide a default value as following: } } ---- +[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 scene 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration public class AppConfig { @@ -5356,12 +6146,26 @@ provide conversion support for your own custom type, you can provide your own } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class AppConfig { + + @Bean + fun conversionService(): ConversionService { + return DefaultFormattingConversionService().apply { + addConverter(MyCustomConverter()) + } + } + } +---- When `@Value` contains a <> the value will be dynamically computed at runtime as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component public class MovieRecommender { @@ -5373,11 +6177,18 @@ computed at runtime as the following example shows: } } ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component public class MovieRecommender { @@ -5390,6 +6201,13 @@ SpEL also enables the use of more complex data structures: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Component + class MovieRecommender( + @Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map) +---- [[beans-postconstruct-and-predestroy-annotations]] @@ -5407,8 +6225,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class CachingMovieLister { @@ -5423,6 +6241,22 @@ cleared upon destruction: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class CachingMovieLister { + + @PostConstruct + fun populateMovieCache() { + // populates the movie cache upon initialization... + } + + @PreDestroy + fun clearMovieCache() { + // clears the movie cache upon destruction... + } + } +---- For details about the effects of combining various lifecycle mechanisms, see <>. @@ -5497,20 +6331,30 @@ own code. A meta-annotation is an annotation that can be applied to another anno For example, the `@Service` annotation mentioned <> is meta-annotated with `@Component`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented - @Component <1> + @Component // Causes @Service to be treated in the same way as @Component public @interface Service { - // .... + // ... } ---- -<1> The `Component` causes `@Service` to be treated in the same way as `@Component`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Component // Causes @Service to be treated in the same way as @Component + annotation class Service { + // ... + } +---- You can also combine meta-annotations to create "`composed annotations`". For example, the `@RestController` annotation from Spring MVC is composed of `@Controller` and @@ -5523,8 +6367,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -5541,11 +6385,23 @@ customization of the `proxyMode`. The following listing shows the definition of } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Scope(WebApplicationContext.SCOPE_SESSION) + annotation class SessionScope( + @get:AliasFor(annotation = Scope::class) + val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS + ) +---- You can then use `@SessionScope` without declaring the `proxyMode` as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Service @SessionScope @@ -5553,11 +6409,20 @@ You can then use `@SessionScope` without declaring the `proxyMode` as follows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Service + @SessionScope + class SessionScopedService { + // ... + } +---- You can also override the value for the `proxyMode`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Service @SessionScope(proxyMode = ScopedProxyMode.INTERFACES) @@ -5565,6 +6430,15 @@ You can also override the value for the `proxyMode`, as the following example sh // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Service + @SessionScope(proxyMode = ScopedProxyMode.INTERFACES) + class SessionScopedUserService : UserService { + // ... + } +---- For further details, see the https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model] @@ -5579,42 +6453,65 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Service public class SimpleMovieLister { private MovieFinder movieFinder; - @Autowired public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Service + class SimpleMovieLister(private val movieFinder: MovieFinder) +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Repository public class JpaMovieFinder implements MovieFinder { // implementation elided for clarity } ---- +[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 `@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute 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.) -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @ComponentScan(basePackages = ["org.example"]) + class AppConfig { + // ... } ---- @@ -5623,8 +6520,7 @@ annotation (that is, `@ComponentScan("org.example")`). The following alternative uses XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -5754,8 +6660,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component public class FactoryMethodComponent { @@ -5771,6 +6677,21 @@ annotated classes. The following example shows how to do so: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Component + class FactoryMethodComponent { + + @Bean + @Qualifier("public") + fun publicInstance() = TestBean("publicInstance") + + fun doWork() { + // Component method implementation omitted + } + } +---- 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 @@ -5786,8 +6707,8 @@ leads to the injection of a lazy-resolution proxy. 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component public class FactoryMethodComponent { @@ -5823,6 +6744,37 @@ support for autowiring of `@Bean` methods. The following example shows how to do } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Component + class FactoryMethodComponent { + + companion object { + private var i: Int = 0 + } + + @Bean + @Qualifier("public") + fun publicInstance() = TestBean("publicInstance") + + // use of a custom qualifier and autowiring of method parameters + @Bean + protected fun protectedInstance( + @Qualifier("public") spouse: TestBean, + @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply { + this.spouse = spouse + this.country = country + } + + @Bean + private fun privateInstance() = TestBean("privateInstance", i++) + + @Bean + @RequestScope + 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 @@ -5841,8 +6793,8 @@ 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 do use `InjectionPoint`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component public class FactoryMethodComponent { @@ -5853,6 +6805,18 @@ The following example shows how to do use `InjectionPoint`: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Component + class FactoryMethodComponent { + + @Bean + @Scope("prototype") + fun prototypeInstance(injectionPoint: 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` @@ -5916,23 +6880,39 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Service("myMovieLister") public class SimpleMovieLister { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Service("myMovieLister") + class SimpleMovieLister { + // ... + } +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Repository public class MovieFinderImpl implements MovieFinder { // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Repository + class MovieFinderImpl : MovieFinder { + // ... + } +---- NOTE: If you do not want to rely on the default bean-naming strategy, you can provide a custom bean-naming strategy. First, implement the @@ -5941,18 +6921,26 @@ interface, and be sure to include a default no-arg constructor. Then, provide th qualified class name when configuring the scanner, as the following example annotation and bean definition show: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) public class AppConfig { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class) + class AppConfig { + // ... } ---- -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -6026,18 +7031,26 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES) public class AppConfig { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES) + class AppConfig { + // ... } ---- -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -6059,8 +7072,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component @Qualifier("Action") @@ -6068,9 +7081,16 @@ technique: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Component + @Qualifier("Action") + class ActionMovieCatalog : MovieCatalog +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component @Genre("Action") @@ -6078,9 +7098,18 @@ technique: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Component + @Genre("Action") + class ActionMovieCatalog : MovieCatalog { + // ... + } +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Component @Offline @@ -6088,6 +7117,15 @@ technique: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +@Component +@Offline +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 @@ -6112,8 +7150,7 @@ To generate the index, add an additional dependency to each module that contains components that are targets for component scan directives. The following example shows how to do so with Maven: -[source,xml,indent=0] -[subs="verbatim,quotes,attributes"] +[source,xml,indent=0,subs="verbatim,quotes,attributes"] ---- @@ -6128,8 +7165,7 @@ how to do so with Maven: With Gradle 4.5 and earlier, the dependency should be declared in the `compileOnly` configuration, as shown in the following example: -[source,groovy,indent=0] -[subs="verbatim,quotes,attributes"] +[source,groovy,indent=0,subs="verbatim,quotes,attributes"] ---- dependencies { compileOnly "org.springframework:spring-context-indexer:{spring-version}" @@ -6141,8 +7177,7 @@ With Gradle 4.6 and later, the dependency should be declared in the `annotationP configuration, as shown in the following example: ==== -[source,groovy,indent=0] -[subs="verbatim,quotes,attributes"] +[source,groovy,indent=0subs="verbatim,quotes,attributes"] ---- dependencies { annotationProcessor "org.springframework:spring-context-indexer:{spring-version}" @@ -6179,8 +7214,7 @@ repository ( https://repo1.maven.org/maven2/javax/inject/javax.inject/1/[https://repo1.maven.org/maven2/javax/inject/javax.inject/1/]). You can add the following dependency to your file pom.xml: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- javax.inject @@ -6197,8 +7231,8 @@ You can add the following dependency to your file pom.xml: Instead of `@Autowired`, you can use `@javax.inject.Inject` as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import javax.inject.Inject; @@ -6213,7 +7247,24 @@ Instead of `@Autowired`, you can use `@javax.inject.Inject` as follows: public void listMovies() { this.movieFinder.findMovies(...); - ... + // ... + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import javax.inject.Inject + + class SimpleMovieLister { + + @Inject + lateinit var movieFinder: MovieFinder + + + fun listMovies() { + movieFinder.findMovies(...) + // ... } } ---- @@ -6224,8 +7275,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import javax.inject.Inject; import javax.inject.Provider; @@ -6241,7 +7292,24 @@ preceding example: public void listMovies() { this.movieFinder.get().findMovies(...); - ... + // ... + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import javax.inject.Inject + + class SimpleMovieLister { + + @Inject + lateinit var movieFinder: MovieFinder + + + fun listMovies() { + movieFinder.findMovies(...) + // ... } } ---- @@ -6249,8 +7317,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import javax.inject.Inject; import javax.inject.Named; @@ -6267,35 +7335,61 @@ you should use the `@Named` annotation, as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import javax.inject.Inject + import javax.inject.Named + + class SimpleMovieLister { + + private lateinit var movieFinder: MovieFinder + + @Inject + fun setMovieFinder(@Named("main") movieFinder: MovieFinder) { + this.movieFinder = movieFinder + } + + // ... + } +---- 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 a `required` attribute. The following pair of examples show how to use `@Inject` and `@Nullable`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes"] ---- public class SimpleMovieLister { @Inject public void setMovieFinder(Optional movieFinder) { - ... + // ... } } ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { @Inject public void setMovieFinder(@Nullable MovieFinder movieFinder) { - ... + // ... } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimpleMovieLister { + + @Inject + var movieFinder: MovieFinder? = null + } +---- @@ -6305,8 +7399,8 @@ a `required` attribute. The following pair of examples show how to use `@Inject` Instead of `@Component`, you can use `@javax.inject.Named` or `javax.annotation.ManagedBean`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import javax.inject.Inject; import javax.inject.Named; @@ -6324,12 +7418,27 @@ as the following example shows: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import javax.inject.Inject + import javax.inject.Named + + @Named("movieListener") // @ManagedBean("movieListener") could be used as well + class SimpleMovieLister { + + @Inject + lateinit var movieFinder: MovieFinder + + // ... + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import javax.inject.Inject; import javax.inject.Named; @@ -6347,17 +7456,41 @@ It is very common to use `@Component` without specifying a name for the componen // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import javax.inject.Inject + import javax.inject.Named + + @Named + class SimpleMovieLister { + + @Inject + lateinit var movieFinder: MovieFinder + + // ... + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @ComponentScan(basePackages = ["org.example"]) + class AppConfig { + // ... } ---- @@ -6457,8 +7590,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration public class AppConfig { @@ -6469,11 +7602,22 @@ The simplest possible `@Configuration` class reads as follows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class AppConfig { + + @Bean + fun myService(): MyService { + return MyServiceImpl() + } + } +---- The preceding `AppConfig` class is equivalent to the following Spring `` XML: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -6536,8 +7680,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); @@ -6545,13 +7689,24 @@ XML-free usage of the Spring container, as the following example shows: myService.doStuff(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.beans.factory.getBean + + fun main() { + val ctx = AnnotationConfigApplicationContext(AppConfig::class.java) + val myService = ctx.getBean() + 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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); @@ -6559,6 +7714,17 @@ as input to the constructor, as the following example shows: myService.doStuff(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.beans.factory.getBean + + fun main() { + val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java) + val myService = ctx.getBean() + myService.doStuff() + } +---- The preceding example assumes that `MyServiceImpl`, `Dependency1`, and `Dependency2` use Spring dependency injection annotations such as `@Autowired`. @@ -6572,8 +7738,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -6584,6 +7750,20 @@ example shows how to do so: myService.doStuff(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.beans.factory.getBean + + fun main() { + val ctx = AnnotationConfigApplicationContext() + ctx.register(AppConfig::class.java, OtherConfig::class.java) + ctx.register(AdditionalConfig::class.java) + ctx.refresh() + val myService = ctx.getBean() + myService.doStuff() + } +---- [[beans-java-instantiating-container-scan]] @@ -6591,16 +7771,24 @@ example shows how to do so: To enable component scanning, you can annotate your `@Configuration` class as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration - @ComponentScan(basePackages = "com.acme") <1> + @ComponentScan(basePackages = "com.acme") // This annotation enables component scanning public class AppConfig { ... } ---- -<1> This annotation enables component scanning. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @ComponentScan(basePackages = ["com.acme"]) // This annotation enables component scanning + class AppConfig { + // ... + } +---- [TIP] @@ -6623,8 +7811,8 @@ definitions within the container. `AnnotationConfigApplicationContext` exposes t `scan(String...)` method to allow for the same component-scanning functionality, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -6633,6 +7821,16 @@ following example shows: MyService myService = ctx.getBean(MyService.class); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun main() { + val ctx = AnnotationConfigApplicationContext() + ctx.scan("com.acme") + ctx.refresh() + val myService = ctx.getBean() + } +---- NOTE: Remember that `@Configuration` classes are <> with `@Component`, so they are candidates for component-scanning. In the preceding example, @@ -6651,8 +7849,7 @@ configuring the Spring `ContextLoaderListener` servlet listener, Spring MVC Spring MVC web application (note the use of the `contextClass` context-param and init-param): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -7627,16 +9256,15 @@ The following example shows part of a sample `system-test-config.xml` file: The following example shows a possible `jdbc.properties` file: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password= ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[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"); @@ -7644,6 +9272,16 @@ jdbc.password= // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun main() { + val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml") + val transferService = ctx.getBean() + // ... + } +---- + NOTE: In `system-test-config.xml` file, the `AppConfig` `` does not declare an `id` element. While it would be acceptable to do so, it is unnecessary, given that no other bean @@ -7663,8 +9301,7 @@ functionality. The following example shows the modified `system-test-config.xml` file: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -7692,8 +9329,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @ImportResource("classpath:/com/acme/properties-config.xml") @@ -7714,9 +9351,30 @@ as needed: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @ImportResource("classpath:/com/acme/properties-config.xml") + class AppConfig { -[source,xml,indent=0] -[subs="verbatim,quotes"] + @Value("\${jdbc.url}") + private lateinit var url: String + + @Value("\${jdbc.username}") + private lateinit var username: String + + @Value("\${jdbc.password}") + private lateinit var password: String + + @Bean + fun dataSource(): DataSource { + return DriverManagerDataSource(url, username, password) + } + } +---- + +[source,xml,indent=0,subs="verbatim,quotes"] ---- properties-config.xml @@ -7724,8 +9382,7 @@ as needed: ---- -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb @@ -7733,8 +9390,8 @@ jdbc.username=sa jdbc.password= ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); @@ -7742,6 +9399,17 @@ jdbc.password= // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.beans.factory.getBean + + fun main() { + val ctx = AnnotationConfigApplicationContext(AppConfig::class.java) + val transferService = ctx.getBean() + // ... + } +---- @@ -7786,8 +9454,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Bean public DataSource dataSource() { @@ -7798,14 +9466,26 @@ Consider the first use case in a practical application that requires a .build(); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Bean + fun dataSource(): DataSource { + return EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("my-schema.sql") + .addScript("my-test-data.sql") + .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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Bean(destroyMethod="") public DataSource dataSource() throws Exception { @@ -7813,6 +9493,15 @@ now looks like the following listing: return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Bean(destroyMethod = "") + fun dataSource(): DataSource { + val ctx = InitialContext() + 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 @@ -7837,8 +9526,8 @@ annotation lets you indicate that a component is eligible for registration when one or more specified profiles are active. Using our preceding example, we can rewrite the `dataSource` configuration as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @Profile("development") @@ -7854,9 +9543,26 @@ can rewrite the `dataSource` configuration as follows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("development") + class StandaloneDataConfig { -[source,java,indent=0] -[subs="verbatim,quotes"] + @Bean + fun dataSource(): DataSource { + return EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("classpath:com/bank/config/sql/schema.sql") + .addScript("classpath:com/bank/config/sql/test-data.sql") + .build() + } + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @Profile("production") @@ -7869,6 +9575,20 @@ can rewrite the `dataSource` configuration as follows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("production") + class JndiDataConfig { + + @Bean(destroyMethod = "") + fun dataSource(): DataSource { + val ctx = InitialContext() + return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource + } + } +---- NOTE: As mentioned earlier, with `@Bean` methods, you typically choose to use programmatic JNDI lookups, by using either Spring's `JndiTemplate`/`JndiLocatorDelegate` helpers or the @@ -7893,8 +9613,8 @@ of creating a custom composed annotation. The following example defines a custom `@Production` annotation that you can use as a drop-in replacement for `@Profile("production")`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -7902,6 +9622,14 @@ of creating a custom composed annotation. The following example defines a custom public @interface Production { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE) + @Retention(AnnotationRetention.RUNTIME) + @Profile("production") + annotation class Production +---- TIP: If a `@Configuration` class is marked with `@Profile`, all of the `@Bean` methods and `@Import` annotations associated with that class are bypassed unless one or more of @@ -7916,14 +9644,14 @@ active. For example, given `@Profile({"p1", "!p2"})`, registration will occur if of a configuration class (for example, for alternative variants of a particular bean), as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration public class AppConfig { @Bean("dataSource") - @Profile("development") <1> + @Profile("development") // The standaloneDataSource method is available only in the development profile public DataSource standaloneDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) @@ -7933,16 +9661,35 @@ the following example shows: } @Bean("dataSource") - @Profile("production") <2> + @Profile("production") // The jndiDataSource method is available only in the production profile public DataSource jndiDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } } ---- -<1> The `standaloneDataSource` method is available only in the `development` profile. -<2> The `jndiDataSource` method is available only in the `production` profile. -==== +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class AppConfig { + + @Bean("dataSource") + @Profile("development") // The standaloneDataSource method is available only in the development profile + fun standaloneDataSource(): DataSource { + return EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("classpath:com/bank/config/sql/schema.sql") + .addScript("classpath:com/bank/config/sql/test-data.sql") + .build() + } + + @Bean("dataSource") + @Profile("production") // The jndiDataSource method is available only in the production profile + fun jndiDataSource() = + InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource + } +---- [NOTE] ==== @@ -7961,6 +9708,7 @@ attribute, as shown in the preceding example. If the argument signatures are all the same (for example, all of the variants have no-arg factory methods), this is the only way to represent such an arrangement in a valid Java class in the first place (since there can only be one method of a particular name and argument signature). +==== [[beans-definition-profiles-xml]] @@ -7969,8 +9717,7 @@ way to represent such an arrangement in a valid Java class in the first place The XML counterpart is the `profile` attribute of the `` element. Our preceding sample configuration can be rewritten in two XML files, as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ` elements within the same file, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -8316,19 +10135,25 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @EnableLoadTimeWeaving public class AppConfig { } ---- +[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: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -8409,8 +10234,7 @@ Spring provides two `MessageSource` implementations, `ResourceBundleMessageSourc messaging. The `StaticMessageSource` is rarely used but provides programmatic ways to add messages to the source. The following example shows `ResourceBundleMessageSource`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -8496,8 +10325,8 @@ converted into `String` objects and inserted into placeholders in the lookup mes ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Example { @@ -8509,16 +10338,29 @@ converted into `String` objects and inserted into placeholders in the lookup mes public void execute() { String message = this.messages.getMessage("argument.required", - new Object [] {"userDao"}, "Required", null); + new Object [] {"userDao"}, "Required", Locale.ENGLISH); System.out.println(message); } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class Example { + + lateinit var messages: MessageSource + + fun execute() { + val message = messages.getMessage("argument.required", + arrayOf("userDao"), "Required", Locale.ENGLISH) + println(message) + } +} +---- The resulting output from the invocation of the `execute()` method is as follows: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- The userDao argument is required. ---- @@ -8534,15 +10376,14 @@ Typically, locale resolution is managed by the surrounding environment of the application. In the following example, the locale against which (British) messages are resolved is specified manually: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- # in exceptions_en_GB.properties argument.required=Ebagum lad, the {0} argument is required, I say, required. ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); @@ -8551,11 +10392,20 @@ argument.required=Ebagum lad, the {0} argument is required, I say, required. System.out.println(message); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun main() { + val resources = ClassPathXmlApplicationContext("beans.xml") + val message = resources.getMessage("argument.required", + arrayOf("userDao"), "Required", Locale.UK) + println(message) + } +---- The resulting output from the running of the above program is as follows: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- Ebagum lad, the 'userDao' argument is required, I say, required. ---- @@ -8642,8 +10492,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class BlackListEvent extends ApplicationEvent { @@ -8659,14 +10509,21 @@ simple class that extends Spring's `ApplicationEvent` base class: // accessor and other methods... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class BlackListEvent(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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class EmailService implements ApplicationEventPublisherAware { @@ -8690,6 +10547,31 @@ example shows such a class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class EmailService : ApplicationEventPublisherAware { + + private lateinit var blackList: List + private lateinit var publisher: ApplicationEventPublisher + + fun setBlackList(blackList: List) { + this.blackList = blackList + } + + override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) { + this.publisher = publisher + } + + fun sendEmail(address: String, content: String) { + if (blackList!!.contains(address)) { + publisher!!.publishEvent(BlackListEvent(this, address, content)) + return + } + // send email... + } + } +---- At configuration time, the Spring container detects that `EmailService` implements `ApplicationEventPublisherAware` and automatically calls @@ -8701,8 +10583,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class BlackListNotifier implements ApplicationListener { @@ -8717,6 +10599,18 @@ shows such a class: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class BlackListNotifier : ApplicationListener { + + lateinit var notificationAddres: String + + override fun onApplicationEvent(event: BlackListEvent) { + // notify appropriate parties via notificationAddress... + } + } +---- Notice that `ApplicationListener` is generically parameterized with the type of your custom event (`BlackListEvent` in the preceding example). This means that the `onApplicationEvent()` method can @@ -8734,8 +10628,7 @@ implementation for configuration options. The following example shows the bean definitions used to register and configure each of the classes above: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -8774,8 +10667,8 @@ As of Spring 4.2, you can register an event listener on any public method of a m bean by using the `@EventListener` annotation. The `BlackListNotifier` can be rewritten as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class BlackListNotifier { @@ -8791,6 +10684,19 @@ follows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class BlackListNotifier { + + lateinit var notificationAddress: String + + @EventListener + fun processBlackListEvent(event: BlackListEvent) { + // notify appropriate parties via notificationAddress... + } + } +---- 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. @@ -8801,12 +10707,20 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) public void handleContextStart() { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class) + fun handleContextStart() { + // ... } ---- @@ -8817,14 +10731,22 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @EventListener(condition = "#blEvent.content == 'my-event'") public void processBlackListEvent(BlackListEvent blEvent) { // notify appropriate parties via notificationAddress... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @EventListener(condition = "#blEvent.content == 'my-event'") + fun processBlackListEvent(blEvent: BlackListEvent) { + // 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: @@ -8859,8 +10781,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @EventListener public ListUpdateEvent handleBlackListEvent(BlackListEvent event) { @@ -8868,6 +10790,15 @@ method signature to return the event that should be published, as the following // then publish a ListUpdateEvent... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @EventListener + fun handleBlackListEvent(event: BlackListEvent): ListUpdateEvent { + // notify appropriate parties via notificationAddress and + // then publish a ListUpdateEvent... + } +---- NOTE: This feature is not supported for <>. @@ -8884,8 +10815,8 @@ If you want a particular listener to process events asynchronously, you can reus <>. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @EventListener @Async @@ -8893,6 +10824,15 @@ The following example shows how to do so: // BlackListEvent is processed in a separate thread } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @EventListener + @Async + fun processBlackListEvent(event: BlackListEvent) { + // BlackListEvent is processed in a separate thread + } +---- Be aware of the following limitations when using asynchronous events: @@ -8910,8 +10850,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @EventListener @Order(42) @@ -8919,6 +10859,15 @@ annotation to the method declaration, as the following example shows: // notify appropriate parties via notificationAddress... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @EventListener + @Order(42) + fun processBlackListEvent(event: BlackListEvent) { + // notify appropriate parties via notificationAddress... + } +---- [[context-functionality-events-generics]] @@ -8929,12 +10878,20 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @EventListener public void onPersonCreated(EntityCreatedEvent event) { - ... + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @EventListener + fun onPersonCreated(event: EntityCreatedEvent) { + // ... } ---- @@ -8947,8 +10904,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class EntityCreatedEvent extends ApplicationEvent implements ResolvableTypeProvider { @@ -8962,6 +10919,16 @@ environment provides. The following event shows how to do so: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class EntityCreatedEvent(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider { + + override fun getResolvableType(): ResolvableType? { + return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource())) + } + } +---- TIP: This works not only for `ApplicationEvent` but any arbitrary object that you send as an event. @@ -9010,8 +10977,7 @@ programmatically by using one of the `ApplicationContext` implementations. You can register an `ApplicationContext` by using the `ContextLoaderListener`, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- contextConfigLocation @@ -9169,8 +11135,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // populate the factory with bean definitions @@ -9181,12 +11147,24 @@ you need to programmatically call `addBeanPostProcessor`, as the following examp // now start using the factory ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val factory = DefaultListableBeanFactory() + // populate the factory with bean definitions + + // now register any needed BeanPostProcessor instances + factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor()) + factory.addBeanPostProcessor(MyBeanPostProcessor()) + + // 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); @@ -9199,6 +11177,20 @@ you need to call its `postProcessBeanFactory` method, as the following example s // now actually do the replacement cfg.postProcessBeanFactory(factory); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val factory = DefaultListableBeanFactory() + val reader = XmlBeanDefinitionReader(factory) + reader.loadBeanDefinitions(FileSystemResource("beans.xml")) + + // bring in some property values from a Properties file + val cfg = PropertySourcesPlaceholderConfigurer() + cfg.setLocation(FileSystemResource("jdbc.properties")) + + // 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/src/docs/asciidoc/core/core-databuffer-codec.adoc b/src/docs/asciidoc/core/core-databuffer-codec.adoc index 50f00110c0..ca50651449 100644 --- a/src/docs/asciidoc/core/core-databuffer-codec.adoc +++ b/src/docs/asciidoc/core/core-databuffer-codec.adoc @@ -140,8 +140,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- DataBuffer buffer = factory.allocateBuffer(); boolean release = true; @@ -156,6 +156,21 @@ a serialization error occurs while populating the buffer with data. For example: } return buffer; ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val buffer = factory.allocateBuffer() + var release = true + try { + // serialize and populate buffer.. + release = false + } finally { + if (release) { + DataBufferUtils.release(buffer) + } + } + 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/src/docs/asciidoc/core/core-expressions.adoc b/src/docs/asciidoc/core/core-expressions.adoc index 20b5f9fe80..a85607ada7 100644 --- a/src/docs/asciidoc/core/core-expressions.adoc +++ b/src/docs/asciidoc/core/core-expressions.adoc @@ -65,14 +65,20 @@ The complete language reference can be found in The following code introduces the SpEL API to evaluate the literal string expression, `Hello World`. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); - Expression exp = parser.parseExpression("'Hello World'"); <1> + Expression exp = parser.parseExpression("'Hello World'"); // The value of the message variable is 'Hello World' String message = (String) exp.getValue(); ---- -<1> The value of the message variable is `'Hello World'`. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + val exp = parser.parseExpression("'Hello World'") // The value of the message variable is 'Hello World' + val message = exp.value as String +---- The SpEL classes and interfaces you are most likely to use are located in the @@ -90,29 +96,43 @@ and calling constructors. In the following example of method invocation, we call the `concat` method on the string literal: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); - Expression exp = parser.parseExpression("'Hello World'.concat('!')"); <1> + Expression exp = parser.parseExpression("'Hello World'.concat('!')"); + // The value of message is now 'Hello World!' String message = (String) exp.getValue(); ---- -<1> The value of `message` is now 'Hello World!'. - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + val exp = parser.parseExpression("'Hello World'.concat('!')") + // The value of message is now 'Hello World!' + val message = exp.value as String +---- The following example of calling a JavaBean property calls the `String` property `Bytes`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes()' - Expression exp = parser.parseExpression("'Hello World'.bytes"); <1> + Expression exp = parser.parseExpression("'Hello World'.bytes"); // This line converts the literal to a byte array byte[] bytes = (byte[]) exp.getValue(); ---- -<1> This line converts the literal to a byte array. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + // invokes 'getBytes()' + val exp = parser.parseExpression("'Hello World'.bytes") // This line converts the literal to a byte array + val bytes = exp.value as ByteArray +---- SpEL also supports nested properties by using the standard dot notation (such as `prop1.prop2.prop3`) and also the corresponding setting of property values. @@ -120,29 +140,44 @@ Public fields may also be accessed. The following example shows how to use dot notation to get the length of a literal: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes().length' - Expression exp = parser.parseExpression("'Hello World'.bytes.length"); <1> + Expression exp = parser.parseExpression("'Hello World'.bytes.length"); // 'Hello World'.bytes.length gives the length of the literal. 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"] +.Kotlin +---- + val parser = SpelExpressionParser() + // invokes 'getBytes().length' + val exp = parser.parseExpression("'Hello World'.bytes.length") // 'Hello World'.bytes.length gives the length of the literal. + val length = exp.value as Int +---- The String's constructor can be called instead of using a string literal, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); - Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); <1> + // Construct a new String from the literal and make it be upper case + Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); 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"] +.Kotlin +---- + val parser = SpelExpressionParser() + // Construct a new String from the literal and make it be upper case + val exp = parser.parseExpression("new String('hello world').toUpperCase()") + val message = exp.getValue(String::class.java) +---- Note the use of the generic method: `public T getValue(Class desiredResultType)`. @@ -155,8 +190,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Create and set a calendar GregorianCalendar c = new GregorianCalendar(); @@ -167,7 +202,7 @@ create a boolean condition: ExpressionParser parser = new SpelExpressionParser(); - Expression exp = parser.parseExpression("name"); <1> + Expression exp = parser.parseExpression("name"); // Parse name as an expression String name = (String) exp.getValue(tesla); // name == "Nikola Tesla" @@ -175,7 +210,27 @@ create a boolean condition: boolean result = exp.getValue(tesla, Boolean.class); // result == true ---- -<1> Parse `name` as an expression. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Create and set a calendar + val c = GregorianCalendar() + c.set(1856, 7, 9) + + // The constructor arguments are name, birthday, and nationality. + val tesla = Inventor("Nikola Tesla", c.time, "Serbian") + + val parser = SpelExpressionParser() + + var exp = parser.parseExpression("name") // Parse name as an expression + val name = exp.getValue(tesla) as String + // name == "Nikola Tesla" + + exp = parser.parseExpression("name == 'Nikola Tesla'") + val result = exp.getValue(tesla, Boolean::class.java) + // result == true +---- + @@ -223,8 +278,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- class Simple { public List booleanList = new ArrayList(); @@ -242,7 +297,25 @@ being placed in it. The following example shows how to do so: // b is false Boolean b = simple.booleanList.get(0); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class Simple { + var booleanList: MutableList = ArrayList() + } + val simple = Simple() + simple.booleanList.add(true) + + val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() + + // "false" is passed in here as a String. SpEL and the conversion service + // will recognize that it needs to be a Boolean and convert it accordingly. + parser.parseExpression("booleanList[0]").setValue(context, simple, "false") + + // b is false + val b = simple.booleanList[0] +---- [[expressions-parser-configuration]] @@ -258,8 +331,8 @@ and specifying an index that is beyond the end of the current size of the array list, you can automatically grow the array or list to accommodate that index. The following example demonstrates how to automatically grow the list: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- class Demo { public List list; @@ -281,6 +354,29 @@ example demonstrates how to automatically grow the list: // demo.list will now be a real collection of 4 entries // Each entry is a new empty String ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class Demo { + var list: List? = null + } + + // Turn on: + // - auto null reference initialization + // - auto collection growing + val config = SpelParserConfiguration(true, true) + + val parser = SpelExpressionParser(config) + + val expression = parser.parseExpression("list[3]") + + val demo = Demo() + + val o = expression.getValue(demo) + + // demo.list will now be a real collection of 4 entries + // Each entry is a new empty String +---- @@ -349,8 +445,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, this.getClass().getClassLoader()); @@ -363,6 +459,20 @@ following example shows how to do so: Object payload = expr.getValue(message); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, + this.javaClass.classLoader) + + val parser = SpelExpressionParser(config) + + val expr = parser.parseExpression("payload") + + val message = MyMessage() + + 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. @@ -409,8 +519,7 @@ form `#{ }`. A property or constructor argument value can be set by using expressions, as the following example shows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -422,8 +531,7 @@ example shows: The `systemProperties` variable is predefined, so you can use it in your expressions, as the following example shows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -437,8 +545,7 @@ symbol in this context. You can also refer to other bean properties by name, as the following example shows: -[source,xml,indent=0] -[subs="verbatim"] +[source,xml,indent=0,subs="verbatim"] ---- @@ -463,31 +570,39 @@ parameters. The following example sets the default value of a field variable: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - public static class FieldValueTestBean + public class FieldValueTestBean { + + @Value("#{ systemProperties['user.region'] }") + private String defaultLocale; + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + public String getDefaultLocale() { + return this.defaultLocale; + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class FieldValueTestBean { @Value("#{ systemProperties['user.region'] }") - private String defaultLocale; - - public void setDefaultLocale(String defaultLocale) { - this.defaultLocale = defaultLocale; - } - - public String getDefaultLocale() { - return this.defaultLocale; - } - + var defaultLocale: String? = null } ---- The following example shows the equivalent but on a property setter method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - public static class PropertyValueTestBean + public class PropertyValueTestBean { private String defaultLocale; @@ -499,15 +614,23 @@ The following example shows the equivalent but on a property setter method: public String getDefaultLocale() { return this.defaultLocale; } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class PropertyValueTestBean { + @Value("#{ systemProperties['user.region'] }") + var defaultLocale: String? = null } ---- Autowired methods and constructors can also use the `@Value` annotation, as the following examples show: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SimpleMovieLister { @@ -524,9 +647,27 @@ examples show: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SimpleMovieLister { -[source,java,indent=0] -[subs="verbatim,quotes"] + private lateinit var movieFinder: MovieFinder + private lateinit var defaultLocale: String + + @Autowired + fun configure(movieFinder: MovieFinder, + @Value("#{ systemProperties['user.region'] }") defaultLocale: String) { + this.movieFinder = movieFinder + this.defaultLocale = defaultLocale + } + + // ... + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MovieRecommender { @@ -534,7 +675,6 @@ examples show: private CustomerPreferenceDao customerPreferenceDao; - @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, @Value("#{systemProperties['user.country']}") String defaultLocale) { this.customerPreferenceDao = customerPreferenceDao; @@ -544,6 +684,14 @@ examples show: // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao, + @Value("#{systemProperties['user.country']}") private val defaultLocale: String) { + // ... + } +---- @@ -583,8 +731,8 @@ The following listing shows simple usage of literals. Typically, they are not us in isolation like this but, rather, as part of a more complex expression -- for example, using a literal on one side of a logical comparison operator. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); @@ -600,6 +748,23 @@ using a literal on one side of a logical comparison operator. Object nullValue = parser.parseExpression("null").getValue(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + + // evals to "Hello World" + val helloWorld = parser.parseExpression("'Hello World'").value as String + + val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double + + // evals to 2147483647 + val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int + + val trueValue = parser.parseExpression("true").value as Boolean + + val nullValue = parser.parseExpression("null").value +---- Numbers support the use of the negative sign, exponential notation, and decimal points. By default, real numbers are parsed by using Double.parseDouble(). @@ -615,21 +780,29 @@ data listed in the <> To navigate "`down`" and get Tesla's year of birth and Pupin's city of birth, we use the following expressions: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // evals to 1856 int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // evals to 1856 + val year = parser.parseExpression("Birthdate.Year + 1900").getValue(context) as Int + + val city = parser.parseExpression("placeOfBirth.City").getValue(context) as String +---- Case insensitivity is allowed for the first letter of property names. The contents of arrays and lists are obtained by using square bracket notation, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); @@ -651,13 +824,36 @@ shows: String invention = parser.parseExpression("Members[0].Inventions[6]").getValue( context, ieee, String.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() + + // Inventions Array + + // evaluates to "Induction motor" + val invention = parser.parseExpression("inventions[3]").getValue( + context, tesla, String::class.java) + + // Members List + + // evaluates to "Nikola Tesla" + val name = parser.parseExpression("Members[0].Name").getValue( + context, ieee, String::class.java) + + // List and Array navigation + // evaluates to "Wireless communication" + 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Officer's Dictionary @@ -672,6 +868,22 @@ string literals: parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue( societyContext, "Croatia"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Officer's Dictionary + + val pupin = parser.parseExpression("Officers['president']").getValue( + societyContext, Inventor::class.java) + + // evaluates to "Idvor" + val city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue( + societyContext, String::class.java) + + // setting values + parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue( + societyContext, "Croatia") +---- @@ -680,14 +892,22 @@ string literals: You can directly express lists in an expression by using `{}` notation. -[source,java,indent=0] -[subs="verbatim,quotes"] +[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); ---- +[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 @@ -701,14 +921,22 @@ expression (rather than building a new list on each evaluation). You can also directly express maps in an expression by using `{key:value}` notation. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // 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); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",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 constant map is created @@ -723,8 +951,8 @@ is optional. The examples above do not use quoted keys. 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); @@ -734,6 +962,17 @@ 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); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray + + // Array with initializer + val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray + + // Multi dimensional array + val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array +---- You cannot currently supply an initializer when you construct multi-dimensional array. @@ -747,8 +986,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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); @@ -757,7 +996,16 @@ invoke methods: boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue( societyContext, Boolean.class); ---- +[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) + // evaluates to true + val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue( + societyContext, Boolean::class.java) +---- [[expressions-operators]] @@ -778,8 +1026,8 @@ The relational operators (equal, not equal, less than, less than or equal, great and greater than or equal) are supported by using standard operator notation. The following listing shows a few examples of operators: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // evaluates to true boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); @@ -790,6 +1038,18 @@ following listing shows a few examples of operators: // evaluates to true boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // evaluates to true + val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java) + + // evaluates to false + val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java) + + // evaluates to true + val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java) +---- [NOTE] ==== @@ -805,8 +1065,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // evaluates to false boolean falseValue = parser.parseExpression( @@ -820,6 +1080,21 @@ expression-based `matches` operator. The following listing shows examples of bot boolean falseValue = parser.parseExpression( "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // evaluates to false + val falseValue = parser.parseExpression( + "'xyz' instanceof T(Integer)").getValue(Boolean::class.java) + + // evaluates to true + val trueValue = parser.parseExpression( + "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java) + + //evaluates to false + 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 the wrapper type, so `1 instanceof T(int)` evaluates to `false` while `1 instanceof T(Integer)` @@ -853,8 +1128,8 @@ SpEL supports the following logical operators: The following example shows how to use the logical operators -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // -- AND -- @@ -883,6 +1158,36 @@ 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); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // -- AND -- + + // evaluates to false + val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java) + + // evaluates to true + val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')" + val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java) + + // -- OR -- + + // evaluates to true + val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java) + + // evaluates to true + val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')" + val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java) + + // -- NOT -- + + // evaluates to false + val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java) + + // -- AND and NOT -- + val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')" + val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java) +---- [[expressions-operators-mathematical]] @@ -893,8 +1198,8 @@ and division operators only on numbers. You can also use the modulus (%) and exponential power (^) operators. Standard operator precedence is enforced. The following example shows the mathematical operators in use: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Addition int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 @@ -925,6 +1230,38 @@ following example shows the mathematical operators in use: // Operator precedence int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Addition + val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2 + + val testString = parser.parseExpression( + "'test' + ' ' + 'string'").getValue(String::class.java) // 'test string' + + // Subtraction + val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4 + + val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java) // -9000 + + // Multiplication + val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6 + + val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0 + + // Division + val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2 + + val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0 + + // Modulus + val three = parser.parseExpression("7 % 4").getValue(Int::class.java) // 3 + + val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1 + + // Operator precedence + val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21 +---- [[expressions-assignment]] @@ -934,8 +1271,8 @@ To setting a property, use the assignment operator (`=`). This is typically done within a call to `setValue` but can also be done inside a call to `getValue`. The following listing shows both ways to use the assignment operator: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Inventor inventor = new Inventor(); EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); @@ -946,7 +1283,18 @@ following listing shows both ways to use the assignment operator: String aleks = parser.parseExpression( "Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val inventor = Inventor() + val context = SimpleEvaluationContext.forReadWriteDataBinding().build() + parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic") + + // alternatively + val aleks = parser.parseExpression( + "Name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java) +---- [[expressions-types]] @@ -960,8 +1308,8 @@ type). Static methods are invoked by using this operator as well. The fully qualified, but all other type references must be. The following example shows how to use the `T` operator: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); @@ -971,6 +1319,17 @@ to use the `T` operator: "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") .getValue(Boolean.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java) + + val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java) + + val trueValue = parser.parseExpression( + "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") + .getValue(Boolean::class.java) +---- @@ -981,8 +1340,8 @@ You can invoke constructors by using the `new` operator. You should use the full for all but the primitive types (`int`, `float`, and so on) and String. The following example shows how to use the `new` operator to invoke constructors: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Inventor einstein = p.parseExpression( "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')") @@ -993,6 +1352,18 @@ example shows how to use the `new` operator to invoke constructors: "Members.add(new org.spring.samples.spel.inventor.Inventor( 'Albert Einstein', 'German'))").getValue(societyContext); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val einstein = p.parseExpression( + "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')") + .getValue(Inventor::class.java) + + //create new inventor instance within add method of List + p.parseExpression( + "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))") + .getValue(societyContext) +---- @@ -1003,8 +1374,8 @@ You can reference variables in the expression by using the `#variableName` synta are set by using the `setVariable` method on `EvaluationContext` implementations. The following example shows how to use variables: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); @@ -1014,6 +1385,17 @@ following example shows how to use variables: parser.parseExpression("Name = #newName").getValue(context, tesla); System.out.println(tesla.getName()) // "Mike Tesla" ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val tesla = Inventor("Nikola Tesla", "Serbian") + + val context = SimpleEvaluationContext.forReadWriteDataBinding().build() + context.setVariable("newName", "Mike Tesla") + + parser.parseExpression("Name = #newName").getValue(context, tesla) + println(tesla.name) // "Mike Tesla" +---- [[expressions-this-root]] @@ -1025,8 +1407,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // create an array of integers List primes = new ArrayList(); @@ -1042,6 +1424,23 @@ show how to use the `#this` and `#root` variables: List primesGreaterThanTen = (List) parser.parseExpression( "#primes.?[#this>10]").getValue(context); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // create an array of integers + val primes = ArrayList() + primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17)) + + // create parser and set variable 'primes' as the array of integers + val parser = SpelExpressionParser() + val context = SimpleEvaluationContext.forReadOnlyDataAccess() + context.setVariable("primes", primes) + + // all prime numbers > 10 from the list (using selection ?{...}) + // evaluates to [11, 13, 17] + val primesGreaterThanTen = parser.parseExpression( + "#primes.?[#this>10]").getValue(context) as List +---- @@ -1052,36 +1451,55 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Method method = ...; EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("myFunction", method); ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public abstract class StringUtils { public static String reverseString(String input) { StringBuilder backwards = new StringBuilder(input.length()); - for (int i = 0; i < input.length(); i++) + for (int i = 0; i < input.length(); i++) { backwards.append(input.charAt(input.length() - 1 - i)); } return backwards.toString(); } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun reverseString(input: String): String { + val backwards = StringBuilder(input.length) + for (i in 0 until input.length) { + backwards.append(input[input.length - 1 - i]) + } + return backwards.toString() + } +---- You can then register and use the preceding method, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); @@ -1092,6 +1510,17 @@ You can then register and use the preceding method, as the following example sho String helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + + val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() + context.setVariable("reverseString", ::reverseString::javaMethod) + + val helloWorldReversed = parser.parseExpression( + "#reverseString('hello')").getValue(context, String::class.java) +---- @@ -1102,8 +1531,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -1112,12 +1541,22 @@ to do so: // This will end up calling resolve(context,"something") on MyBeanResolver during evaluation Object bean = parser.parseExpression("@something").getValue(context); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + val context = StandardEvaluationContext() + context.setBeanResolver(MyBeanResolver()) + + // 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -1126,7 +1565,16 @@ 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); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + val context = StandardEvaluationContext() + context.setBeanResolver(MyBeanResolver()) + // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation + val bean = parser.parseExpression("&foo").getValue(context) +---- [[expressions-operator-ternary]] @@ -1135,18 +1583,24 @@ The following example shows how to do so: You can use the ternary operator for performing if-then-else conditional logic inside the expression. The following listing shows a minimal example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- String falseString = parser.parseExpression( "false ? 'trueExp' : 'falseExp'").getValue(String.class); ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- parser.parseExpression("Name").setValue(societyContext, "IEEE"); societyContext.setVariable("queryName", "Nikola Tesla"); @@ -1158,6 +1612,18 @@ realistic example follows: .getValue(societyContext, String.class); // queryResultString = "Nikola Tesla is a member of the IEEE Society" ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + parser.parseExpression("Name").setValue(societyContext, "IEEE") + societyContext.setVariable("queryName", "Nikola Tesla") + + expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'" + + val queryResultString = parser.parseExpression(expression) + .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. @@ -1172,8 +1638,7 @@ http://www.groovy-lang.org/operators.html#_elvis_operator[Groovy] language. With the ternary operator syntax, you usually have to repeat a variable twice, as the following example shows: -[source,groovy,indent=0] -[subs="verbatim,quotes"] +[source,groovy,indent=0,subs="verbatim,quotes"] ---- String name = "Elvis Presley"; String displayName = (name != null ? name : "Unknown"); @@ -1182,19 +1647,27 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("name?:'Unknown'").getValue(String.class); System.out.println(name); // 'Unknown' ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + + val name = parser.parseExpression("name?:'Unknown'").getValue(String::class.java) + println(name) // 'Unknown' +---- The following listing shows a more complex example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); @@ -1207,14 +1680,27 @@ 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 ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() + + val tesla = Inventor("Nikola Tesla", "Serbian") + var name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java) + println(name) // Nikola Tesla + + tesla.setName(null) + name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java) + println(name) // Elvis Presley +---- [NOTE] ===== You can use the Elvis operator to apply default values in expressions. The following example shows how to use the Elvis operator in a `@Value` expression: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes"] ---- @Value("#{systemProperties['pop3.port'] ?: 25}") ---- @@ -1233,8 +1719,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); @@ -1249,6 +1735,22 @@ 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!!! ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val parser = SpelExpressionParser() + val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() + + val tesla = Inventor("Nikola Tesla", "Serbian") + tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan")) + + var city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java) + println(city) // Smiljan + + tesla.setPlaceOfBirth(null) + city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java) + println(city) // null - does not throw NullPointerException!!! +---- @@ -1262,12 +1764,18 @@ Selection uses a syntax of `.?[selectionExpression]`. It filters the collection returns a new collection that contain a subset of the original elements. For example, selection lets us easily get a list of Serbian inventors, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- List list = (List) parser.parseExpression( "Members.?[Nationality == 'Serbian']").getValue(societyContext); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val list = parser.parseExpression( + "Members.?[Nationality == 'Serbian']").getValue(societyContext) as List +---- Selection is possible upon both lists and maps. For a list, the selection criteria is evaluated against each individual list element. Against a map, the @@ -1278,11 +1786,17 @@ the selection. The following expression returns a new map that consists of those elements of the original map where the entry value is less than 27: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Map newMap = parser.parseExpression("map.?[value<27]").getValue(); ---- +[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 value. To obtain the first entry matching the selection, the syntax is @@ -1300,12 +1814,18 @@ example, suppose we have a list of inventors but want the list of cities where they were born. Effectively, we want to evaluate 'placeOfBirth.city' for every entry in the inventor list. The following example uses projection to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // returns ['Smiljan', 'Idvor' ] List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // returns ['Smiljan', 'Idvor' ] + val placesOfBirth = parser.parseExpression("Members.![placeOfBirth.city]") as List<*> +---- You can also use a map to drive projection and, in this case, the projection expression is evaluated against each entry in the map (represented as a Java `Map.Entry`). The result @@ -1322,8 +1842,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- String randomPhrase = parser.parseExpression( "random number is #{T(java.lang.Math).random()}", @@ -1331,6 +1851,15 @@ shows: // evaluates to "random number is 0.7038186818312008" ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val randomPhrase = parser.parseExpression( + "random number is #{T(java.lang.Math).random()}", + TemplateParserContext()).getValue(String::class.java) + + // 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 @@ -1339,8 +1868,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class TemplateParserContext implements ParserContext { @@ -1357,8 +1886,24 @@ The definition of `TemplateParserContext` follows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class TemplateParserContext : ParserContext { + override fun getExpressionPrefix(): String { + return "#{" + } + override fun getExpressionSuffix(): String { + return "}" + } + + override fun isTemplate(): Boolean { + return true + } + } +---- [[expressions-example-classes]] @@ -1366,9 +1911,8 @@ The definition of `TemplateParserContext` follows: This section lists the classes used in the examples throughout this chapter. -.Inventor.java -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Inventor.Java ---- package org.spring.samples.spel.inventor; @@ -1440,10 +1984,19 @@ This section lists the classes used in the examples throughout this chapter. } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Inventor.kt +---- +class Inventor( + var name: String, + var nationality: String, + var inventions: Array? = null, + var birthdate: Date = GregorianCalendar().time, + var placeOfBirth: PlaceOfBirth? = null) +---- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] .PlaceOfBirth.java -[source,java,indent=0] -[subs="verbatim,quotes"] ---- package org.spring.samples.spel.inventor; @@ -1476,13 +2029,16 @@ This section lists the classes used in the examples throughout this chapter. public void setCountry(String country) { this.country = country; } - } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.PlaceOfBirth.kt +---- + class PlaceOfBirth(var city: String, var country: String? = null) { +---- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] .Society.java -[source,java,indent=0] -[subs="verbatim,quotes"] ---- package org.spring.samples.spel.inventor; @@ -1522,6 +2078,32 @@ This section lists the classes used in the examples throughout this chapter. } return false; } - + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Society.kt +---- + package org.spring.samples.spel.inventor + + import java.util.* + + class Society { + + val Advisors = "advisors" + val President = "president" + + var name: String? = null + + val members = ArrayList() + val officers = mapOf() + + fun isMember(name: String): Boolean { + for (inventor in members) { + if (inventor.name == name) { + return true + } + } + return false + } } ---- diff --git a/src/docs/asciidoc/core/core-resources.adoc b/src/docs/asciidoc/core/core-resources.adoc index ac498e9fe4..17ffdeba8c 100644 --- a/src/docs/asciidoc/core/core-resources.adoc +++ b/src/docs/asciidoc/core/core-resources.adoc @@ -37,8 +37,8 @@ Spring's `Resource` interface is meant to be a more capable interface for abstra access to low-level resources. The following listing shows the `Resource` interface definition: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface Resource extends InputStreamSource { @@ -55,7 +55,27 @@ definition: String getFilename(); String getDescription(); + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface Resource : InputStreamSource { + fun exists(): Boolean + + val isOpen: Boolean + + val url: URL + + val file: File + + @Throws(IOException::class) + fun createRelative(relativePath: String): Resource + + val filename: String + + val description: String } ---- @@ -63,13 +83,20 @@ As the definition of the `Resource` interface shows, it extends the `InputStream interface. The following listing shows the definition of the `InputStreamSource` interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface InputStreamSource { InputStream getInputStream() throws IOException; + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface InputStreamSource { + val inputStream: InputStream } ---- @@ -223,15 +250,22 @@ The `ResourceLoader` interface is meant to be implemented by objects that can re (that is, load) `Resource` instances. The following listing shows the `ResourceLoader` interface definition: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface ResourceLoader { Resource getResource(String location); - } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +interface ResourceLoader { + + fun getResource(location: String): Resource +} +---- All application contexts implement the `ResourceLoader` interface. Therefore, all application contexts may be used to obtain `Resource` instances. @@ -241,11 +275,16 @@ 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 executed against a `ClassPathXmlApplicationContext` instance: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Resource template = ctx.getResource("some/resource/path/myTemplate.txt"); ---- +[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 executed against a `FileSystemXmlApplicationContext` instance, it would return a @@ -259,27 +298,42 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt"); ---- +[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 pair of examples use the `file` and `http` prefixes: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val template = ctx.getResource("file:///some/resource/path/myTemplate.txt") +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt"); ---- +[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: @@ -315,14 +369,22 @@ The `ResourceLoaderAware` interface is a special callback interface which identi components that expect to be provided with a `ResourceLoader` reference. The following listing shows the definition of the `ResourceLoaderAware` interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface ResourceLoaderAware { void setResourceLoader(ResourceLoader resourceLoader); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface ResourceLoaderAware { + + fun setResourceLoader(resourceLoader: ResourceLoader) + } +---- When a class implements `ResourceLoaderAware` and is deployed into an application context (as a Spring-managed bean), it is recognized as `ResourceLoaderAware` by the application @@ -367,8 +429,7 @@ register and use a special JavaBeans `PropertyEditor`, which can convert `String to `Resource` objects. So, if `myBean` has a template property of type `Resource`, it can be configured with a simple string for that resource, as the following example shows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -384,14 +445,12 @@ If you need to force a specific `Resource` type to be used, you can use a prefix The following two examples show how to force a `ClassPathResource` and a `UrlResource` (the latter being used to access a filesystem file): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -419,21 +478,31 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml"); ---- +[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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val ctx = FileSystemXmlApplicationContext("conf/appContext.xml") +---- Now the bean definition is loaded from a filesystem location (in this case, relative to the current working directory). @@ -442,12 +511,17 @@ Note that the use of the special classpath prefix or a standard URL prefix on th location path overrides the default type of `Resource` created to load the definition. Consider the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml"); ---- +[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 `ResourceLoader`, any @@ -465,8 +539,7 @@ then derives the path information from the supplied class. Consider the following directory layout: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- com/ foo/ @@ -478,12 +551,17 @@ com/ The following example shows how a `ClassPathXmlApplicationContext` instance composed of the beans defined in files named `services.xml` and `daos.xml` (which are on the classpath) can be instantiated: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {"services.xml", "daos.xml"}, MessengerService.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "daos.xml"), MessengerService::class.java) +---- See the {api-spring-framework}/jca/context/SpringContextResourceAdapter.html[`ClassPathXmlApplicationContext`] javadoc for details on the various constructors. @@ -516,8 +594,7 @@ a resource points to just one resource at a time. Path locations can contain Ant-style patterns, as the following example shows: -[literal] -[subs="verbatim"] +[literal,subs="verbatim,quotes"] ---- /WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml @@ -562,12 +639,17 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml"); ---- +[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 @@ -625,16 +707,14 @@ Ant-style patterns with `classpath:` resources are not guaranteed to find matchi resources if the root package to search is available in multiple class path locations. Consider the following example of a resource location: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- com/mycompany/package1/service-context.xml ---- Now consider an Ant-style path that someone might use to try to find that file: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- classpath:com/mycompany/**/service-context.xml ---- @@ -663,53 +743,87 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/context.xml"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val ctx = FileSystemXmlApplicationContext("conf/context.xml") +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ApplicationContext ctx = new FileSystemXmlApplicationContext("/conf/context.xml"); ---- +[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): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- FileSystemXmlApplicationContext ctx = ...; ctx.getResource("some/resource/path/myTemplate.txt"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val ctx: FileSystemXmlApplicationContext = ... + ctx.getResource("some/resource/path/myTemplate.txt") +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- FileSystemXmlApplicationContext ctx = ...; ctx.getResource("/some/resource/path/myTemplate.txt"); ---- +[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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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"); ---- +[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") +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[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"); ---- +[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/src/docs/asciidoc/core/core-validation.adoc b/src/docs/asciidoc/core/core-validation.adoc index 9d48a4620a..7b34fe6409 100644 --- a/src/docs/asciidoc/core/core-validation.adoc +++ b/src/docs/asciidoc/core/core-validation.adoc @@ -54,8 +54,8 @@ validators can report validation failures to the `Errors` object. Consider the following example of a small data object: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Person { @@ -65,6 +65,11 @@ Consider the following example of a small data object: // the usual getters and setters... } ---- +[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: @@ -77,13 +82,13 @@ 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: -[source,java,indent=0] -[subs="verbatim"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class PersonValidator implements Validator { /** - * This Validator validates *only* Person instances + * This Validator validates only Person instances */ public boolean supports(Class clazz) { return Person.class.equals(clazz); @@ -100,6 +105,29 @@ example implements `Validator` for `Person` instances: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class PersonValidator : Validator { + + /** + * This Validator validates only Person instances + */ + override fun supports(clazz: Class<*>): Boolean { + return Person::class.java == clazz + } + + override fun validate(obj: Any, e: Errors) { + ValidationUtils.rejectIfEmpty(e, "name", "name.empty") + val p = obj as Person + if (p.age < 0) { + e.rejectValue("age", "negativevalue") + } else if (p.age > 110) { + e.rejectValue("age", "too.darn.old") + } + } + } +---- 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 @@ -117,8 +145,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class CustomerValidator implements Validator { @@ -156,6 +184,40 @@ as the following example shows: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class CustomerValidator(private val addressValidator: Validator) : Validator { + + init { + if (addressValidator == null) { + throw IllegalArgumentException("The supplied [Validator] is required and must not be null.") + } + if (!addressValidator.supports(Address::class.java)) { + throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.") + } + } + + /** + * This Validator validates Customer instances, and any subclasses of Customer too + */ + override fun supports(clazz: Class<*>): Boolean { + return Customer::class.java.isAssignableFrom(clazz) + } + + override fun validate(target: Any, errors: Errors) { + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required") + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required") + val customer = target as Customer + try { + errors.pushNestedPath("address") + ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors) + } finally { + errors.popNestedPath() + } + } + } +---- 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 @@ -259,8 +321,8 @@ and their default implementations, you should skip ahead to the The following two example classes use the `BeanWrapper` to get and set properties: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Company { @@ -284,9 +346,17 @@ properties: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class Company { + var name: String? = null + var managingDirector: Employee? = null + } +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class Employee { @@ -311,12 +381,20 @@ properties: } } ---- +[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 `Companies` and `Employees`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- BeanWrapper company = new BeanWrapperImpl(new Company()); // setting the company name.. @@ -333,6 +411,24 @@ the properties of instantiated `Companies` and `Employees`: // retrieving the salary of the managingDirector through the company Float salary = (Float) company.getPropertyValue("managingDirector.salary"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val company = BeanWrapperImpl(Company()) + // setting the company name.. + company.setPropertyValue("name", "Some Company Inc.") + // ... can also be done like this: + val value = PropertyValue("name", "Some Company Inc.") + company.setPropertyValue(value) + + // ok, let's create the director and tie it to the company: + val jim = BeanWrapperImpl(Employee()) + jim.setPropertyValue("name", "Jim Stravinsky") + company.setPropertyValue("managingDirector", jim.wrappedInstance) + + // retrieving the salary of the managingDirector through the company + val salary = company.getPropertyValue("managingDirector.salary") as Float? +---- @@ -443,8 +539,7 @@ name as that class, with `Editor` appended. For example, one could have the foll class and package structure, which would be sufficient for the `SomethingEditor` class to be recognized and used as the `PropertyEditor` for `Something`-typed properties. -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- com chank @@ -460,8 +555,7 @@ here]). The following example use the `BeanInfo` mechanism to explicitly register one or more `PropertyEditor` instances with the properties of an associated class: -[literal] -[subs="verbatim,quotes"] +[literal,subs="verbatim,quotes"] ---- com chank @@ -473,8 +567,8 @@ com The following Java source code for the referenced `SomethingBeanInfo` class associates a `CustomNumberEditor` with the `age` property of the `Something` class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class SomethingBeanInfo extends SimpleBeanInfo { @@ -494,6 +588,27 @@ associates a `CustomNumberEditor` with the `age` property of the `Something` cla } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class SomethingBeanInfo : SimpleBeanInfo() { + + override fun getPropertyDescriptors(): Array { + try { + val numberPE = CustomNumberEditor(Int::class.java, true) + val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) { + override fun createPropertyEditor(bean: Any): PropertyEditor { + return numberPE + } + } + return arrayOf(ageDescriptor) + } catch (ex: IntrospectionException) { + throw Error(ex.toString()) + } + + } + } +---- [[beans-beans-conversion-customeditor-registration]] @@ -533,8 +648,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package example; @@ -556,13 +671,24 @@ another class called `DependsOnExoticType`, which needs `ExoticType` set as a pr } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package example + + class ExoticType(val name: String) + + class DependsOnExoticType { + + 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 `ExoticType` instance. The following bean definition shows how to set up this relationship: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -571,8 +697,8 @@ string, which a `PropertyEditor` converts into an actual The `PropertyEditor` implementation could look similar to the following: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // converts string representation to ExoticType object package example; @@ -584,12 +710,26 @@ The `PropertyEditor` implementation could look similar to the following: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // converts string representation to ExoticType object + package example + + import java.beans.PropertyEditorSupport + + class ExoticTypeEditor : PropertyEditorSupport() { + + override fun setAsText(text: String) { + value = ExoticType(text.toUpperCase()) + } + } +---- 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: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -620,8 +760,8 @@ instances for each bean creation attempt. The following example shows how to create your own `PropertyEditorRegistrar` implementation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.foo.editors.spring; @@ -636,6 +776,25 @@ The following example shows how to create your own `PropertyEditorRegistrar` imp } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.foo.editors.spring + + import org.springframework.beans.PropertyEditorRegistrar + import org.springframework.beans.PropertyEditorRegistry + + class CustomPropertyEditorRegistrar : PropertyEditorRegistrar { + + override fun registerCustomEditors(registry: PropertyEditorRegistry) { + + // it is expected that new PropertyEditor instances are created + registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor()) + + // you could register as many custom property editors as are required here... + } + } +---- See also the `org.springframework.beans.support.ResourceEditorRegistrar` for an example `PropertyEditorRegistrar` implementation. Notice how in its implementation of the @@ -644,8 +803,7 @@ See also the `org.springframework.beans.support.ResourceEditorRegistrar` for an The next example shows how to configure a `CustomEditorConfigurer` and inject an instance of our `CustomPropertyEditorRegistrar` into it: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -665,8 +823,8 @@ conjunction with data-binding `Controllers` (such as `SimpleFormController`) can convenient. The following example uses a `PropertyEditorRegistrar` in the implementation of an `initBinder(..)` method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public final class RegisterUserController extends SimpleFormController { @@ -684,6 +842,20 @@ implementation of an `initBinder(..)` method: // other methods to do with registering a User } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class RegisterUserController( + private val customPropertyEditorRegistrar: PropertyEditorRegistrar) : SimpleFormController() { + + protected fun initBinder(request: HttpServletRequest, + binder: ServletRequestDataBinder) { + this.customPropertyEditorRegistrar.registerCustomEditors(binder) + } + + // other methods to do with registering a User + } +---- This style of `PropertyEditor` registration can lead to concise code (the implementation of `initBinder(..)` is only one line long) and lets common `PropertyEditor` @@ -711,8 +883,8 @@ application where type conversion is needed. The SPI to implement type conversion logic is simple and strongly typed, as the following interface definition shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.core.convert.converter; @@ -721,6 +893,16 @@ interface definition shows: T convert(S source); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.core.convert.converter + + interface Converter { + + fun convert(source: S): T + } +---- To create your own converter, implement the `Converter` interface and parameterize `S` as the type you are converting from and `T` as the type you are converting to. You can also transparently apply such a @@ -737,8 +919,8 @@ Several converter implementations are provided in the `core.convert.support` pac a convenience. These include converters from strings to numbers and other common types. The following listing shows the `StringToInteger` class, which is a typical `Converter` implementation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.core.convert.support; @@ -749,6 +931,20 @@ The following listing shows the `StringToInteger` class, which is a typical `Con } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.core.convert.support + + import org.springframework.core.convert.converter.Converter + + internal class StringToInteger : Converter { + + override fun convert(source: String): Int? { + return Integer.valueOf(source) + } + } +---- @@ -759,8 +955,8 @@ When you need to centralize the conversion logic for an entire class hierarchy (for example, when converting from `String` to `Enum` objects), you can implement `ConverterFactory`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.core.convert.converter; @@ -769,6 +965,16 @@ When you need to centralize the conversion logic for an entire class hierarchy Converter getConverter(Class targetType); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.core.convert.converter + + interface ConverterFactory { + + fun getConverter(targetType: Class): Converter + } +---- Parameterize S to be the type you are converting from and R to be the base type defining the __range__ of classes you can convert to. Then implement `getConverter(Class)`, @@ -776,8 +982,8 @@ where T is a subclass of R. Consider the `StringToEnumConverterFactory` as an example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes"] +.Java ---- package org.springframework.core.convert.support; @@ -803,7 +1009,6 @@ Consider the `StringToEnumConverterFactory` as an example: ---- - [[core-convert-GenericConverter-SPI]] === Using `GenericConverter` @@ -815,8 +1020,8 @@ context that you can use when you implement your conversion logic. Such context type conversion be driven by a field annotation or by generic information declared on a field signature. The following listing shows the interface definition of `GenericConverter`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.core.convert.converter; @@ -827,6 +1032,18 @@ field signature. The following listing shows the interface definition of `Generi Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.core.convert.converter + + interface GenericConverter { + + fun getConvertibleTypes(): Set? + + fun convert(@Nullable source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any? + } +---- To implement a `GenericConverter`, have `getConvertibleTypes()` return the supported source->target type pairs. Then implement `convert(Object, TypeDescriptor, @@ -855,8 +1072,8 @@ on the target field, or you might want to run a `Converter` only if a specific m `ConditionalGenericConverter` is the union of the `GenericConverter` and `ConditionalConverter` interfaces that lets you define such custom matching criteria: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface ConditionalConverter { @@ -866,6 +1083,16 @@ on the target field, or you might want to run a `Converter` only if a specific m public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface ConditionalConverter { + + fun matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean + } + + interface ConditionalGenericConverter : GenericConverter, ConditionalConverter +---- A good example of a `ConditionalGenericConverter` is an `EntityConverter` that converts between a persistent entity identifier and an entity reference. Such an `EntityConverter` @@ -881,8 +1108,8 @@ might match only if the target entity type declares a static finder method (for `ConversionService` defines a unified API for executing type conversion logic at runtime. Converters are often executed behind the following facade interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.core.convert; @@ -898,6 +1125,23 @@ runtime. Converters are often executed behind the following facade interface: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.core.convert + + interface ConversionService { + + fun canConvert(sourceType: Class<*>, targetType: Class<*>): Boolean + + fun convert(source: Any, targetType: Class): T + + fun canConvert(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean + + fun convert(source: Any, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any + + } +---- Most `ConversionService` implementations also implement `ConverterRegistry`, which provides an SPI for registering converters. Internally, a `ConversionService` @@ -926,8 +1170,7 @@ system is used. To register a default `ConversionService` with Spring, add the following bean definition with an `id` of `conversionService`: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -938,8 +1181,7 @@ maps, and other common types. To supplement or override the default converters w own custom converters, set the `converters` property. Property values can implement any of the `Converter`, `ConverterFactory`, or `GenericConverter` interfaces. -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -965,13 +1207,12 @@ In certain situations, you may wish to apply formatting during conversion. See 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Service public class MyService { - @Autowired public MyService(ConversionService conversionService) { this.conversionService = conversionService; } @@ -981,6 +1222,17 @@ it like you would for any other bean. The following example shows how to do so: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Service + class MyService(private val conversionService: ConversionService) { + + fun doIt() { + conversionService.convert(...) + } + } +---- 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. @@ -990,16 +1242,26 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- DefaultConversionService cs = new DefaultConversionService(); - List input = .... + List input = ... cs.convert(input, TypeDescriptor.forObject(input), // List type descriptor TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class))); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val cs = DefaultConversionService() + + val input: List = ... + cs.convert(input, + 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 @@ -1048,8 +1310,8 @@ provides a unified type conversion API for both SPIs. The `Formatter` SPI to implement field formatting logic is simple and strongly typed. The following listing shows the `Formatter` interface definition: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.format; @@ -1060,17 +1322,25 @@ following listing shows the `Formatter` interface definition: `Formatter` extends from the `Printer` and `Parser` building-block interfaces. The following listing shows the definitions of those two interfaces: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public interface Printer { String print(T fieldValue, Locale locale); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface Printer { -[source,java,indent=0] -[subs="verbatim,quotes"] + fun print(fieldValue: T, locale: Locale): String + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import java.text.ParseException; @@ -1079,6 +1349,15 @@ following listing shows the definitions of those two interfaces: T parse(String clientValue, Locale locale) throws ParseException; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + interface Parser { + + @Throws(ParseException::class) + fun parse(clientValue: String, locale: Locale): T + } +---- To create your own `Formatter`, implement the `Formatter` interface shown earlier. Parameterize `T` to be the type of object you wish to format -- for example, @@ -1097,8 +1376,8 @@ formatting support based on the https://www.joda.org/joda-time/[Joda-Time librar The following `DateFormatter` is an example `Formatter` implementation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.format.datetime; @@ -1131,6 +1410,25 @@ The following `DateFormatter` is an example `Formatter` implementation: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class DateFormatter(private val pattern: String) : Formatter { + + override fun print(date: Date, locale: Locale) + = getDateFormat(locale).format(date) + + @Throws(ParseException::class) + override fun parse(formatted: String, locale: Locale) + = getDateFormat(locale).parse(formatted) + + protected fun getDateFormat(locale: Locale): DateFormat { + val dateFormat = SimpleDateFormat(this.pattern, locale) + dateFormat.isLenient = false + return dateFormat + } + } +---- The Spring team welcomes community-driven `Formatter` contributions. See https://github.com/spring-projects/spring-framework/issues[GitHub Issues] to contribute. @@ -1144,8 +1442,8 @@ Field formatting can be configured by field type or annotation. To bind an annotation to a `Formatter`, implement `AnnotationFormatterFactory`. The following listing shows the definition of the `AnnotationFormatterFactory` interface: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.format; @@ -1158,6 +1456,20 @@ listing shows the definition of the `AnnotationFormatterFactory` interface: Parser getParser(A annotation, Class fieldType); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.format + + interface AnnotationFormatterFactory { + + val fieldTypes: Set> + + fun getPrinter(annotation: A, fieldType: Class<*>): Printer<*> + + fun getParser(annotation: A, fieldType: Class<*>): Parser<*> + } +---- To create an implementation: . Parameterize A to be the field `annotationType` with which you wish to associate @@ -1170,8 +1482,8 @@ The following example `AnnotationFormatterFactory` implementation binds the `@Nu annotation to a formatter to let a number style or pattern be specified: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public final class NumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory { @@ -1206,12 +1518,43 @@ specified: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory { + + override fun getFieldTypes(): Set> { + return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java) + } + + override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer { + return configureFormatterFrom(annotation, fieldType) + } + + override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser { + return configureFormatterFrom(annotation, fieldType) + } + + private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter { + return if (annotation.pattern.isNotEmpty()) { + NumberStyleFormatter(annotation.pattern) + } else { + val style = annotation.style + when { + style === NumberFormat.Style.PERCENT -> PercentStyleFormatter() + style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter() + else -> NumberStyleFormatter() + } + } + } + } +---- To trigger formatting, you can annotate fields with @NumberFormat, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyModel { @@ -1219,7 +1562,13 @@ example shows: private BigDecimal decimal; } ---- - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyModel( + @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal + ) +---- [[format-annotations-api]] @@ -1233,8 +1582,8 @@ 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): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class MyModel { @@ -1242,7 +1591,13 @@ The following example uses `@DateTimeFormat` to format a `java.util.Date` as an private Date date; } ---- - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyModel( + @DateTimeFormat(iso= ISO.DATE) private val date: Date + ) +---- [[format-FormatterRegistry-SPI]] @@ -1257,8 +1612,8 @@ for use with Spring's `DataBinder` and the Spring Expression Language (SpEL). The following listing shows the `FormatterRegistry` SPI: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.format; @@ -1270,7 +1625,23 @@ The following listing shows the `FormatterRegistry` SPI: void addFormatterForFieldType(Formatter formatter); - void addFormatterForAnnotation(AnnotationFormatterFactory factory); + void addFormatterForAnnotation(AnnotationFormatterFactory factory); + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.format + + interface FormatterRegistry : ConverterRegistry { + + fun addFormatterForFieldType(fieldType: Class<*>, printer: Printer<*>, parser: Parser<*>) + + fun addFormatterForFieldType(fieldType: Class<*>, formatter: Formatter<*>) + + fun addFormatterForFieldType(formatter: Formatter<*>) + + fun addFormatterForAnnotation(factory: AnnotationFormatterFactory<*>) } ---- @@ -1290,8 +1661,8 @@ these rules once, and they are applied whenever formatting is needed. `FormatterRegistrar` is an SPI for registering formatters and converters through the FormatterRegistry. The following listing shows its interface definition: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package org.springframework.format; @@ -1300,6 +1671,16 @@ FormatterRegistry. The following listing shows its interface definition: void registerFormatters(FormatterRegistry registry); } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package org.springframework.format + + interface FormatterRegistrar { + + fun registerFormatters(registry: FormatterRegistry) + } +---- A `FormatterRegistrar` is useful when registering multiple related converters and formatters for a given formatting category, such as date formatting. It can also be @@ -1334,8 +1715,8 @@ you use the Joda-Time library. For example, the following Java configuration registers a global `yyyyMMdd` format (this example does not depend on the Joda-Time library): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration public class AppConfig { @@ -1358,13 +1739,32 @@ format (this example does not depend on the Joda-Time library): } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class AppConfig { + + @Bean + fun conversionService(): FormattingConversionService { + // Use the DefaultFormattingConversionService but do not register defaults + return DefaultFormattingConversionService(false).apply { + // Ensure @NumberFormat is still supported + addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory()) + // Register date conversion with a specific global format + val registrar = DateFormatterRegistrar() + registrar.setFormatter(DateFormatter("yyyyMMdd")) + registrar.registerFormatters(this) + } + } + } +---- If you prefer XML-based configuration, you can use a `FormattingConversionServiceFactoryBean`. The following example shows how to do so (this time using Joda Time): -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -1501,8 +1918,8 @@ these interfaces into beans that need to invoke validation logic. You can inject a reference to `javax.validation.Validator` if you prefer to work with the Bean Validation API directly, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import javax.validation.Validator; @@ -1511,13 +1928,22 @@ Validation API directly, as the following example shows: @Autowired private Validator validator; + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import javax.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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import org.springframework.validation.Validator; @@ -1528,6 +1954,14 @@ the Spring Validation API, as the following example shows: private Validator validator; } ---- +[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]] @@ -1552,8 +1986,8 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @@ -1561,9 +1995,17 @@ The following example shows a custom `@Constraint` declaration followed by an as public @interface MyConstraint { } ---- +[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 +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import javax.validation.ConstraintValidator; @@ -1572,9 +2014,20 @@ The following example shows a custom `@Constraint` declaration followed by an as @Autowired; private Foo aDependency; - ... + // ... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import javax.validation.ConstraintValidator + + class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator { + + // ... + } +---- + As the preceding example shows, a `ConstraintValidator` implementation can have its dependencies `@Autowired` as any other Spring bean. @@ -1587,8 +2040,7 @@ You can integrate the method validation feature supported by Bean Validation 1.1 extension, also by Hibernate Validator 4.3) into a Spring context through a `MethodValidationPostProcessor` bean definition, as follows: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- @@ -1620,8 +2072,8 @@ configured, you can invoke the `Validator` by calling `binder.validate()`. Any v The following example shows how to use a `DataBinder` programmatically to invoke validation logic after binding to a target object: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Foo target = new Foo(); DataBinder binder = new DataBinder(target); @@ -1636,6 +2088,22 @@ logic after binding to a target object: // get BindingResult that includes any validation errors BindingResult results = binder.getBindingResult(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val target = Foo() + val binder = DataBinder(target) + binder.validator = FooValidator() + + // bind to the target object + binder.bind(propertyValues) + + // validate the target object + binder.validate() + + // 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