mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68537d6d5b | |||
| 57b838ddda | |||
| 8c784085d2 | |||
| c0a1e1718e | |||
| fe29e734ae | |||
| 50c3a62589 | |||
| a936a6a8ce | |||
| 8d112b8514 | |||
| f8cb0fa2a0 | |||
| c56c16d7ba | |||
| df1f8139cc | |||
| 9624ea392a | |||
| 682a4d5353 | |||
| 4419d56178 | |||
| 244c97993b | |||
| 9d28fe90f5 | |||
| d2a4ac519c | |||
| dbbebf541d | |||
| fe73c630da | |||
| 3456fd054f | |||
| 5fd75dd27b | |||
| 6c29a5779e | |||
| e9413b93c6 | |||
| 4cc02fe3bc | |||
| b1cf832c28 | |||
| 34e5ce9360 | |||
| 375114defa | |||
| 00c2c1d2a1 | |||
| 7c9fc575ff | |||
| c859211f7a | |||
| 4dbe9d6709 | |||
| acedbfbaba | |||
| 5d6f151031 | |||
| a09495d4e9 | |||
| 5cab6a1f3a | |||
| a02a017e6e | |||
| 9f2f93129b | |||
| 475ac6ef5d | |||
| 79f43041ad | |||
| edb4a3467a | |||
| 024d02225c | |||
| 9305a64a50 | |||
| 40672c3715 | |||
| 7c50464bba | |||
| 0d10d4beee | |||
| f60bec986f | |||
| 6825a842b5 | |||
| 7ace9aa429 | |||
| 8979ac789f | |||
| 9cb4c5565a | |||
| 08f38c52c7 | |||
| afa936e985 | |||
| 3677d3597b | |||
| 76bc7deb8e | |||
| 6c3cb5d2e0 | |||
| 3ce71932c0 | |||
| 3a9c7524f0 | |||
| ce66b251ab | |||
| 626a7fc52a | |||
| 3d8455b257 | |||
| fd38c23699 | |||
| e47418c948 | |||
| d6de374424 | |||
| b437b7be34 | |||
| 2d56505ea9 | |||
| 8a44b6445d | |||
| 6d24e62e83 | |||
| 2d62be8590 | |||
| 2e1374b459 | |||
| bb6150e44e | |||
| 38a4f23f16 | |||
| 6739ca82ce | |||
| 88e6544d9d | |||
| 1999c78350 |
+1
-1
@@ -29,7 +29,7 @@ spring-test/test-output/
|
||||
|
||||
# Maven artifacts
|
||||
pom.xml
|
||||
target/
|
||||
/target/
|
||||
|
||||
# Eclipse artifacts, including WTP generated manifests
|
||||
bin
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ javadoc {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.4"
|
||||
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Spring Portfolio
|
||||
:docs-site: https://docs.spring.io
|
||||
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
|
||||
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
|
||||
:docs-spring-security: {docs-site}/spring-security/reference
|
||||
// spring-asciidoctor-backends Settings
|
||||
:chomp: default headers packages
|
||||
:fold: all
|
||||
:docs-site: https://docs.spring.io
|
||||
// Spring Framework
|
||||
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
|
||||
:api-spring-framework: {docs-spring-framework}/javadoc-api/org/springframework
|
||||
@@ -8,10 +13,6 @@
|
||||
:docs-kotlin: {docdir}/../../main/kotlin/org/springframework/docs
|
||||
:docs-resources: {docdir}/../../main/resources
|
||||
:spring-framework-main-code: https://github.com/spring-projects/spring-framework/tree/main
|
||||
// Spring portfolio Links
|
||||
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
|
||||
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
|
||||
:docs-spring-security: {docs-site}/spring-security/reference
|
||||
// Third-party Links
|
||||
:docs-graalvm: https://www.graalvm.org/22.3/reference-manual
|
||||
:gh-rsocket: https://github.com/rsocket
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,9 @@ Applying such optimizations early implies the following restrictions:
|
||||
* The classpath is fixed and fully defined at build time.
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
|
||||
** Environment properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) can't be transformed Ahead of Time (see https://github.com/spring-projects/spring-framework/issues/29555[spring-framework#29555] related issue)
|
||||
* The return type of methods annotated with `@Bean` should be the most specific one in order to allow proper hint inference (typically the concrete class, not an interface).
|
||||
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time (see related https://github.com/spring-projects/spring-framework/issues/29555[spring-framework#29555] issue).
|
||||
* The return type of methods annotated with `@Bean` should be the most specific type possible (typically the concrete class, not an interface) in order to support proper type inference without invoking the corresponding `@Bean` method at build time.
|
||||
|
||||
When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets.
|
||||
A Spring AOT processed application typically generates:
|
||||
|
||||
@@ -123,7 +123,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package jakarta.persistence;
|
||||
@@ -134,7 +134,7 @@ The following example enumeration shows how easy injecting an enum value is:
|
||||
EXTENDED
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package jakarta.persistence
|
||||
@@ -148,7 +148,7 @@ The following example enumeration shows how easy injecting an enum value is:
|
||||
|
||||
Now consider the following setter of type `PersistenceContextType` and the corresponding bean definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package example;
|
||||
@@ -162,7 +162,7 @@ Now consider the following setter of type `PersistenceContextType` and the corre
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package example
|
||||
@@ -796,7 +796,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
@@ -810,7 +810,7 @@ use the `NamespaceHandlerSupport` class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
@@ -847,7 +847,7 @@ 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,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
@@ -884,7 +884,7 @@ the basic grunt work of creating a single `BeanDefinition`.
|
||||
<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our
|
||||
single `BeanDefinition` represents.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
@@ -1056,7 +1056,7 @@ setter method for the `components` property. This makes it hard (or rather impos
|
||||
to configure a bean definition for the `Component` class by using setter injection.
|
||||
The following listing shows the `Component` class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1087,7 +1087,7 @@ The following listing shows the `Component` class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1114,7 +1114,7 @@ The typical solution to this issue is to create a custom `FactoryBean` that expo
|
||||
setter property for the `components` property. The following listing shows such a custom
|
||||
`FactoryBean`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1154,7 +1154,7 @@ setter property for the `components` property. The following listing shows such
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1226,7 +1226,7 @@ listing shows:
|
||||
Again following <<core.appendix.xsd-custom-introduction, the process described earlier>>,
|
||||
we then create a custom `NamespaceHandler`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1240,7 +1240,7 @@ we then create a custom `NamespaceHandler`:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1259,7 +1259,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1308,7 +1308,7 @@ listing shows our custom `BeanDefinitionParser` implementation:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1403,14 +1403,14 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
public class JCacheInitializer {
|
||||
|
||||
private String name;
|
||||
private final String name;
|
||||
|
||||
public JCacheInitializer(String name) {
|
||||
this.name = name;
|
||||
@@ -1421,7 +1421,7 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1453,7 +1453,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1469,7 +1469,7 @@ Next, we need to create the associated `NamespaceHandler`, as follows:
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1490,7 +1490,7 @@ Next, we need to create the parser. Note that, in this case, because we are goin
|
||||
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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1544,7 +1544,7 @@ The following listing shows our `BeanDefinitionDecorator` implementation:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -949,7 +949,7 @@ order in which the constructor arguments are defined in a bean definition is the
|
||||
in which those arguments are supplied to the appropriate constructor when the bean is
|
||||
being instantiated. Consider the following class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y;
|
||||
@@ -961,7 +961,7 @@ being instantiated. Consider the following class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y
|
||||
@@ -993,7 +993,7 @@ case with the preceding example). When a simple type is used, such as
|
||||
`<value>true</value>`, 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package examples;
|
||||
@@ -1012,7 +1012,7 @@ by type without help. Consider the following class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package examples
|
||||
@@ -1077,7 +1077,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package examples;
|
||||
@@ -1093,7 +1093,7 @@ then have to look as follows:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package examples
|
||||
@@ -2276,10 +2276,9 @@ and by <<beans-factory-client,making a `getBean("B")` call to the container>> 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
|
||||
.Java
|
||||
----
|
||||
// a class that uses a stateful Command-style class to perform some processing
|
||||
package fiona.apple;
|
||||
|
||||
// Spring-API imports
|
||||
@@ -2287,6 +2286,10 @@ shows this approach:
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* A class that uses a stateful Command-style class to perform
|
||||
* some processing.
|
||||
*/
|
||||
public class CommandManager implements ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
@@ -2310,16 +2313,17 @@ shows this approach:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
|
||||
.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
|
||||
|
||||
// A class that uses a stateful Command-style class to perform
|
||||
// some processing.
|
||||
class CommandManager : ApplicationContextAware {
|
||||
|
||||
private lateinit var applicationContext: ApplicationContext
|
||||
@@ -2382,7 +2386,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
|
||||
.Java
|
||||
----
|
||||
package fiona.apple;
|
||||
@@ -2403,7 +2407,7 @@ the reworked example shows:
|
||||
protected abstract Command createCommand();
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
|
||||
.Kotlin
|
||||
----
|
||||
package fiona.apple
|
||||
@@ -4214,7 +4218,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package scripting;
|
||||
@@ -4234,9 +4238,11 @@ The following listing shows the custom `BeanPostProcessor` implementation class
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package scripting
|
||||
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor
|
||||
|
||||
class InstantiationTracingBeanPostProcessor : BeanPostProcessor {
|
||||
|
||||
@@ -1978,7 +1978,7 @@ The definition of `TemplateParserContext` follows:
|
||||
|
||||
This section lists the classes used in the examples throughout this chapter.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Inventor.Java
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
@@ -2051,18 +2051,20 @@ This section lists the classes used in the examples throughout this chapter.
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Inventor.kt
|
||||
----
|
||||
class Inventor(
|
||||
var name: String,
|
||||
var nationality: String,
|
||||
var inventions: Array<String>? = null,
|
||||
var birthdate: Date = GregorianCalendar().time,
|
||||
var placeOfBirth: PlaceOfBirth? = null)
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
class Inventor(
|
||||
var name: String,
|
||||
var nationality: String,
|
||||
var inventions: Array<String>? = null,
|
||||
var birthdate: Date = GregorianCalendar().time,
|
||||
var placeOfBirth: PlaceOfBirth? = null)
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.PlaceOfBirth.java
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
@@ -2098,13 +2100,15 @@ class Inventor(
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.PlaceOfBirth.kt
|
||||
----
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
class PlaceOfBirth(var city: String, var country: String? = null) {
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Society.java
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
@@ -2147,7 +2151,7 @@ class Inventor(
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Society.kt
|
||||
----
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
@@ -640,7 +640,7 @@ 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",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package example;
|
||||
@@ -663,7 +663,7 @@ another class called `DependsOnExoticType`, which needs `ExoticType` set as a pr
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package example
|
||||
@@ -689,12 +689,14 @@ string, which a `PropertyEditor` converts into an actual
|
||||
|
||||
The `PropertyEditor` implementation could look similar to the following:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// converts string representation to ExoticType object
|
||||
package example;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
|
||||
// converts string representation to ExoticType object
|
||||
public class ExoticTypeEditor extends PropertyEditorSupport {
|
||||
|
||||
public void setAsText(String text) {
|
||||
@@ -702,14 +704,14 @@ The `PropertyEditor` implementation could look similar to the following:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// converts string representation to ExoticType object
|
||||
package example
|
||||
|
||||
import java.beans.PropertyEditorSupport
|
||||
|
||||
// converts string representation to ExoticType object
|
||||
class ExoticTypeEditor : PropertyEditorSupport() {
|
||||
|
||||
override fun setAsText(text: String) {
|
||||
@@ -752,7 +754,7 @@ instances for each bean creation attempt.
|
||||
|
||||
The following example shows how to create your own `PropertyEditorRegistrar` implementation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo.editors.spring;
|
||||
@@ -768,7 +770,7 @@ The following example shows how to create your own `PropertyEditorRegistrar` imp
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo.editors.spring
|
||||
@@ -876,7 +878,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.converter;
|
||||
|
||||
@@ -901,7 +903,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
@@ -922,7 +924,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.converter;
|
||||
|
||||
@@ -938,7 +940,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
@@ -975,7 +977,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.converter;
|
||||
|
||||
@@ -1039,7 +1041,7 @@ 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 run behind the following facade interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert;
|
||||
|
||||
@@ -1223,7 +1225,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1268,7 +1270,7 @@ a `java.text.DateFormat`.
|
||||
|
||||
The following `DateFormatter` is an example `Formatter` implementation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package org.springframework.format.datetime;
|
||||
@@ -1302,7 +1304,7 @@ The following `DateFormatter` is an example `Formatter` implementation:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
class DateFormatter(private val pattern: String) : Formatter<Date> {
|
||||
@@ -1334,7 +1336,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1350,15 +1352,14 @@ listing shows the definition of the `AnnotationFormatterFactory` interface:
|
||||
|
||||
To create an implementation:
|
||||
|
||||
. Parameterize A to be the field `annotationType` with which you wish to associate
|
||||
. Parameterize `A` to be the field `annotationType` with which you wish to associate
|
||||
formatting logic -- for example `org.springframework.format.annotation.DateTimeFormat`.
|
||||
. Have `getFieldTypes()` return the types of fields on which the annotation can be used.
|
||||
. Have `getPrinter()` return a `Printer` to print the value of an annotated field.
|
||||
. Have `getParser()` return a `Parser` to parse a `clientValue` for an annotated field.
|
||||
|
||||
The following example `AnnotationFormatterFactory` implementation binds the `@NumberFormat`
|
||||
annotation to a formatter to let a number style or pattern be
|
||||
specified:
|
||||
annotation to a formatter to let a number style or pattern be specified:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
@@ -1366,10 +1367,12 @@ specified:
|
||||
public final class NumberFormatAnnotationFormatterFactory
|
||||
implements AnnotationFormatterFactory<NumberFormat> {
|
||||
|
||||
private static final Set<Class<?>> FIELD_TYPES = Set.of(Short.class,
|
||||
Integer.class, Long.class, Float.class, Double.class,
|
||||
BigDecimal.class, BigInteger.class);
|
||||
|
||||
public Set<Class<?>> getFieldTypes() {
|
||||
return new HashSet<Class<?>>(asList(new Class<?>[] {
|
||||
Short.class, Integer.class, Long.class, Float.class,
|
||||
Double.class, BigDecimal.class, BigInteger.class }));
|
||||
return FIELD_TYPES;
|
||||
}
|
||||
|
||||
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
|
||||
@@ -1383,16 +1386,13 @@ specified:
|
||||
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
|
||||
if (!annotation.pattern().isEmpty()) {
|
||||
return new NumberStyleFormatter(annotation.pattern());
|
||||
} else {
|
||||
Style style = annotation.style();
|
||||
if (style == Style.PERCENT) {
|
||||
return new PercentStyleFormatter();
|
||||
} else if (style == Style.CURRENCY) {
|
||||
return new CurrencyStyleFormatter();
|
||||
} else {
|
||||
return new NumberStyleFormatter();
|
||||
}
|
||||
}
|
||||
// else
|
||||
return switch(annotation.style()) {
|
||||
case Style.PERCENT -> new PercentStyleFormatter();
|
||||
case Style.CURRENCY -> new CurrencyStyleFormatter();
|
||||
default -> new NumberStyleFormatter();
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -1428,7 +1428,7 @@ specified:
|
||||
}
|
||||
----
|
||||
|
||||
To trigger formatting, you can annotate fields with @NumberFormat, as the following
|
||||
To trigger formatting, you can annotate fields with `@NumberFormat`, as the following
|
||||
example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
@@ -1490,7 +1490,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1526,7 +1526,7 @@ 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",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1578,20 +1578,22 @@ For example, the following Java configuration registers a global `yyyyMMdd` form
|
||||
public FormattingConversionService conversionService() {
|
||||
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
|
||||
DefaultFormattingConversionService conversionService =
|
||||
new DefaultFormattingConversionService(false);
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
|
||||
conversionService.addFormatterForFieldAnnotation(
|
||||
new NumberFormatAnnotationFormatterFactory());
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
registrar.registerFormatters(conversionService);
|
||||
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
dateTimeRegistrar.registerFormatters(conversionService);
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
|
||||
registrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
registrar.registerFormatters(conversionService);
|
||||
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
|
||||
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
dateRegistrar.registerFormatters(conversionService);
|
||||
|
||||
return conversionService;
|
||||
}
|
||||
@@ -1612,14 +1614,14 @@ For example, the following Java configuration registers a global `yyyyMMdd` form
|
||||
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
val registrar = DateTimeFormatterRegistrar()
|
||||
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
registrar.registerFormatters(this)
|
||||
val dateTimeRegistrar = DateTimeFormatterRegistrar()
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
dateTimeRegistrar.registerFormatters(this)
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
val registrar = DateFormatterRegistrar()
|
||||
registrar.setFormatter(DateFormatter("yyyyMMdd"))
|
||||
registrar.registerFormatters(this)
|
||||
val dateRegistrar = DateFormatterRegistrar()
|
||||
dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
|
||||
dateRegistrar.registerFormatters(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@ transactions being created and then rolled back in response to the
|
||||
`UnsupportedOperationException` instance. The following listing shows the `FooService`
|
||||
interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// the service interface that we want to make transactional
|
||||
@@ -641,7 +641,7 @@ interface:
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// the service interface that we want to make transactional
|
||||
@@ -662,7 +662,7 @@ interface:
|
||||
|
||||
The following example shows an implementation of the preceding interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y.service;
|
||||
@@ -690,7 +690,7 @@ The following example shows an implementation of the preceding interface:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y.service
|
||||
@@ -893,7 +893,7 @@ return type is reactive.
|
||||
The following listing shows a modified version of the previously used `FooService`, but
|
||||
this time the code uses reactive types:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// the reactive service interface that we want to make transactional
|
||||
@@ -912,7 +912,7 @@ this time the code uses reactive types:
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// the reactive service interface that we want to make transactional
|
||||
@@ -933,7 +933,7 @@ this time the code uses reactive types:
|
||||
|
||||
The following example shows an implementation of the preceding interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y.service;
|
||||
@@ -961,7 +961,7 @@ The following example shows an implementation of the preceding interface:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y.service
|
||||
@@ -2028,7 +2028,7 @@ configuration and AOP in general.
|
||||
|
||||
The following code shows the simple profiling aspect discussed earlier:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y;
|
||||
@@ -2065,9 +2065,15 @@ The following code shows the simple profiling aspect discussed earlier:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.springframework.util.StopWatch
|
||||
import org.springframework.core.Ordered
|
||||
|
||||
class SimpleProfiler : Ordered {
|
||||
|
||||
private var order: Int = 0
|
||||
|
||||
@@ -582,7 +582,7 @@ a similar contract to the JMS `MessageListener` interface but also gives the mes
|
||||
method access to the JMS `Session` from which the `Message` was received.
|
||||
The following listing shows the definition of the `SessionAwareMessageListener` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ The core class in Spring's JMX framework is the `MBeanExporter`. This class is
|
||||
responsible for taking your Spring beans and registering them with a JMX `MBeanServer`.
|
||||
For example, consider the following class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.jmx;
|
||||
|
||||
@@ -358,7 +358,7 @@ an operation or an attribute.
|
||||
The following example shows the annotated version of the `JmxTestBean` class that we
|
||||
used in <<jmx-exporting-mbeanserver>>:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.jmx;
|
||||
|
||||
@@ -1067,7 +1067,7 @@ example, consider the scenario where one would like to be informed (through a
|
||||
`Notification`) each and every time an attribute of a target MBean changes. The following
|
||||
example writes notifications to the console:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.example;
|
||||
|
||||
@@ -1317,7 +1317,7 @@ published, and invoke the `sendNotification(Notification)` on the
|
||||
In the following example, exported instances of the `JmxTestBean` publish a
|
||||
`NotificationEvent` every time the `add(int, int)` operation is invoked:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.jmx;
|
||||
|
||||
|
||||
@@ -816,8 +816,7 @@ its properties from the job data mapped to properties of the job instance. So, i
|
||||
the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail`
|
||||
has it applied automatically:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package example;
|
||||
|
||||
@@ -827,7 +826,7 @@ has it applied automatically:
|
||||
|
||||
/**
|
||||
* Setter called after the ExampleJob is instantiated
|
||||
* with the value from the JobDetailFactoryBean (5)
|
||||
* with the value from the JobDetailFactoryBean.
|
||||
*/
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
|
||||
@@ -33,7 +33,7 @@ implement. Note that this interface is defined in plain Java. Dependent objects
|
||||
are injected with a reference to the `Messenger` do not know that the underlying
|
||||
implementation is a Groovy script. The following listing shows the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -45,7 +45,7 @@ implementation is a Groovy script. The following listing shows the `Messenger` i
|
||||
|
||||
The following example defines a class that has a dependency on the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -65,15 +65,14 @@ The following example defines a class that has a dependency on the `Messenger` i
|
||||
|
||||
The following example implements the `Messenger` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
----
|
||||
// from the file 'Messenger.groovy'
|
||||
package org.springframework.scripting.groovy;
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// import the Messenger interface (written in Java) that is to be implemented
|
||||
// Import the Messenger interface (written in Java) that is to be implemented
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// define the implementation in Groovy
|
||||
// Define the implementation in Groovy in file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
String message
|
||||
@@ -276,7 +275,7 @@ surrounded by quotation marks. The following listing shows the changes that you
|
||||
(the developer) should make to the `Messenger.groovy` source file when the
|
||||
execution of the program is paused:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting
|
||||
|
||||
@@ -331,13 +330,13 @@ feature works:
|
||||
<lang:groovy id="messenger">
|
||||
<lang:inline-script>
|
||||
|
||||
package org.springframework.scripting.groovy;
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
class GroovyMessenger implements Messenger {
|
||||
String message
|
||||
}
|
||||
class GroovyMessenger implements Messenger {
|
||||
String message
|
||||
}
|
||||
|
||||
</lang:inline-script>
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
@@ -363,13 +362,13 @@ constructors and properties 100% clear, the following mixture of code and config
|
||||
does not work:
|
||||
|
||||
.An approach that cannot work
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
// from the file 'Messenger.groovy'
|
||||
package org.springframework.scripting.groovy;
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// from the file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
GroovyMessenger() {}
|
||||
@@ -420,7 +419,7 @@ If you have read this chapter straight from the top, you have already
|
||||
<<dynamic-language-a-first-example, seen an example>> of a Groovy-dynamic-language-backed
|
||||
bean. Now consider another example (again using an example from the Spring test suite):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -432,11 +431,11 @@ bean. Now consider another example (again using an example from the Spring test
|
||||
|
||||
The following example implements the `Calculator` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
// from the file 'calculator.groovy'
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// from the file 'calculator.groovy'
|
||||
class GroovyCalculator implements Calculator {
|
||||
|
||||
int add(int x, int y) {
|
||||
@@ -457,7 +456,7 @@ The following bean definition uses the calculator defined in Groovy:
|
||||
|
||||
Finally, the following small application exercises the preceding configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -596,7 +595,7 @@ Now we can show a fully working example of using a BeanShell-based bean that imp
|
||||
the `Messenger` interface that was defined earlier in this chapter. We again show the
|
||||
definition of the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -676,9 +675,8 @@ beans, you have to enable the "`refreshable beans`" functionality. See
|
||||
The following example shows an `org.springframework.web.servlet.mvc.Controller` implemented
|
||||
by using the Groovy dynamic language:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
// from the file '/WEB-INF/groovy/FortuneController.groovy'
|
||||
package org.springframework.showcase.fortune.web
|
||||
|
||||
import org.springframework.showcase.fortune.service.FortuneService
|
||||
@@ -689,6 +687,7 @@ by using the Groovy dynamic language:
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
|
||||
// from the file '/WEB-INF/groovy/FortuneController.groovy'
|
||||
class FortuneController implements Controller {
|
||||
|
||||
@Property FortuneService fortuneService
|
||||
|
||||
@@ -11,7 +11,7 @@ dependencies {
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.4"))
|
||||
api(platform("io.netty:netty-bom:4.1.89.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.3"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.4"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.8"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.19.0"))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.0.5-SNAPSHOT
|
||||
version=6.0.6
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+32
-47
@@ -38,6 +38,14 @@ import org.springframework.util.StringUtils;
|
||||
* for an advice method from the pointcut expression, returning, and throwing clauses.
|
||||
* If an unambiguous interpretation is not available, it returns {@code null}.
|
||||
*
|
||||
* <h3>Algorithm Summary</h3>
|
||||
* <p>If an unambiguous binding can be deduced, then it is.
|
||||
* If the advice requirements cannot possibly be satisfied, then {@code null}
|
||||
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property to {@code true}, descriptive exceptions will be thrown instead of
|
||||
* returning {@code null} in the case that the parameter names cannot be discovered.
|
||||
*
|
||||
* <h3>Algorithm Details</h3>
|
||||
* <p>This class interprets arguments in the following way:
|
||||
* <ol>
|
||||
* <li>If the first parameter of the method is of type {@link JoinPoint}
|
||||
@@ -65,15 +73,15 @@ import org.springframework.util.StringUtils;
|
||||
* zero we proceed to the next stage. If {@code a} > 1 then an
|
||||
* {@code AmbiguousBindingException} is raised. If {@code a} == 1,
|
||||
* and there are no unbound arguments of type {@code Annotation+},
|
||||
* then an {@code IllegalArgumentException} is raised. if there is
|
||||
* then an {@code IllegalArgumentException} is raised. If there is
|
||||
* exactly one such argument, then the corresponding parameter name is
|
||||
* assigned the value from the pointcut expression.</li>
|
||||
* <li>If a returningName has been set, and there are no unbound arguments
|
||||
* <li>If a {@code returningName} has been set, and there are no unbound arguments
|
||||
* then an {@code IllegalArgumentException} is raised. If there is
|
||||
* more than one unbound argument then an
|
||||
* {@code AmbiguousBindingException} is raised. If there is exactly
|
||||
* one unbound argument then the corresponding parameter name is assigned
|
||||
* the value <returningName>.</li>
|
||||
* the value of the {@code returningName}.</li>
|
||||
* <li>If there remain unbound arguments, then the pointcut expression is
|
||||
* examined once more for {@code this}, {@code target}, and
|
||||
* {@code args} pointcut expressions used in the binding form (binding
|
||||
@@ -99,20 +107,12 @@ import org.springframework.util.StringUtils;
|
||||
* <p>The behavior on raising an {@code IllegalArgumentException} or
|
||||
* {@code AmbiguousBindingException} is configurable to allow this discoverer
|
||||
* to be used as part of a chain-of-responsibility. By default the condition will
|
||||
* be logged and the {@code getParameterNames(..)} method will simply return
|
||||
* be logged and the {@link #getParameterNames(Method)} method will simply return
|
||||
* {@code null}. If the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property is set to {@code true}, the conditions will be thrown as
|
||||
* {@code IllegalArgumentException} and {@code AmbiguousBindingException},
|
||||
* respectively.
|
||||
*
|
||||
* <p>Was that perfectly clear? ;)
|
||||
*
|
||||
* <p>Short version: If an unambiguous binding can be deduced, then it is.
|
||||
* If the advice requirements cannot possibly be satisfied, then {@code null}
|
||||
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property to {@code true}, descriptive exceptions will be thrown instead of
|
||||
* returning {@code null} in the case that the parameter names cannot be discovered.
|
||||
*
|
||||
* @author Adrian Colyer
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
@@ -197,7 +197,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
/**
|
||||
* If {@code afterReturning} advice binds the return value, the
|
||||
* returning variable name must be specified.
|
||||
* {@code returning} variable name must be specified.
|
||||
* @param returningName the name of the returning variable
|
||||
*/
|
||||
public void setReturningName(@Nullable String returningName) {
|
||||
@@ -206,18 +206,17 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
/**
|
||||
* If {@code afterThrowing} advice binds the thrown value, the
|
||||
* throwing variable name must be specified.
|
||||
* {@code throwing} variable name must be specified.
|
||||
* @param throwingName the name of the throwing variable
|
||||
*/
|
||||
public void setThrowingName(@Nullable String throwingName) {
|
||||
this.throwingName = throwingName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deduce the parameter names for an advice method.
|
||||
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class level javadoc}
|
||||
* for this class for details of the algorithm used.
|
||||
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class-level javadoc}
|
||||
* for this class for details on the algorithm used.
|
||||
* @param method the target {@link Method}
|
||||
* @return the parameter names
|
||||
*/
|
||||
@@ -309,7 +308,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
|
||||
/**
|
||||
* If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as
|
||||
* If the first parameter is of type JoinPoint or ProceedingJoinPoint, bind "thisJoinPoint" as
|
||||
* parameter name and return true, else return false.
|
||||
*/
|
||||
private boolean maybeBindThisJoinPoint() {
|
||||
@@ -348,14 +347,14 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
// Second candidate we've found - ambiguous binding
|
||||
throw new AmbiguousBindingException("Binding of throwing parameter '" +
|
||||
this.throwingName + "' is ambiguous: could be bound to argument " +
|
||||
throwableIndex + " or argument " + i);
|
||||
throwableIndex + " or " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (throwableIndex == -1) {
|
||||
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName
|
||||
+ "' could not be completed as no available arguments are a subtype of Throwable");
|
||||
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName +
|
||||
"' could not be completed as no available arguments are a subtype of Throwable");
|
||||
}
|
||||
else {
|
||||
bindParameterName(throwableIndex, this.throwingName);
|
||||
@@ -374,7 +373,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
if (this.returningName != null) {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
|
||||
"' is ambiguous, there are " + this.numberOfRemainingUnboundArguments + " candidates.");
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
|
||||
}
|
||||
|
||||
// We're all set... find the unbound parameter, and bind it.
|
||||
@@ -387,7 +386,6 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the string pointcut expression looking for:
|
||||
* @this, @target, @args, @within, @withincode, @annotation.
|
||||
@@ -431,7 +429,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
int numAnnotationSlots = countNumberOfUnboundAnnotationArguments();
|
||||
if (numAnnotationSlots > 1) {
|
||||
throw new AmbiguousBindingException("Found " + varNames.size() +
|
||||
" potential annotation variable(s), and " +
|
||||
" potential annotation variable(s) and " +
|
||||
numAnnotationSlots + " potential argument slots");
|
||||
}
|
||||
else if (numAnnotationSlots == 1) {
|
||||
@@ -452,7 +450,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* If the token starts meets Java identifier conventions, it's in.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -488,7 +486,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at this(),target(),args() binding stage, with no way to determine between them");
|
||||
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -520,10 +518,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (varNames.size() > 1) {
|
||||
throw new AmbiguousBindingException("Found " + varNames.size() +
|
||||
" candidate this(), target() or args() variables but only one unbound argument slot");
|
||||
" candidate this(), target(), or args() variables but only one unbound argument slot");
|
||||
}
|
||||
else if (varNames.size() == 1) {
|
||||
for (int j = 0; j < this.parameterNameBindings.length; j++) {
|
||||
@@ -596,7 +593,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
// else varNames.size must be 0 and we have nothing to bind.
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* We've found the start of a binding pointcut at the given index into the
|
||||
* token array. Now we need to extract the pointcut body and return it.
|
||||
*/
|
||||
@@ -649,8 +646,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
private void maybeBindPrimitiveArgsFromPointcutExpression() {
|
||||
int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments();
|
||||
if (numUnboundPrimitives > 1) {
|
||||
throw new AmbiguousBindingException("Found '" + numUnboundPrimitives +
|
||||
"' unbound primitive arguments with no way to distinguish between them.");
|
||||
throw new AmbiguousBindingException("Found " + numUnboundPrimitives +
|
||||
" unbound primitive arguments with no way to distinguish between them.");
|
||||
}
|
||||
if (numUnboundPrimitives == 1) {
|
||||
// Look for arg variable and bind it if we find exactly one...
|
||||
@@ -696,7 +693,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Return {@code true} if the given argument type is a subclass
|
||||
* of the given supertype.
|
||||
*/
|
||||
@@ -724,7 +721,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Find the argument index with the given type, and bind the given
|
||||
* {@code varName} in that position.
|
||||
*/
|
||||
@@ -741,22 +738,10 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
|
||||
/**
|
||||
* Simple struct to hold the extracted text from a pointcut body, together
|
||||
* Simple record to hold the extracted text from a pointcut body, together
|
||||
* with the number of tokens consumed in extracting it.
|
||||
*/
|
||||
private static class PointcutBody {
|
||||
|
||||
private final int numTokensConsumed;
|
||||
|
||||
@Nullable
|
||||
private final String text;
|
||||
|
||||
public PointcutBody(int tokens, @Nullable String text) {
|
||||
this.numTokensConsumed = tokens;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
|
||||
|
||||
/**
|
||||
* Thrown in response to an ambiguous binding being detected when
|
||||
|
||||
+24
-22
@@ -121,21 +121,21 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
|
||||
for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
|
||||
AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
|
||||
if (foundAnnotation != null) {
|
||||
return foundAnnotation;
|
||||
protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) {
|
||||
for (Class<?> annotationType : ASPECTJ_ANNOTATION_CLASSES) {
|
||||
AspectJAnnotation annotation = findAnnotation(method, (Class<Annotation>) annotationType);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
|
||||
A result = AnnotationUtils.findAnnotation(method, toLookFor);
|
||||
if (result != null) {
|
||||
return new AspectJAnnotation<>(result);
|
||||
private static AspectJAnnotation findAnnotation(Method method, Class<? extends Annotation> annotationType) {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
if (annotation != null) {
|
||||
return new AspectJAnnotation(annotation);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -156,9 +156,8 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
/**
|
||||
* Class modeling an AspectJ annotation, exposing its type enumeration and
|
||||
* pointcut String.
|
||||
* @param <A> the annotation type
|
||||
*/
|
||||
protected static class AspectJAnnotation<A extends Annotation> {
|
||||
protected static class AspectJAnnotation {
|
||||
|
||||
private static final String[] EXPRESSION_ATTRIBUTES = {"pointcut", "value"};
|
||||
|
||||
@@ -171,7 +170,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing //
|
||||
);
|
||||
|
||||
private final A annotation;
|
||||
private final Annotation annotation;
|
||||
|
||||
private final AspectJAnnotationType annotationType;
|
||||
|
||||
@@ -179,11 +178,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
|
||||
private final String argumentNames;
|
||||
|
||||
public AspectJAnnotation(A annotation) {
|
||||
public AspectJAnnotation(Annotation annotation) {
|
||||
this.annotation = annotation;
|
||||
this.annotationType = determineAnnotationType(annotation);
|
||||
try {
|
||||
this.pointcutExpression = resolveExpression(annotation);
|
||||
this.pointcutExpression = resolvePointcutExpression(annotation);
|
||||
Object argNames = AnnotationUtils.getValue(annotation, "argNames");
|
||||
this.argumentNames = (argNames instanceof String names ? names : "");
|
||||
}
|
||||
@@ -192,7 +191,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
}
|
||||
}
|
||||
|
||||
private AspectJAnnotationType determineAnnotationType(A annotation) {
|
||||
private AspectJAnnotationType determineAnnotationType(Annotation annotation) {
|
||||
AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType());
|
||||
if (type != null) {
|
||||
return type;
|
||||
@@ -200,21 +199,21 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
throw new IllegalStateException("Unknown annotation type: " + annotation);
|
||||
}
|
||||
|
||||
private String resolveExpression(A annotation) {
|
||||
private String resolvePointcutExpression(Annotation annotation) {
|
||||
for (String attributeName : EXPRESSION_ATTRIBUTES) {
|
||||
Object val = AnnotationUtils.getValue(annotation, attributeName);
|
||||
if (val instanceof String str && !str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Failed to resolve expression in: " + annotation);
|
||||
throw new IllegalStateException("Failed to resolve pointcut expression in: " + annotation);
|
||||
}
|
||||
|
||||
public AspectJAnnotationType getAnnotationType() {
|
||||
return this.annotationType;
|
||||
}
|
||||
|
||||
public A getAnnotation() {
|
||||
public Annotation getAnnotation() {
|
||||
return this.annotation;
|
||||
}
|
||||
|
||||
@@ -239,19 +238,22 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
*/
|
||||
private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer {
|
||||
|
||||
private static final String[] EMPTY_ARRAY = new String[0];
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames(Method method) {
|
||||
if (method.getParameterCount() == 0) {
|
||||
return new String[0];
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
AspectJAnnotation<?> annotation = findAspectJAnnotationOnMethod(method);
|
||||
AspectJAnnotation annotation = findAspectJAnnotationOnMethod(method);
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ",");
|
||||
if (nameTokens.countTokens() > 0) {
|
||||
String[] names = new String[nameTokens.countTokens()];
|
||||
int numTokens = nameTokens.countTokens();
|
||||
if (numTokens > 0) {
|
||||
String[] names = new String[numTokens];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
names[i] = nameTokens.nextToken();
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,7 +35,8 @@ import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Internal implementation of AspectJPointcutAdvisor.
|
||||
* Note that there will be one instance of this advisor for each target method.
|
||||
*
|
||||
* <p>Note that there will be one instance of this advisor for each target method.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
@@ -212,7 +213,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
* creation of the advice.
|
||||
*/
|
||||
private void determineAdviceType() {
|
||||
AspectJAnnotation<?> aspectJAnnotation =
|
||||
AspectJAnnotation aspectJAnnotation =
|
||||
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(this.aspectJAdviceMethod);
|
||||
if (aspectJAnnotation == null) {
|
||||
this.isBeforeAdvice = false;
|
||||
|
||||
+3
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,10 +23,6 @@ import org.springframework.lang.Nullable;
|
||||
* Subinterface of {@link org.springframework.aop.aspectj.AspectInstanceFactory}
|
||||
* that returns {@link AspectMetadata} associated with AspectJ-annotated classes.
|
||||
*
|
||||
* <p>Ideally, AspectInstanceFactory would include this method itself, but because
|
||||
* AspectMetadata uses Java-5-only {@link org.aspectj.lang.reflect.AjType},
|
||||
* we need to split out this subinterface.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @since 2.0
|
||||
* @see AspectMetadata
|
||||
@@ -35,13 +31,13 @@ import org.springframework.lang.Nullable;
|
||||
public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory {
|
||||
|
||||
/**
|
||||
* Return the AspectJ AspectMetadata for this factory's aspect.
|
||||
* Get the AspectJ AspectMetadata for this factory's aspect.
|
||||
* @return the aspect metadata
|
||||
*/
|
||||
AspectMetadata getAspectMetadata();
|
||||
|
||||
/**
|
||||
* Return the best possible creation mutex for this factory.
|
||||
* Get the best possible creation mutex for this factory.
|
||||
* @return the mutex object (may be {@code null} for no mutex to use)
|
||||
* @since 4.3
|
||||
*/
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -87,7 +87,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
new InstanceComparator<>(
|
||||
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
|
||||
(Converter<Method, Annotation>) method -> {
|
||||
AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
|
||||
AspectJAnnotation ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
|
||||
return (ann != null ? ann.getAnnotation() : null);
|
||||
});
|
||||
Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);
|
||||
@@ -216,7 +216,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
|
||||
@Nullable
|
||||
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
|
||||
AspectJAnnotation<?> aspectJAnnotation =
|
||||
AspectJAnnotation aspectJAnnotation =
|
||||
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
|
||||
if (aspectJAnnotation == null) {
|
||||
return null;
|
||||
@@ -240,7 +240,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
|
||||
validate(candidateAspectClass);
|
||||
|
||||
AspectJAnnotation<?> aspectJAnnotation =
|
||||
AspectJAnnotation aspectJAnnotation =
|
||||
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
|
||||
if (aspectJAnnotation == null) {
|
||||
return null;
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -92,8 +92,7 @@ public class BeanFactoryAdvisorRetrievalHelper {
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
Throwable rootCause = ex.getMostSpecificCause();
|
||||
if (rootCause instanceof BeanCurrentlyInCreationException) {
|
||||
BeanCreationException bce = (BeanCreationException) rootCause;
|
||||
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
|
||||
String bceBeanName = bce.getBeanName();
|
||||
if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Various {@link org.springframework.aop.framework.autoproxy.TargetSourceCreator}
|
||||
* implementations for use with Spring's AOP auto-proxying support.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.aop.framework.autoproxy.target;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
+55
-67
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.aop.aspectj;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
@@ -43,17 +42,17 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void noArgs() {
|
||||
assertParameterNames(getMethod("noArgs"), "execution(* *(..))", new String[0]);
|
||||
assertParameterNames(getMethod("noArgs"), "execution(* *(..))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinPointOnly() {
|
||||
assertParameterNames(getMethod("tjp"), "execution(* *(..))", new String[] {"thisJoinPoint"});
|
||||
assertParameterNames(getMethod("tjp"), "execution(* *(..))", "thisJoinPoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinPointStaticPartOnly() {
|
||||
assertParameterNames(getMethod("tjpsp"), "execution(* *(..))", new String[] {"thisJoinPointStaticPart"});
|
||||
assertParameterNames(getMethod("tjpsp"), "execution(* *(..))", "thisJoinPointStaticPart");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,18 +63,18 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void oneThrowable() {
|
||||
assertParameterNames(getMethod("oneThrowable"), "foo()", null, "ex", new String[] {"ex"});
|
||||
assertParameterNamesExtended(getMethod("oneThrowable"), "foo()", null, "ex", "ex");
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneJPAndOneThrowable() {
|
||||
assertParameterNames(getMethod("jpAndOneThrowable"), "foo()", null, "ex", new String[] {"thisJoinPoint", "ex"});
|
||||
assertParameterNamesExtended(getMethod("jpAndOneThrowable"), "foo()", null, "ex", "thisJoinPoint", "ex");
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneJPAndTwoThrowables() {
|
||||
assertException(getMethod("jpAndTwoThrowables"), "foo()", null, "ex", AmbiguousBindingException.class,
|
||||
"Binding of throwing parameter 'ex' is ambiguous: could be bound to argument 1 or argument 2");
|
||||
"Binding of throwing parameter 'ex' is ambiguous: could be bound to argument 1 or 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,13 +85,13 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void returning() {
|
||||
assertParameterNames(getMethod("oneObject"), "foo()", "obj", null, new String[] {"obj"});
|
||||
assertParameterNamesExtended(getMethod("oneObject"), "foo()", "obj", null, "obj");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ambiguousReturning() {
|
||||
assertException(getMethod("twoObjects"), "foo()", "obj", null, AmbiguousBindingException.class,
|
||||
"Binding of returning parameter 'obj' is ambiguous, there are 2 candidates.");
|
||||
"Binding of returning parameter 'obj' is ambiguous: there are 2 candidates.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,22 +102,22 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void thisBindingOneCandidate() {
|
||||
assertParameterNames(getMethod("oneObject"), "this(x)", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "this(x)", "x");
|
||||
}
|
||||
|
||||
@Test
|
||||
void thisBindingWithAlternateTokenizations() {
|
||||
assertParameterNames(getMethod("oneObject"), "this( x )", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "this( x)", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "this (x )", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "this(x )", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "foo() && this(x)", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "this( x )", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "this( x)", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "this (x )", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "this(x )", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "foo() && this(x)", "x");
|
||||
}
|
||||
|
||||
@Test
|
||||
void thisBindingTwoCandidates() {
|
||||
assertException(getMethod("oneObject"), "this(x) || this(y)", AmbiguousBindingException.class,
|
||||
"Found 2 candidate this(), target() or args() variables but only one unbound argument slot");
|
||||
"Found 2 candidate this(), target(), or args() variables but only one unbound argument slot");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,22 +130,22 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void targetBindingOneCandidate() {
|
||||
assertParameterNames(getMethod("oneObject"), "target(x)", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "target(x)", "x");
|
||||
}
|
||||
|
||||
@Test
|
||||
void targetBindingWithAlternateTokenizations() {
|
||||
assertParameterNames(getMethod("oneObject"), "target( x )", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "target( x)", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "target (x )", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "target(x )", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "foo() && target(x)", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "target( x )", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "target( x)", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "target (x )", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "target(x )", "x");
|
||||
assertParameterNames(getMethod("oneObject"), "foo() && target(x)", "x");
|
||||
}
|
||||
|
||||
@Test
|
||||
void targetBindingTwoCandidates() {
|
||||
assertException(getMethod("oneObject"), "target(x) || target(y)", AmbiguousBindingException.class,
|
||||
"Found 2 candidate this(), target() or args() variables but only one unbound argument slot");
|
||||
"Found 2 candidate this(), target(), or args() variables but only one unbound argument slot");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -159,24 +158,24 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void argsBindingOneObject() {
|
||||
assertParameterNames(getMethod("oneObject"), "args(x)", new String[] {"x"});
|
||||
assertParameterNames(getMethod("oneObject"), "args(x)", "x");
|
||||
}
|
||||
|
||||
@Test
|
||||
void argsBindingOneObjectTwoCandidates() {
|
||||
assertException(getMethod("oneObject"), "args(x,y)", AmbiguousBindingException.class,
|
||||
"Found 2 candidate this(), target() or args() variables but only one unbound argument slot");
|
||||
"Found 2 candidate this(), target(), or args() variables but only one unbound argument slot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ambiguousArgsBinding() {
|
||||
assertException(getMethod("twoObjects"), "args(x,y)", AmbiguousBindingException.class,
|
||||
"Still 2 unbound args at this(),target(),args() binding stage, with no way to determine between them");
|
||||
"Still 2 unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
@Test
|
||||
void argsOnePrimitive() {
|
||||
assertParameterNames(getMethod("onePrimitive"), "args(count)", new String[] {"count"});
|
||||
assertParameterNames(getMethod("onePrimitive"), "args(count)", "count");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -188,37 +187,37 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
@Test
|
||||
void thisAndPrimitive() {
|
||||
assertParameterNames(getMethod("oneObjectOnePrimitive"), "args(count) && this(obj)",
|
||||
new String[] {"obj", "count"});
|
||||
"obj", "count");
|
||||
}
|
||||
|
||||
@Test
|
||||
void targetAndPrimitive() {
|
||||
assertParameterNames(getMethod("oneObjectOnePrimitive"), "args(count) && target(obj)",
|
||||
new String[] {"obj", "count"});
|
||||
"obj", "count");
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwingAndPrimitive() {
|
||||
assertParameterNames(getMethod("oneThrowableOnePrimitive"), "args(count)", null, "ex",
|
||||
new String[] {"ex", "count"});
|
||||
assertParameterNamesExtended(getMethod("oneThrowableOnePrimitive"), "args(count)", null, "ex",
|
||||
"ex", "count");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allTogetherNow() {
|
||||
assertParameterNames(getMethod("theBigOne"), "this(foo) && args(x)", null, "ex",
|
||||
new String[] {"thisJoinPoint", "ex", "x", "foo"});
|
||||
assertParameterNamesExtended(getMethod("theBigOne"), "this(foo) && args(x)", null, "ex",
|
||||
"thisJoinPoint", "ex", "x", "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void referenceBinding() {
|
||||
assertParameterNames(getMethod("onePrimitive"),"somepc(foo)", new String[] {"foo"});
|
||||
assertParameterNames(getMethod("onePrimitive"),"somepc(foo)", "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void referenceBindingWithAlternateTokenizations() {
|
||||
assertParameterNames(getMethod("onePrimitive"),"call(bar *) && somepc(foo)", new String[] {"foo"});
|
||||
assertParameterNames(getMethod("onePrimitive"),"somepc ( foo )", new String[] {"foo"});
|
||||
assertParameterNames(getMethod("onePrimitive"),"somepc( foo)", new String[] {"foo"});
|
||||
assertParameterNames(getMethod("onePrimitive"),"call(bar *) && somepc(foo)", "foo");
|
||||
assertParameterNames(getMethod("onePrimitive"),"somepc ( foo )", "foo");
|
||||
assertParameterNames(getMethod("onePrimitive"),"somepc( foo)", "foo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,38 +229,38 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void atThis() {
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@this(a)", new String[] {"a"});
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@this(a)", "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atTarget() {
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@target(a)", new String[] {"a"});
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@target(a)", "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atArgs() {
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@args(a)", new String[] {"a"});
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@args(a)", "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atWithin() {
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@within(a)", new String[] {"a"});
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@within(a)", "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atWithincode() {
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@withincode(a)", new String[] {"a"});
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@withincode(a)", "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atAnnotation() {
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@annotation(a)", new String[] {"a"});
|
||||
assertParameterNames(getMethod("oneAnnotation"),"@annotation(a)", "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ambiguousAnnotationTwoVars() {
|
||||
assertException(getMethod("twoAnnotations"),"@annotation(a) && @this(x)", AmbiguousBindingException.class,
|
||||
"Found 2 potential annotation variable(s), and 2 potential argument slots");
|
||||
"Found 2 potential annotation variable(s) and 2 potential argument slots");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -272,15 +271,14 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
|
||||
@Test
|
||||
void annotationMedley() {
|
||||
assertParameterNames(getMethod("annotationMedley"),"@annotation(a) && args(count) && this(foo)",
|
||||
null, "ex", new String[] {"ex", "foo", "count", "a"});
|
||||
assertParameterNamesExtended(getMethod("annotationMedley"),"@annotation(a) && args(count) && this(foo)",
|
||||
null, "ex", "ex", "foo", "count", "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationBinding() {
|
||||
assertParameterNames(getMethod("pjpAndAnAnnotation"),
|
||||
"execution(* *(..)) && @annotation(ann)",
|
||||
new String[] {"thisJoinPoint","ann"});
|
||||
"execution(* *(..)) && @annotation(ann)", "thisJoinPoint", "ann");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -296,33 +294,23 @@ class AspectJAdviceParameterNameDiscovererTests {
|
||||
throw new AssertionError("Bad test specification, no method '" + name + "' found in test class");
|
||||
}
|
||||
|
||||
private void assertParameterNames(Method method, String pointcut, String[] parameterNames) {
|
||||
assertParameterNames(method, pointcut, null, null, parameterNames);
|
||||
private void assertParameterNames(Method method, String pointcut, String... parameterNames) {
|
||||
assertParameterNamesExtended(method, pointcut, null, null, parameterNames);
|
||||
}
|
||||
|
||||
private void assertParameterNames(
|
||||
Method method, String pointcut, String returning, String throwing, String[] parameterNames) {
|
||||
private void assertParameterNamesExtended(
|
||||
Method method, String pointcut, String returning, String throwing, String... parameterNames) {
|
||||
|
||||
assertThat(parameterNames.length).as("bad test specification, must have same number of parameter names as method arguments").isEqualTo(method.getParameterCount());
|
||||
assertThat(parameterNames)
|
||||
.as("bad test specification, must have same number of parameter names as method arguments")
|
||||
.hasSize(method.getParameterCount());
|
||||
|
||||
AspectJAdviceParameterNameDiscoverer discoverer = new AspectJAdviceParameterNameDiscoverer(pointcut);
|
||||
discoverer.setRaiseExceptions(true);
|
||||
discoverer.setReturningName(returning);
|
||||
discoverer.setThrowingName(throwing);
|
||||
String[] discoveredNames = discoverer.getParameterNames(method);
|
||||
|
||||
String formattedExpectedNames = Arrays.toString(parameterNames);
|
||||
String formattedActualNames = Arrays.toString(discoveredNames);
|
||||
|
||||
assertThat(discoveredNames.length).as("Expecting " + parameterNames.length + " parameter names in return set '" +
|
||||
formattedExpectedNames + "', but found " + discoveredNames.length +
|
||||
" '" + formattedActualNames + "'").isEqualTo(parameterNames.length);
|
||||
|
||||
for (int i = 0; i < discoveredNames.length; i++) {
|
||||
assertThat(discoveredNames[i]).as("Parameter names must never be null").isNotNull();
|
||||
assertThat(discoveredNames[i]).as("Expecting parameter " + i + " to be named '" +
|
||||
parameterNames[i] + "' but was '" + discoveredNames[i] + "'").isEqualTo(parameterNames[i]);
|
||||
}
|
||||
assertThat(discoverer.getParameterNames(method)).isEqualTo(parameterNames);
|
||||
}
|
||||
|
||||
private void assertException(Method method, String pointcut, Class<? extends Throwable> exceptionType, String message) {
|
||||
|
||||
+44
-48
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,56 +38,55 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
* @author Adrian Colyer
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class ArgumentBindingTests {
|
||||
class ArgumentBindingTests {
|
||||
|
||||
@Test
|
||||
public void testBindingInPointcutUsedByAdvice() {
|
||||
TestBean tb = new TestBean();
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb);
|
||||
void bindingInPointcutUsedByAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(NamedPointcutWithArgs.class);
|
||||
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
proxiedTestBean.setName("Supercalifragalisticexpialidocious"));
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotationArgumentNameBinding() {
|
||||
TransactionalBean tb = new TransactionalBean();
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb);
|
||||
void annotationArgumentNameBinding() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
|
||||
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
|
||||
|
||||
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
proxiedTestBean::doInTransaction);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(proxiedTestBean::doInTransaction)
|
||||
.withMessage("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterNameDiscoverWithReferencePointcut() throws Exception {
|
||||
void parameterNameDiscoverWithReferencePointcut() throws Exception {
|
||||
AspectJAdviceParameterNameDiscoverer discoverer =
|
||||
new AspectJAdviceParameterNameDiscoverer("somepc(formal) && set(* *)");
|
||||
discoverer.setRaiseExceptions(true);
|
||||
Method methodUsedForParameterTypeDiscovery =
|
||||
getClass().getMethod("methodWithOneParam", String.class);
|
||||
String[] pnames = discoverer.getParameterNames(methodUsedForParameterTypeDiscovery);
|
||||
assertThat(pnames.length).as("one parameter name").isEqualTo(1);
|
||||
assertThat(pnames[0]).isEqualTo("formal");
|
||||
Method method = getClass().getDeclaredMethod("methodWithOneParam", String.class);
|
||||
assertThat(discoverer.getParameterNames(method)).containsExactly("formal");
|
||||
}
|
||||
|
||||
|
||||
public void methodWithOneParam(String aParam) {
|
||||
@SuppressWarnings("unused")
|
||||
private void methodWithOneParam(String aParam) {
|
||||
}
|
||||
|
||||
|
||||
public interface ITransactionalBean {
|
||||
interface ITransactionalBean {
|
||||
|
||||
@Transactional
|
||||
void doInTransaction();
|
||||
}
|
||||
|
||||
|
||||
public static class TransactionalBean implements ITransactionalBean {
|
||||
static class TransactionalBean implements ITransactionalBean {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -95,38 +94,35 @@ public class ArgumentBindingTests {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Transactional {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents Spring's Transactional annotation without actually introducing the dependency
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Transactional {
|
||||
}
|
||||
@Aspect
|
||||
static class PointcutWithAnnotationArgument {
|
||||
|
||||
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
|
||||
throw new IllegalStateException("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
@Aspect
|
||||
class PointcutWithAnnotationArgument {
|
||||
}
|
||||
|
||||
@Aspect
|
||||
static class NamedPointcutWithArgs {
|
||||
|
||||
@Pointcut("execution(* *(..)) && args(s,..)")
|
||||
public void pointcutWithArgs(String s) {}
|
||||
|
||||
@Around("pointcutWithArgs(aString)")
|
||||
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
|
||||
throw new IllegalArgumentException(aString);
|
||||
}
|
||||
|
||||
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transaction)")
|
||||
public Object around(ProceedingJoinPoint pjp, Transactional transaction) throws Throwable {
|
||||
System.out.println("Invoked with transaction " + transaction);
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
class NamedPointcutWithArgs {
|
||||
|
||||
@Pointcut("execution(* *(..)) && args(s,..)")
|
||||
public void pointcutWithArgs(String s) {}
|
||||
|
||||
@Around("pointcutWithArgs(aString)")
|
||||
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
|
||||
System.out.println("got '" + aString + "' at '" + pjp + "'");
|
||||
throw new IllegalArgumentException(aString);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -246,7 +246,8 @@ public abstract class BeanUtils {
|
||||
// A single public constructor
|
||||
return (Constructor<T>) ctors[0];
|
||||
}
|
||||
else if (ctors.length == 0){
|
||||
else if (ctors.length == 0) {
|
||||
// No public constructors -> check non-public
|
||||
ctors = clazz.getDeclaredConstructors();
|
||||
if (ctors.length == 1) {
|
||||
// A single non-public constructor, e.g. from a non-public record type
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+4
-4
@@ -130,10 +130,10 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
|
||||
* {@link BeanPostProcessor BeanPostProcessors}.
|
||||
* <p>Note: This is intended for creating a fresh instance, populating annotated
|
||||
* fields and methods as well as applying all standard bean initialization callbacks.
|
||||
* Constructor resolution is done via {@link #AUTOWIRE_CONSTRUCTOR}, also influenced
|
||||
* by {@link SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors}.
|
||||
* It does <i>not</i> imply traditional by-name or by-type autowiring of properties;
|
||||
* use {@link #createBean(Class, int, boolean)} for those purposes.
|
||||
* Constructor resolution is based on Kotlin primary / single public / single non-public,
|
||||
* with a fallback to the default constructor in ambiguous scenarios, also influenced
|
||||
* by {@link SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors}
|
||||
* (e.g. for annotation-driven constructor selection).
|
||||
* @param beanClass the class of the bean to create
|
||||
* @return the new bean instance
|
||||
* @throws BeansException if instantiation or wiring failed
|
||||
|
||||
+43
-3
@@ -315,8 +315,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T createBean(Class<T> beanClass) throws BeansException {
|
||||
// Use non-singleton bean definition, to avoid registering bean as dependent bean.
|
||||
RootBeanDefinition bd = new RootBeanDefinition(beanClass);
|
||||
bd.setAutowireMode(AUTOWIRE_CONSTRUCTOR);
|
||||
RootBeanDefinition bd = new CreateFromClassBeanDefinition(beanClass);
|
||||
bd.setScope(SCOPE_PROTOTYPE);
|
||||
bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader());
|
||||
return (T) createBean(beanClass.getName(), bd, null);
|
||||
@@ -1364,6 +1363,17 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
}
|
||||
}
|
||||
|
||||
if (bw.getWrappedClass().isRecord()) {
|
||||
if (mbd.hasPropertyValues()) {
|
||||
throw new BeanCreationException(
|
||||
mbd.getResourceDescription(), beanName, "Cannot apply property values to a record");
|
||||
}
|
||||
else {
|
||||
// Skip property population phase for records since they are immutable.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
|
||||
// state of the bean before properties are set. This can be used, for example,
|
||||
// to support styles of field injection.
|
||||
@@ -1898,6 +1908,36 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link RootBeanDefinition} subclass for {@code #createBean} calls with
|
||||
* flexible selection of a Kotlin primary / single public / single non-public
|
||||
* constructor candidate in addition to the default constructor.
|
||||
* @see BeanUtils#getResolvableConstructor(Class)
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class CreateFromClassBeanDefinition extends RootBeanDefinition {
|
||||
|
||||
public CreateFromClassBeanDefinition(Class<?> beanClass) {
|
||||
super(beanClass);
|
||||
}
|
||||
|
||||
public CreateFromClassBeanDefinition(CreateFromClassBeanDefinition original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Constructor<?>[] getPreferredConstructors() {
|
||||
return ConstructorResolver.determinePreferredConstructors(getBeanClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootBeanDefinition cloneBeanDefinition() {
|
||||
return new CreateFromClassBeanDefinition(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Special DependencyDescriptor variant for Spring's good old autowire="byType" mode.
|
||||
* Always optional; never considering the parameter name for choosing a primary candidate.
|
||||
@@ -1930,7 +1970,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
public void doWith(Method method) throws IllegalArgumentException {
|
||||
if (isFactoryBeanMethod(method)) {
|
||||
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
|
||||
ResolvableType candidate = returnType.as(FactoryBean.class).getGeneric();
|
||||
|
||||
+48
@@ -1222,6 +1222,54 @@ class ConstructorResolver {
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link BeanUtils#getResolvableConstructor(Class)} for alignment.
|
||||
* This variant adds a lenient fallback to the default constructor if available, similar to
|
||||
* {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors}.
|
||||
*/
|
||||
@Nullable
|
||||
static Constructor<?>[] determinePreferredConstructors(Class<?> clazz) {
|
||||
Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
|
||||
|
||||
Constructor<?> defaultCtor;
|
||||
try {
|
||||
defaultCtor = clazz.getDeclaredConstructor();
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
defaultCtor = null;
|
||||
}
|
||||
|
||||
if (primaryCtor != null) {
|
||||
if (defaultCtor != null && !primaryCtor.equals(defaultCtor)) {
|
||||
return new Constructor<?>[] {primaryCtor, defaultCtor};
|
||||
}
|
||||
else {
|
||||
return new Constructor<?>[] {primaryCtor};
|
||||
}
|
||||
}
|
||||
|
||||
Constructor<?>[] ctors = clazz.getConstructors();
|
||||
if (ctors.length == 1) {
|
||||
// A single public constructor, potentially in combination with a non-public default constructor
|
||||
if (defaultCtor != null && !ctors[0].equals(defaultCtor)) {
|
||||
return new Constructor<?>[] {ctors[0], defaultCtor};
|
||||
}
|
||||
else {
|
||||
return ctors;
|
||||
}
|
||||
}
|
||||
else if (ctors.length == 0) {
|
||||
// No public constructors -> check non-public
|
||||
ctors = clazz.getDeclaredConstructors();
|
||||
if (ctors.length == 1) {
|
||||
// A single non-public constructor, e.g. from a non-public record type
|
||||
return ctors;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private inner class for holding argument combinations.
|
||||
|
||||
+17
@@ -2126,6 +2126,23 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(tb.wasDestroyed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createBeanWithNonDefaultConstructor() {
|
||||
lbf.registerBeanDefinition("otherTestBean", new RootBeanDefinition(TestBean.class));
|
||||
TestBeanRecipient tb = lbf.createBean(TestBeanRecipient.class);
|
||||
assertThat(lbf.containsSingleton("otherTestBean")).isTrue();
|
||||
assertThat(tb.testBean).isEqualTo(lbf.getBean("otherTestBean"));
|
||||
lbf.destroyBean(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createBeanWithPreferredDefaultConstructor() {
|
||||
lbf.registerBeanDefinition("otherTestBean", new RootBeanDefinition(TestBean.class));
|
||||
TestBean tb = lbf.createBean(TestBean.class);
|
||||
assertThat(lbf.containsSingleton("otherTestBean")).isFalse();
|
||||
lbf.destroyBean(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureBean() {
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+3
-1
@@ -22,6 +22,7 @@ import org.quartz.spi.TriggerFiredBundle;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyAccessorFactory;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -86,8 +87,9 @@ public class SpringBeanJobFactory extends AdaptableJobFactory
|
||||
@Override
|
||||
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
|
||||
Object job = (this.applicationContext != null ?
|
||||
// to be replaced with createBean(Class) in 6.1
|
||||
this.applicationContext.getAutowireCapableBeanFactory().createBean(
|
||||
bundle.getJobDetail().getJobClass()) :
|
||||
bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false) :
|
||||
super.createJobInstance(bundle));
|
||||
|
||||
if (isEligibleForPropertyPopulation(job)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,7 +38,7 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* <p>If Lazy is present on a {@link Configuration @Configuration} class, this
|
||||
* indicates that all {@code @Bean} methods within that {@code @Configuration}
|
||||
* should be lazily initialized. If {@code @Lazy} is present and false on a {@code @Bean}
|
||||
* should be lazily initialized. If {@code @Lazy} is present and {@code false} on a {@code @Bean}
|
||||
* method within a {@code @Lazy}-annotated {@code @Configuration} class, this indicates
|
||||
* overriding the 'default lazy' behavior and that the bean should be eagerly initialized.
|
||||
*
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -132,7 +132,7 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
|
||||
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
|
||||
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
|
||||
Executor executor = getTaskExecutor();
|
||||
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -558,7 +558,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
|
||||
|
||||
|
||||
/**
|
||||
* {@link RootBeanDefinition} marker subclass for {@code #registerBean} based
|
||||
* {@link RootBeanDefinition} subclass for {@code #registerBean} based
|
||||
* registrations with flexible autowiring for public constructors.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
|
||||
+9
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -169,8 +169,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
||||
public List<FieldError> getFieldErrors() {
|
||||
List<FieldError> result = new ArrayList<>();
|
||||
for (ObjectError objectError : this.errors) {
|
||||
if (objectError instanceof FieldError) {
|
||||
result.add((FieldError) objectError);
|
||||
if (objectError instanceof FieldError fieldError) {
|
||||
result.add(fieldError);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(result);
|
||||
@@ -180,8 +180,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
||||
@Nullable
|
||||
public FieldError getFieldError() {
|
||||
for (ObjectError objectError : this.errors) {
|
||||
if (objectError instanceof FieldError) {
|
||||
return (FieldError) objectError;
|
||||
if (objectError instanceof FieldError fieldError) {
|
||||
return fieldError;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -192,8 +192,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
||||
List<FieldError> result = new ArrayList<>();
|
||||
String fixedField = fixedField(field);
|
||||
for (ObjectError objectError : this.errors) {
|
||||
if (objectError instanceof FieldError && isMatchingFieldError(fixedField, (FieldError) objectError)) {
|
||||
result.add((FieldError) objectError);
|
||||
if (objectError instanceof FieldError fieldError && isMatchingFieldError(fixedField, fieldError)) {
|
||||
result.add(fieldError);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(result);
|
||||
@@ -204,10 +204,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
||||
public FieldError getFieldError(String field) {
|
||||
String fixedField = fixedField(field);
|
||||
for (ObjectError objectError : this.errors) {
|
||||
if (objectError instanceof FieldError fieldError) {
|
||||
if (isMatchingFieldError(fixedField, fieldError)) {
|
||||
return fieldError;
|
||||
}
|
||||
if (objectError instanceof FieldError fieldError && isMatchingFieldError(fixedField, fieldError)) {
|
||||
return fieldError;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,10 +42,15 @@ public abstract class BindingResultUtils {
|
||||
Assert.notNull(model, "Model map must not be null");
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Object attr = model.get(BindingResult.MODEL_KEY_PREFIX + name);
|
||||
if (attr != null && !(attr instanceof BindingResult)) {
|
||||
if (attr == null) {
|
||||
return null;
|
||||
}
|
||||
if (attr instanceof BindingResult bindingResult) {
|
||||
return bindingResult;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("BindingResult attribute is not of type BindingResult: " + attr);
|
||||
}
|
||||
return (BindingResult) attr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -760,8 +760,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
* @see #doBind(org.springframework.beans.MutablePropertyValues)
|
||||
*/
|
||||
public void bind(PropertyValues pvs) {
|
||||
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
|
||||
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
|
||||
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues mutablePropertyValues ?
|
||||
mutablePropertyValues : new MutablePropertyValues(pvs));
|
||||
doBind(mpvs);
|
||||
}
|
||||
|
||||
@@ -849,8 +849,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
PropertyValue pv = propertyValues.get(field);
|
||||
boolean empty = (pv == null || pv.getValue() == null);
|
||||
if (!empty) {
|
||||
if (pv.getValue() instanceof String) {
|
||||
empty = !StringUtils.hasText((String) pv.getValue());
|
||||
if (pv.getValue() instanceof String text) {
|
||||
empty = !StringUtils.hasText(text);
|
||||
}
|
||||
else if (pv.getValue() instanceof String[] values) {
|
||||
empty = (values.length == 0 || !StringUtils.hasText(values[0]));
|
||||
@@ -925,8 +925,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
BindingResult bindingResult = getBindingResult();
|
||||
// Call each validator with the same binding result
|
||||
for (Validator validator : getValidators()) {
|
||||
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
|
||||
((SmartValidator) validator).validate(target, bindingResult, validationHints);
|
||||
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator smartValidator) {
|
||||
smartValidator.validate(target, bindingResult, validationHints);
|
||||
}
|
||||
else if (validator != null) {
|
||||
validator.validate(target, bindingResult);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -105,8 +105,8 @@ public class ObjectError extends DefaultMessageSourceResolvable {
|
||||
if (sourceType.isInstance(this.source)) {
|
||||
return sourceType.cast(this.source);
|
||||
}
|
||||
else if (this.source instanceof Throwable) {
|
||||
Throwable cause = ((Throwable) this.source).getCause();
|
||||
else if (this.source instanceof Throwable throwable) {
|
||||
Throwable cause = throwable.getCause();
|
||||
if (sourceType.isInstance(cause)) {
|
||||
return sourceType.cast(cause);
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class ObjectError extends DefaultMessageSourceResolvable {
|
||||
*/
|
||||
public boolean contains(Class<?> sourceType) {
|
||||
return (sourceType.isInstance(this.source) ||
|
||||
(this.source instanceof Throwable && sourceType.isInstance(((Throwable) this.source).getCause())));
|
||||
(this.source instanceof Throwable throwable && sourceType.isInstance(throwable.getCause())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -82,8 +82,8 @@ public abstract class ValidationUtils {
|
||||
"Validator [" + validator.getClass() + "] does not support [" + target.getClass() + "]");
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
|
||||
((SmartValidator) validator).validate(target, errors, validationHints);
|
||||
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator smartValidator) {
|
||||
smartValidator.validate(target, errors, validationHints);
|
||||
}
|
||||
else {
|
||||
validator.validate(target, errors);
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -64,7 +64,7 @@ public abstract class ValidationAnnotationUtils {
|
||||
if (hints == null) {
|
||||
return EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
return (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
|
||||
return (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
-1
@@ -30,6 +30,8 @@ import jakarta.validation.metadata.MethodDescriptor;
|
||||
import jakarta.validation.metadata.MethodType;
|
||||
import jakarta.validation.metadata.ParameterDescriptor;
|
||||
import jakarta.validation.metadata.PropertyDescriptor;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
@@ -37,6 +39,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -52,6 +55,8 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
|
||||
private static final boolean isBeanValidationPresent = ClassUtils.isPresent(
|
||||
"jakarta.validation.Validation", BeanValidationBeanRegistrationAotProcessor.class.getClassLoader());
|
||||
|
||||
private static final Log logger = LogFactory.getLog(BeanValidationBeanRegistrationAotProcessor.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
@@ -67,7 +72,22 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
|
||||
|
||||
@Nullable
|
||||
public static BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
BeanDescriptor descriptor = validator.getConstraintsForClass(registeredBean.getBeanClass());
|
||||
BeanDescriptor descriptor;
|
||||
try {
|
||||
descriptor = validator.getConstraintsForClass(registeredBean.getBeanClass());
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (KotlinDetector.isKotlinType(registeredBean.getBeanClass()) && ex instanceof ArrayIndexOutOfBoundsException) {
|
||||
// See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857
|
||||
logger.warn("Skipping validation constraint hint inference for bean " + registeredBean.getBeanName() +
|
||||
" due to an ArrayIndexOutOfBoundsException at validator level");
|
||||
}
|
||||
else {
|
||||
logger.error("Skipping validation constraint hint inference for bean " +
|
||||
registeredBean.getBeanName(), ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Set<ConstraintDescriptor<?>> constraintDescriptors = new HashSet<>();
|
||||
for (MethodDescriptor methodDescriptor : descriptor.getConstrainedMethods(MethodType.NON_GETTER, MethodType.GETTER)) {
|
||||
for (ParameterDescriptor parameterDescriptor : methodDescriptor.getParameterDescriptors()) {
|
||||
|
||||
+22
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -77,10 +77,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class AspectJAutoProxyCreatorTests {
|
||||
class AspectJAutoProxyCreatorTests {
|
||||
|
||||
@Test
|
||||
public void testAspectsAreApplied() {
|
||||
void aspectsAreApplied() {
|
||||
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
|
||||
|
||||
ITestBean tb = (ITestBean) bf.getBean("adrian");
|
||||
@@ -91,7 +91,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleAspectsWithParameterApplied() {
|
||||
void multipleAspectsWithParameterApplied() {
|
||||
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
|
||||
|
||||
ITestBean tb = (ITestBean) bf.getBean("adrian");
|
||||
@@ -100,7 +100,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAspectsAreAppliedInDefinedOrder() {
|
||||
void aspectsAreAppliedInDefinedOrder() {
|
||||
ClassPathXmlApplicationContext bf = newContext("aspectsWithOrdering.xml");
|
||||
|
||||
ITestBean tb = (ITestBean) bf.getBean("adrian");
|
||||
@@ -108,7 +108,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAspectsAndAdvisorAreApplied() {
|
||||
void aspectsAndAdvisorAreApplied() {
|
||||
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
|
||||
|
||||
ITestBean shouldBeWeaved = (ITestBean) ac.getBean("adrian");
|
||||
@@ -116,7 +116,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAspectsAndAdvisorAreAppliedEvenIfComingFromParentFactory() {
|
||||
void aspectsAndAdvisorAreAppliedEvenIfComingFromParentFactory() {
|
||||
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
|
||||
|
||||
GenericApplicationContext childAc = new GenericApplicationContext(ac);
|
||||
@@ -153,7 +153,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerThisAspect() {
|
||||
void perThisAspect() {
|
||||
ClassPathXmlApplicationContext bf = newContext("perthis.xml");
|
||||
|
||||
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
|
||||
@@ -173,7 +173,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerTargetAspect() throws SecurityException, NoSuchMethodException {
|
||||
void perTargetAspect() throws SecurityException, NoSuchMethodException {
|
||||
ClassPathXmlApplicationContext bf = newContext("pertarget.xml");
|
||||
|
||||
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
|
||||
@@ -209,7 +209,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoAdviceAspect() {
|
||||
void twoAdviceAspect() {
|
||||
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspect.xml");
|
||||
|
||||
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
|
||||
@@ -217,7 +217,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoAdviceAspectSingleton() {
|
||||
void twoAdviceAspectSingleton() {
|
||||
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectSingleton.xml");
|
||||
|
||||
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
|
||||
@@ -228,7 +228,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoAdviceAspectPrototype() {
|
||||
void twoAdviceAspectPrototype() {
|
||||
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectPrototype.xml");
|
||||
|
||||
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
|
||||
@@ -244,13 +244,13 @@ public class AspectJAutoProxyCreatorTests {
|
||||
assertThat(adrian.age()).isEqualTo(start);
|
||||
int newAge = 32;
|
||||
adrian.setAge(newAge);
|
||||
assertThat(adrian.age()).isEqualTo((start + increment));
|
||||
assertThat(adrian.age()).isEqualTo(start + increment);
|
||||
adrian.setAge(0);
|
||||
assertThat(adrian.age()).isEqualTo((start + increment * 2));
|
||||
assertThat(adrian.age()).isEqualTo(start + increment * 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdviceUsingJoinPoint() {
|
||||
void adviceUsingJoinPoint() {
|
||||
ClassPathXmlApplicationContext bf = newContext("usesJoinPointAspect.xml");
|
||||
|
||||
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
|
||||
@@ -262,7 +262,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncludeMechanism() {
|
||||
void includeMechanism() {
|
||||
ClassPathXmlApplicationContext bf = newContext("usesInclude.xml");
|
||||
|
||||
ITestBean adrian = (ITestBean) bf.getBean("adrian");
|
||||
@@ -271,7 +271,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceProxyTargetClass() {
|
||||
void forceProxyTargetClass() {
|
||||
ClassPathXmlApplicationContext bf = newContext("aspectsWithCGLIB.xml");
|
||||
|
||||
ProxyConfig pc = (ProxyConfig) bf.getBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
|
||||
@@ -280,7 +280,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithAbstractFactoryBeanAreApplied() {
|
||||
void withAbstractFactoryBeanAreApplied() {
|
||||
ClassPathXmlApplicationContext bf = newContext("aspectsWithAbstractBean.xml");
|
||||
|
||||
ITestBean adrian = (ITestBean) bf.getBean("adrian");
|
||||
@@ -289,7 +289,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetryAspect() {
|
||||
void retryAspect() {
|
||||
ClassPathXmlApplicationContext bf = newContext("retryAspect.xml");
|
||||
|
||||
UnreliableBean bean = (UnreliableBean) bf.getBean("unreliableBean");
|
||||
@@ -302,7 +302,7 @@ public class AspectJAutoProxyCreatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithBeanNameAutoProxyCreator() {
|
||||
void withBeanNameAutoProxyCreator() {
|
||||
ClassPathXmlApplicationContext bf = newContext("withBeanNameAutoProxyCreator.xml");
|
||||
|
||||
ITestBean tb = (ITestBean) bf.getBean("adrian");
|
||||
@@ -571,11 +571,11 @@ class RetryAspect {
|
||||
@SuppressWarnings("serial")
|
||||
class RetryableException extends NestedRuntimeException {
|
||||
|
||||
public RetryableException(String msg) {
|
||||
RetryableException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public RetryableException(String msg, Throwable cause) {
|
||||
RetryableException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -518,6 +518,9 @@ class AnnotationConfigApplicationContextTests {
|
||||
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS))
|
||||
.accepts(runtimeHints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(CglibConfiguration.class)
|
||||
.withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS))
|
||||
.accepts(runtimeHints);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+16
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -233,6 +233,17 @@ class AutowiredConfigurationTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValueInjectionWithRecord() {
|
||||
System.setProperty("recordBeanName", "enigma");
|
||||
try (GenericApplicationContext context = new AnnotationConfigApplicationContext(RecordBean.class)) {
|
||||
assertThat(context.getBean(RecordBean.class).name()).isEqualTo("enigma");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty("recordBeanName");
|
||||
}
|
||||
}
|
||||
|
||||
private int contentLength() throws IOException {
|
||||
return (int) new ClassPathResource("do_not_delete_me.txt").contentLength();
|
||||
}
|
||||
@@ -506,4 +517,8 @@ class AutowiredConfigurationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
record RecordBean(@Value("${recordBeanName}") String name) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -105,7 +105,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
|
||||
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
|
||||
@Retention(RUNTIME)
|
||||
@Repeatable(Exists.List.class)
|
||||
private @interface Exists {
|
||||
@interface Exists {
|
||||
|
||||
String message() default "Does not exist";
|
||||
|
||||
@@ -121,7 +121,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExistsValidator implements ConstraintValidator<Exists, String> {
|
||||
static class ExistsValidator implements ConstraintValidator<Exists, String> {
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
|
||||
@@ -129,7 +129,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static class MethodParameterLevelConstraint {
|
||||
static class MethodParameterLevelConstraint {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public String hello(@Exists String name) {
|
||||
@@ -139,7 +139,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class ConstructorParameterLevelConstraint {
|
||||
static class ConstructorParameterLevelConstraint {
|
||||
|
||||
private final String name;
|
||||
|
||||
@@ -154,7 +154,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class PropertyLevelConstraint {
|
||||
static class PropertyLevelConstraint {
|
||||
|
||||
@Exists
|
||||
private String name;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package test.aspect;
|
||||
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
* @since 6.0.6
|
||||
*/
|
||||
public class CommonPointcuts {
|
||||
|
||||
@Pointcut("execution(* getAge())")
|
||||
public void getAgeExecution() {}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2005 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
@Aspect("perthis(execution(* getAge()))")
|
||||
@Aspect("perthis(test.aspect.CommonPointcuts.getAgeExecution())")
|
||||
public class PerThisAspect {
|
||||
|
||||
private int invocations = 0;
|
||||
@@ -29,9 +29,9 @@ public class PerThisAspect {
|
||||
return this.invocations;
|
||||
}
|
||||
|
||||
@Around("execution(* getAge())")
|
||||
public int changeAge(ProceedingJoinPoint pjp) throws Throwable {
|
||||
return invocations++;
|
||||
@Around("test.aspect.CommonPointcuts.getAgeExecution()")
|
||||
public int changeAge(ProceedingJoinPoint pjp) {
|
||||
return this.invocations++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.validation.beanvalidation
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.aot.generate.GenerationContext
|
||||
import org.springframework.aot.hint.MemberCategory
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates
|
||||
import org.springframework.aot.test.generate.TestGenerationContext
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory
|
||||
import org.springframework.beans.factory.support.RegisteredBean
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition
|
||||
import org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests.*
|
||||
|
||||
/**
|
||||
* Kotlin tests for {@link BeanValidationBeanRegistrationAotProcessor}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class KotlinBeanValidationBeanRegistrationAotProcessorTests {
|
||||
|
||||
private val processor = BeanValidationBeanRegistrationAotProcessor()
|
||||
|
||||
private val generationContext: GenerationContext = TestGenerationContext()
|
||||
|
||||
@Test
|
||||
fun shouldProcessMethodParameterLevelConstraint() {
|
||||
process(MethodParameterLevelConstraint::class.java)
|
||||
Assertions.assertThat(
|
||||
RuntimeHintsPredicates.reflection().onType(ExistsValidator::class.java)
|
||||
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
|
||||
).accepts(generationContext.runtimeHints)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldSkipMethodParameterLevelConstraintWihExtension() {
|
||||
process(MethodParameterLevelConstraintWithExtension::class.java)
|
||||
Assertions.assertThat(generationContext.runtimeHints.reflection().typeHints()).isEmpty()
|
||||
}
|
||||
|
||||
private fun process(beanClass: Class<*>) {
|
||||
val contribution = createContribution(beanClass)
|
||||
contribution?.applyTo(generationContext, Mockito.mock())
|
||||
}
|
||||
|
||||
private fun createContribution(beanClass: Class<*>): BeanRegistrationAotContribution? {
|
||||
val beanFactory = DefaultListableBeanFactory()
|
||||
beanFactory.registerBeanDefinition(beanClass.name, RootBeanDefinition(beanClass))
|
||||
return processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.name))
|
||||
}
|
||||
|
||||
internal class MethodParameterLevelConstraintWithExtension {
|
||||
|
||||
@Suppress("unused")
|
||||
fun hello(name: @Exists String): String {
|
||||
return name.toHello()
|
||||
}
|
||||
|
||||
private fun String.toHello() =
|
||||
"Hello $this"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -86,6 +86,7 @@ dependencies {
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
|
||||
testImplementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
testFixturesImplementation("com.google.code.findbugs:jsr305")
|
||||
testFixturesImplementation("org.junit.platform:junit-platform-launcher")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class BindingReflectionHintsRegistrar {
|
||||
}
|
||||
|
||||
private boolean shouldSkipMembers(Class<?> type) {
|
||||
return (type.getCanonicalName() != null && type.getCanonicalName().startsWith("java.")) || type.isArray();
|
||||
return type.getCanonicalName().startsWith("java.") || type.isArray();
|
||||
}
|
||||
|
||||
private void registerReflectionHints(ReflectionHints hints, Set<Type> seen, Type type) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.aot.hint.TypeHint.Builder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ReflectionHints {
|
||||
@@ -106,7 +108,11 @@ public class ReflectionHints {
|
||||
* @see #registerType(Class, MemberCategory...)
|
||||
*/
|
||||
public ReflectionHints registerType(Class<?> type, Consumer<TypeHint.Builder> typeHint) {
|
||||
return registerType(TypeReference.of(type), typeHint);
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
if (type.getCanonicalName() != null) {
|
||||
registerType(TypeReference.of(type), typeHint);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +123,11 @@ public class ReflectionHints {
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerType(Class<?> type, MemberCategory... memberCategories) {
|
||||
return registerType(TypeReference.of(type), memberCategories);
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
if (type.getCanonicalName() != null) {
|
||||
registerType(TypeReference.of(type), memberCategories);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,11 +17,13 @@
|
||||
package org.springframework.aot.hint;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link TypeReference} based on a {@link Class}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0
|
||||
*/
|
||||
final class ReflectionTypeReference extends AbstractTypeReference {
|
||||
@@ -41,6 +43,8 @@ final class ReflectionTypeReference extends AbstractTypeReference {
|
||||
}
|
||||
|
||||
static ReflectionTypeReference of(Class<?> type) {
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
Assert.notNull(type.getCanonicalName(), "'type.getCanonicalName()' must not be null");
|
||||
return new ReflectionTypeReference(type);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.lang.Nullable;
|
||||
* a {@link Class} yet.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface TypeReference {
|
||||
@@ -68,6 +69,7 @@ public interface TypeReference {
|
||||
* Create an instance based on the specified type.
|
||||
* @param type the type to wrap
|
||||
* @return a type reference for the specified type
|
||||
* @throws IllegalArgumentException if the specified type {@linkplain Class#getCanonicalName() canonical name} is {@code null}
|
||||
*/
|
||||
static TypeReference of(Class<?> type) {
|
||||
return ReflectionTypeReference.of(type);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,6 +34,7 @@ import org.springframework.util.ClassUtils;
|
||||
* on the JDK and CGLIB proxy facilities and their core conventions.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0.3
|
||||
* @see org.springframework.aot.hint.ProxyHints
|
||||
* @see org.springframework.aot.hint.ReflectionHints
|
||||
@@ -46,7 +47,8 @@ public abstract class ClassHintUtils {
|
||||
MemberCategory.DECLARED_FIELDS);
|
||||
|
||||
private static final Consumer<TypeHint.Builder> asProxiedUserClass = hint ->
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -41,6 +41,8 @@ import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utilities for working with Kotlin Coroutines.
|
||||
*
|
||||
@@ -68,13 +70,14 @@ public abstract class CoroutinesUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a suspending function and converts it to {@link Mono} or
|
||||
* {@link Flux}. Uses an {@linkplain Dispatchers#getUnconfined() unconfined}
|
||||
* dispatcher.
|
||||
* Invoke a suspending function and converts it to {@link Mono} or {@link Flux}.
|
||||
* Uses an {@linkplain Dispatchers#getUnconfined() unconfined} dispatcher.
|
||||
* @param method the suspending function to invoke
|
||||
* @param target the target to invoke {@code method} on
|
||||
* @param args the function arguments
|
||||
* @param args the function arguments. If the {@code Continuation} argument is specified as the last argument
|
||||
* (typically {@code null}), it is ignored.
|
||||
* @return the method invocation result as reactive stream
|
||||
* @throws IllegalArgumentException if {@code method} is not a suspending function
|
||||
*/
|
||||
public static Publisher<?> invokeSuspendingFunction(Method method, Object target,
|
||||
Object... args) {
|
||||
@@ -87,20 +90,22 @@ public abstract class CoroutinesUtils {
|
||||
* @param context the coroutine context to use
|
||||
* @param method the suspending function to invoke
|
||||
* @param target the target to invoke {@code method} on
|
||||
* @param args the function arguments
|
||||
* @param args the function arguments. If the {@code Continuation} argument is specified as the last argument
|
||||
* (typically {@code null}), it is ignored.
|
||||
* @return the method invocation result as reactive stream
|
||||
* @throws IllegalArgumentException if {@code method} is not a suspending function
|
||||
* @since 6.0
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Publisher<?> invokeSuspendingFunction(CoroutineContext context, Method method, Object target,
|
||||
Object... args) {
|
||||
|
||||
Assert.isTrue(KotlinDetector.isSuspendingFunction(method), "'method' must be a suspending function");
|
||||
KFunction<?> function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method));
|
||||
if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) {
|
||||
KCallablesJvm.setAccessible(function, true);
|
||||
}
|
||||
Mono<Object> mono = MonoKt.mono(context, (scope, continuation) ->
|
||||
KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation))
|
||||
KCallables.callSuspend(function, getSuspendedFunctionArgs(method, target, args), continuation))
|
||||
.filter(result -> !Objects.equals(result, Unit.INSTANCE))
|
||||
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
|
||||
|
||||
@@ -120,10 +125,11 @@ public abstract class CoroutinesUtils {
|
||||
return mono;
|
||||
}
|
||||
|
||||
private static Object[] getSuspendedFunctionArgs(Object target, Object... args) {
|
||||
Object[] functionArgs = new Object[args.length];
|
||||
private static Object[] getSuspendedFunctionArgs(Method method, Object target, Object... args) {
|
||||
int length = (args.length == method.getParameterCount() - 1 ? args.length + 1 : args.length);
|
||||
Object[] functionArgs = new Object[length];
|
||||
functionArgs[0] = target;
|
||||
System.arraycopy(args, 0, functionArgs, 1, args.length - 1);
|
||||
System.arraycopy(args, 0, functionArgs, 1, length - 1);
|
||||
return functionArgs;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -762,7 +762,7 @@ public class ResolvableType implements Serializable {
|
||||
|
||||
/**
|
||||
* Convenience method that will {@link #getGeneric(int...) get} and
|
||||
* {@link #resolve() resolve} a specific generic parameters.
|
||||
* {@link #resolve() resolve} a specific generic parameter.
|
||||
* @param indexes the indexes that refer to the generic parameter
|
||||
* (may be omitted to return the first generic)
|
||||
* @return a resolved {@link Class} or {@code null}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -56,8 +56,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
// Try a URL connection content-length header
|
||||
URLConnection con = url.openConnection();
|
||||
customizeConnection(con);
|
||||
HttpURLConnection httpCon =
|
||||
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
|
||||
HttpURLConnection httpCon = (con instanceof HttpURLConnection huc ? huc : null);
|
||||
if (httpCon != null) {
|
||||
httpCon.setRequestMethod("HEAD");
|
||||
int code = httpCon.getResponseCode();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -226,9 +226,9 @@ public abstract class AbstractResource implements Resource {
|
||||
* @see #getDescription()
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof Resource &&
|
||||
((Resource) other).getDescription().equals(getDescription())));
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof Resource that &&
|
||||
getDescription().equals(that.getDescription())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -129,9 +129,9 @@ public class ByteArrayResource extends AbstractResource {
|
||||
* @see java.util.Arrays#equals(byte[], byte[])
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof ByteArrayResource &&
|
||||
Arrays.equals(((ByteArrayResource) other).byteArray, this.byteArray)));
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof ByteArrayResource that &&
|
||||
Arrays.equals(this.byteArray, that.byteArray)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -272,9 +272,9 @@ public class ClassPathResource extends AbstractFileResolvingResource {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
return ((obj instanceof ClassPathResource other) &&
|
||||
this.absolutePath.equals(other.absolutePath) &&
|
||||
ObjectUtils.nullSafeEquals(getClassLoader(), other.getClassLoader()));
|
||||
return ((obj instanceof ClassPathResource that) &&
|
||||
this.absolutePath.equals(that.absolutePath) &&
|
||||
ObjectUtils.nullSafeEquals(getClassLoader(), that.getClassLoader()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -72,9 +72,9 @@ public class DescriptiveResource extends AbstractResource {
|
||||
* This implementation compares the underlying description String.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof DescriptiveResource &&
|
||||
((DescriptiveResource) other).description.equals(this.description)));
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof DescriptiveResource that &&
|
||||
this.description.equals(that.description)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -396,8 +396,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof FileSystemResource other &&
|
||||
this.path.equals(other.path)));
|
||||
return (this == obj || (obj instanceof FileSystemResource that && this.path.equals(that.path)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -115,9 +115,9 @@ public class InputStreamResource extends AbstractResource {
|
||||
* This implementation compares the underlying InputStream.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof InputStreamResource &&
|
||||
((InputStreamResource) other).inputStream.equals(this.inputStream)));
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof InputStreamResource that &&
|
||||
this.inputStream.equals(that.inputStream)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -298,9 +298,8 @@ public class PathResource extends AbstractResource implements WritableResource {
|
||||
* This implementation compares the underlying Path references.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof PathResource &&
|
||||
this.path.equals(((PathResource) other).path)));
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof PathResource that && this.path.equals(that.path)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -343,9 +343,9 @@ public class UrlResource extends AbstractFileResolvingResource {
|
||||
* This implementation compares the underlying URL references.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof UrlResource resource &&
|
||||
getCleanedUrl().equals(resource.getCleanedUrl())));
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof UrlResource that &&
|
||||
getCleanedUrl().equals(that.getCleanedUrl())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -130,9 +130,9 @@ public class VfsResource extends AbstractResource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof VfsResource &&
|
||||
this.resource.equals(((VfsResource) other).resource)));
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof VfsResource that &&
|
||||
this.resource.equals(that.resource)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+18
-17
@@ -405,8 +405,19 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
* @see #doFindPathMatchingFileResources
|
||||
*/
|
||||
protected Resource convertClassLoaderURL(URL url) {
|
||||
return (ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol()) ?
|
||||
new FileSystemResource(url.getPath()) : new UrlResource(url));
|
||||
if (ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol())) {
|
||||
try {
|
||||
// URI decoding for special characters such as spaces.
|
||||
return new FileSystemResource(ResourceUtils.toURI(url).getSchemeSpecificPart());
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
// Fallback for URLs that are not valid URIs (should hardly ever happen).
|
||||
return new FileSystemResource(url.getFile());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new UrlResource(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,8 +759,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
rootDirUri = rootDirResource.getURI();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Failed to resolve %s as URI: %s".formatted(rootDirResource, ex));
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to resolve directory [%s] as URI: %s".formatted(rootDirResource, ex));
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
@@ -797,21 +808,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
|
||||
Set<Resource> result = new LinkedHashSet<>();
|
||||
try (Stream<Path> files = Files.walk(rootPath)) {
|
||||
files.filter(isMatchingFile).sorted().forEach(file -> {
|
||||
try {
|
||||
result.add(new FileSystemResource(file));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to convert file %s to an org.springframework.core.io.Resource: %s"
|
||||
.formatted(file, ex));
|
||||
}
|
||||
}
|
||||
});
|
||||
files.filter(isMatchingFile).sorted().map(FileSystemResource::new).forEach(result::add);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to complete search in directory [%s] for files matching pattern [%s]: %s"
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to search in directory [%s] for files matching pattern [%s]: %s"
|
||||
.formatted(rootPath.toAbsolutePath(), subPattern, ex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,12 +22,13 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A simple log message type for use with Commons Logging, allowing
|
||||
* for convenient lazy resolution of a given {@link Supplier} instance
|
||||
* (typically bound to a Java 8 lambda expression) or a printf-style
|
||||
* format string ({@link String#format}) in its {@link #toString()}.
|
||||
* A simple log message type for use with Commons Logging, allowing for convenient
|
||||
* lazy resolution of a given {@link Supplier} instance (typically bound to a lambda
|
||||
* expression) or a printf-style format string ({@link String#format}) in its
|
||||
* {@link #toString()}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
* @see #of(Supplier)
|
||||
* @see #format(String, Object)
|
||||
@@ -77,7 +78,7 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
/**
|
||||
* Build a lazily resolving message from the given supplier.
|
||||
* @param supplier the supplier (typically bound to a Java 8 lambda expression)
|
||||
* @param supplier the supplier (typically bound to a lambda expression)
|
||||
* @see #toString()
|
||||
*/
|
||||
public static LogMessage of(Supplier<? extends CharSequence> supplier) {
|
||||
@@ -87,56 +88,61 @@ public abstract class LogMessage implements CharSequence {
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and argument.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the argument
|
||||
* @param arg1 the argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1) {
|
||||
return new FormatMessage1(format, arg1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and arguments.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the first argument
|
||||
* @param arg2 the second argument
|
||||
* @param arg1 the first argument (can be {@code null})
|
||||
* @param arg2 the second argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1, Object arg2) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2) {
|
||||
return new FormatMessage2(format, arg1, arg2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and arguments.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the first argument
|
||||
* @param arg2 the second argument
|
||||
* @param arg3 the third argument
|
||||
* @param arg1 the first argument (can be {@code null})
|
||||
* @param arg2 the second argument (can be {@code null})
|
||||
* @param arg3 the third argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
|
||||
return new FormatMessage3(format, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and arguments.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the first argument
|
||||
* @param arg2 the second argument
|
||||
* @param arg3 the third argument
|
||||
* @param arg4 the fourth argument
|
||||
* @param arg1 the first argument (can be {@code null})
|
||||
* @param arg2 the second argument (can be {@code null})
|
||||
* @param arg3 the third argument (can be {@code null})
|
||||
* @param arg4 the fourth argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
|
||||
@Nullable Object arg4) {
|
||||
return new FormatMessage4(format, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and varargs.
|
||||
* <p>This varargs {@code format()} variant may be costly. You should therefore
|
||||
* use the individual argument variants whenever possible;
|
||||
* {@link #format(String, Object)}, {@link #format(String, Object, Object)}, etc.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param args the varargs array (costly, prefer individual arguments)
|
||||
* @param args the varargs array (can be {@code null} and can contain {@code null}
|
||||
* elements)
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object... args) {
|
||||
public static LogMessage format(String format, @Nullable Object... args) {
|
||||
return new FormatMessageX(format, args);
|
||||
}
|
||||
|
||||
@@ -170,9 +176,10 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage1 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
FormatMessage1(String format, Object arg1) {
|
||||
FormatMessage1(String format, @Nullable Object arg1) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
}
|
||||
@@ -186,11 +193,13 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage2 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
@Nullable
|
||||
private final Object arg2;
|
||||
|
||||
FormatMessage2(String format, Object arg1, Object arg2) {
|
||||
FormatMessage2(String format, @Nullable Object arg1, @Nullable Object arg2) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
@@ -205,13 +214,16 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage3 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
@Nullable
|
||||
private final Object arg2;
|
||||
|
||||
@Nullable
|
||||
private final Object arg3;
|
||||
|
||||
FormatMessage3(String format, Object arg1, Object arg2, Object arg3) {
|
||||
FormatMessage3(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
@@ -227,15 +239,20 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage4 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
@Nullable
|
||||
private final Object arg2;
|
||||
|
||||
@Nullable
|
||||
private final Object arg3;
|
||||
|
||||
@Nullable
|
||||
private final Object arg4;
|
||||
|
||||
FormatMessage4(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
|
||||
FormatMessage4(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
|
||||
@Nullable Object arg4) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
@@ -252,9 +269,10 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessageX extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object[] args;
|
||||
|
||||
FormatMessageX(String format, Object... args) {
|
||||
FormatMessageX(String format, @Nullable Object... args) {
|
||||
super(format);
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -219,6 +219,7 @@ public abstract class ResourceUtils {
|
||||
"because it does not reside in the file system: " + resourceUrl);
|
||||
}
|
||||
try {
|
||||
// URI decoding for special characters such as spaces.
|
||||
return new File(toURI(resourceUrl).getSchemeSpecificPart());
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
|
||||
@@ -25,4 +25,4 @@ import kotlin.reflect.KClass
|
||||
* @since 6.0.5
|
||||
*/
|
||||
fun JdkProxyHint.Builder.proxiedInterfaces(vararg proxiedInterfaces: KClass<*>) =
|
||||
proxiedInterfaces(*proxiedInterfaces.map { it::class.java }.toTypedArray())
|
||||
proxiedInterfaces(*proxiedInterfaces.map { it.java }.toTypedArray())
|
||||
|
||||
@@ -25,4 +25,4 @@ import kotlin.reflect.KClass
|
||||
* @since 6.0.5
|
||||
*/
|
||||
fun ProxyHints.registerJdkProxy(vararg proxiedInterfaces: KClass<*>) =
|
||||
registerJdkProxy(*proxiedInterfaces.map { it::class.java }.toTypedArray())
|
||||
registerJdkProxy(*proxiedInterfaces.map { it.java }.toTypedArray())
|
||||
|
||||
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
||||
* Tests for {@link ReflectionHints}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class ReflectionHintsTests {
|
||||
|
||||
@@ -132,6 +133,16 @@ class ReflectionHintsTests {
|
||||
assertThat(fieldHint.getName()).isEqualTo("field"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeIgnoresLambda() {
|
||||
Runnable lambda = () -> { };
|
||||
Consumer<TypeHint.Builder> hintBuilder = mock();
|
||||
this.reflectionHints.registerType(lambda.getClass());
|
||||
this.reflectionHints.registerType(lambda.getClass(), hintBuilder);
|
||||
assertThat(this.reflectionHints.typeHints()).isEmpty();
|
||||
verifyNoInteractions(hintBuilder);
|
||||
}
|
||||
|
||||
private void assertTestTypeFieldHint(Consumer<FieldHint> fieldHint) {
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
|
||||
+15
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,11 +18,13 @@ package org.springframework.aot.hint;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
/**
|
||||
@@ -30,9 +32,21 @@ import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Moritz Halbritter
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class ReflectionTypeReferenceTests {
|
||||
|
||||
@Test
|
||||
void typeReferenceWithNullClass() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeReferenceWithLambda() {
|
||||
Runnable lambda = () -> { };
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(lambda.getClass()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("reflectionTargetNames")
|
||||
void typeReferenceFromClassHasSuitableReflectionTargetName(Class<?> clazz, String binaryName) {
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -201,6 +201,14 @@ public class ReflectionHintsWriterTests {
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreLambda() throws JSONException {
|
||||
Runnable anonymousRunnable = () -> { };
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(anonymousRunnable.getClass());
|
||||
assertEquals("[]", hints);
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException {
|
||||
StringWriter out = new StringWriter();
|
||||
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
|
||||
|
||||
@@ -40,7 +40,6 @@ import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -49,9 +48,12 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.junit.jupiter.api.Named.named;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
/**
|
||||
* Unit tests for various {@link Resource} implementations.
|
||||
@@ -67,11 +69,11 @@ class ResourceTests {
|
||||
@MethodSource("resource")
|
||||
void resourceIsValid(Resource resource) throws Exception {
|
||||
assertThat(resource.getFilename()).isEqualTo("Resource.class");
|
||||
assertThat(resource.getURL().getFile().endsWith("Resource.class")).isTrue();
|
||||
assertThat(resource.getURL().getFile()).endsWith("Resource.class");
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isReadable()).isTrue();
|
||||
assertThat(resource.contentLength() > 0).isTrue();
|
||||
assertThat(resource.lastModified() > 0).isTrue();
|
||||
assertThat(resource.contentLength()).isGreaterThan(0);
|
||||
assertThat(resource.lastModified()).isGreaterThan(0);
|
||||
assertThat(resource.getContentAsByteArray()).containsExactly(Files.readAllBytes(Path.of(resource.getURI())));
|
||||
}
|
||||
|
||||
@@ -83,8 +85,8 @@ class ResourceTests {
|
||||
assertThat(relative1.getURL().getFile().endsWith("ClassPathResource.class")).isTrue();
|
||||
assertThat(relative1.exists()).isTrue();
|
||||
assertThat(relative1.isReadable()).isTrue();
|
||||
assertThat(relative1.contentLength() > 0).isTrue();
|
||||
assertThat(relative1.lastModified() > 0).isTrue();
|
||||
assertThat(relative1.contentLength()).isGreaterThan(0);
|
||||
assertThat(relative1.lastModified()).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@@ -92,11 +94,11 @@ class ResourceTests {
|
||||
void resourceCreateRelativeWithFolder(Resource resource) throws Exception {
|
||||
Resource relative2 = resource.createRelative("support/ResourcePatternResolver.class");
|
||||
assertThat(relative2.getFilename()).isEqualTo("ResourcePatternResolver.class");
|
||||
assertThat(relative2.getURL().getFile().endsWith("ResourcePatternResolver.class")).isTrue();
|
||||
assertThat(relative2.getURL().getFile()).endsWith("ResourcePatternResolver.class");
|
||||
assertThat(relative2.exists()).isTrue();
|
||||
assertThat(relative2.isReadable()).isTrue();
|
||||
assertThat(relative2.contentLength() > 0).isTrue();
|
||||
assertThat(relative2.lastModified() > 0).isTrue();
|
||||
assertThat(relative2.contentLength()).isGreaterThan(0);
|
||||
assertThat(relative2.lastModified()).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@@ -104,11 +106,11 @@ class ResourceTests {
|
||||
void resourceCreateRelativeWithDotPath(Resource resource) throws Exception {
|
||||
Resource relative3 = resource.createRelative("../SpringVersion.class");
|
||||
assertThat(relative3.getFilename()).isEqualTo("SpringVersion.class");
|
||||
assertThat(relative3.getURL().getFile().endsWith("SpringVersion.class")).isTrue();
|
||||
assertThat(relative3.getURL().getFile()).endsWith("SpringVersion.class");
|
||||
assertThat(relative3.exists()).isTrue();
|
||||
assertThat(relative3.isReadable()).isTrue();
|
||||
assertThat(relative3.contentLength() > 0).isTrue();
|
||||
assertThat(relative3.lastModified() > 0).isTrue();
|
||||
assertThat(relative3.contentLength()).isGreaterThan(0);
|
||||
assertThat(relative3.lastModified()).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@@ -122,21 +124,20 @@ class ResourceTests {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::getInputStream);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::readableChannel);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::getContentAsByteArray);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(
|
||||
() -> relative4.getContentAsString(StandardCharsets.UTF_8));
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> relative4.getContentAsString(UTF_8));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> resource() throws URISyntaxException {
|
||||
URL resourceClass = ResourceTests.class.getResource("Resource.class");
|
||||
Path resourceClassFilePath = Paths.get(resourceClass.toURI());
|
||||
return Stream.of(
|
||||
Arguments.of(Named.of("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
|
||||
Arguments.of(Named.of("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
|
||||
Arguments.of(Named.of("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
|
||||
Arguments.of(Named.of("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
|
||||
Arguments.of(Named.of("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
|
||||
Arguments.of(Named.of("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
|
||||
Arguments.of(Named.of("UrlResource", new UrlResource(resourceClass)))
|
||||
arguments(named("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
|
||||
arguments(named("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
|
||||
arguments(named("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
|
||||
arguments(named("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
|
||||
arguments(named("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
|
||||
arguments(named("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
|
||||
arguments(named("UrlResource", new UrlResource(resourceClass)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,7 +259,7 @@ class ResourceTests {
|
||||
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
|
||||
channel.read(buffer);
|
||||
buffer.rewind();
|
||||
assertThat(buffer.limit() > 0).isTrue();
|
||||
assertThat(buffer.limit()).isGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -621,6 +621,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
buffer.write(new byte[]{'a', 'b', 'c'});
|
||||
buffer.read(); // skip a
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
ByteBuffer result = buffer.toByteBuffer();
|
||||
assertThat(result.capacity()).isEqualTo(2);
|
||||
assertThat(result.remaining()).isEqualTo(2);
|
||||
@@ -639,6 +640,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
DataBuffer buffer = createDataBuffer(3);
|
||||
buffer.write(new byte[]{'a', 'b', 'c'});
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
ByteBuffer result = buffer.toByteBuffer(1, 2);
|
||||
assertThat(result.capacity()).isEqualTo(2);
|
||||
assertThat(result.remaining()).isEqualTo(2);
|
||||
|
||||
+13
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
@@ -29,6 +30,7 @@ import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -59,7 +61,7 @@ class PathMatchingResourcePatternResolverTests {
|
||||
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATION = { "NonNull.class", "NonNullApi.class", "Nullable.class" };
|
||||
|
||||
|
||||
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
|
||||
|
||||
@Nested
|
||||
@@ -88,8 +90,16 @@ class PathMatchingResourcePatternResolverTests {
|
||||
assertFilenames(pattern, expectedFilenames);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodedHashtagInPath() throws IOException {
|
||||
Path rootDir = Paths.get("src/test/resources/custom%23root").toAbsolutePath();
|
||||
URL root = new URL("file:" + rootDir + "/");
|
||||
resolver = new PathMatchingResourcePatternResolver(new DefaultResourceLoader(new URLClassLoader(new URL[] {root})));
|
||||
assertExactFilenames("classpath*:scanned/*.txt", "resource#test1.txt", "resource#test2.txt");
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithHashtagsInTheirFileNames {
|
||||
class WithHashtagsInTheirFilenames {
|
||||
|
||||
@Test
|
||||
void usingClasspathStarProtocol() {
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ class JdkProxyHintExtensionsTests {
|
||||
fun `proxiedInterfaces builder extension`() {
|
||||
every { builder.proxiedInterfaces(*anyVararg<Class<*>>()) } returns builder
|
||||
builder.proxiedInterfaces(String::class, Int::class)
|
||||
verify { builder.proxiedInterfaces(*anyVararg<Class<*>>()) }
|
||||
verify { builder.proxiedInterfaces(String::class.java, Int::class.java) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ class ProxyHintsExtensionsTests {
|
||||
fun `registerJdkProxy extension`() {
|
||||
every { proxyHints.registerJdkProxy(*anyVararg<Class<*>>()) } returns proxyHints
|
||||
proxyHints.registerJdkProxy(String::class, Int::class)
|
||||
verify { proxyHints.registerJdkProxy(*anyVararg<Class<*>>()) }
|
||||
verify { proxyHints.registerJdkProxy(String::class.java, Int::class.java) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
class KotlinCoroutinesUtilsTests {
|
||||
|
||||
@Test
|
||||
fun deferredToMono() {
|
||||
runBlocking {
|
||||
val deferred: Deferred<String> = async(Dispatchers.IO) {
|
||||
delay(10)
|
||||
"foo"
|
||||
}
|
||||
val mono = CoroutinesUtils.deferredToMono(deferred)
|
||||
StepVerifier.create(mono)
|
||||
.expectNext("foo")
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun monoToDeferred() {
|
||||
runBlocking {
|
||||
val mono = Mono.just("foo")
|
||||
val deferred = CoroutinesUtils.monoToDeferred(mono)
|
||||
Assertions.assertThat(deferred.await()).isEqualTo("foo")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invokeSuspendingFunctionWithNullContinuationParameter() {
|
||||
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunction", String::class.java, Continuation::class.java)
|
||||
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null)
|
||||
Assertions.assertThat(publisher).isInstanceOf(Mono::class.java)
|
||||
StepVerifier.create(publisher)
|
||||
.expectNext("foo")
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invokeSuspendingFunctionWithoutContinuationParameter() {
|
||||
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunction", String::class.java, Continuation::class.java)
|
||||
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo")
|
||||
Assertions.assertThat(publisher).isInstanceOf(Mono::class.java)
|
||||
StepVerifier.create(publisher)
|
||||
.expectNext("foo")
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invokeNonSuspendingFunction() {
|
||||
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("nonSuspendingFunction", String::class.java)
|
||||
Assertions.assertThatIllegalArgumentException().isThrownBy { CoroutinesUtils.invokeSuspendingFunction(method, this, "foo") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invokeSuspendingFunctionWithFlow() {
|
||||
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithFlow", Continuation::class.java)
|
||||
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this)
|
||||
Assertions.assertThat(publisher).isInstanceOf(Flux::class.java)
|
||||
StepVerifier.create(publisher)
|
||||
.expectNext("foo")
|
||||
.expectNext("bar")
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invokeSuspendingFunctionWithNullContinuationParameterAndContext() {
|
||||
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContext", String::class.java, Continuation::class.java)
|
||||
val context = CoroutineName("name")
|
||||
val mono = CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo", null) as Mono
|
||||
runBlocking {
|
||||
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invokeSuspendingFunctionWithoutContinuationParameterAndContext() {
|
||||
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContext", String::class.java, Continuation::class.java)
|
||||
val context = CoroutineName("name")
|
||||
val mono = CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo") as Mono
|
||||
runBlocking {
|
||||
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invokeNonSuspendingFunctionWithContext() {
|
||||
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("nonSuspendingFunction", String::class.java)
|
||||
val context = CoroutineName("name")
|
||||
Assertions.assertThatIllegalArgumentException().isThrownBy { CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo") }
|
||||
}
|
||||
|
||||
suspend fun suspendingFunction(value: String): String {
|
||||
delay(10)
|
||||
return value
|
||||
}
|
||||
|
||||
suspend fun suspendingFunctionWithFlow(): Flow<String> {
|
||||
delay(10)
|
||||
return flowOf("foo", "bar")
|
||||
}
|
||||
|
||||
fun nonSuspendingFunction(value: String): String {
|
||||
return value
|
||||
}
|
||||
|
||||
suspend fun suspendingFunctionWithContext(value: String): String {
|
||||
delay(10)
|
||||
Assertions.assertThat(coroutineContext[CoroutineName]?.name).isEqualTo("name")
|
||||
return value
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
test 1
|
||||
@@ -0,0 +1 @@
|
||||
test 2
|
||||
+46
@@ -1,9 +1,26 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.expression.spel
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.expression.ExpressionParser
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
class KotlinSpelReproTests {
|
||||
|
||||
@@ -18,11 +35,40 @@ class KotlinSpelReproTests {
|
||||
assertThat(expr.getValue(context)).isEqualTo("test")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `gh-26867 SpEL can process Kotlin regular function parameter`() {
|
||||
val expr = parser.parseExpression("#key.startsWith('hello')")
|
||||
context.registerFunction("get", Config::class.java.getMethod("get", String::class.java))
|
||||
context.setVariable("key", "hello world")
|
||||
assertThat(expr.getValue(context, Boolean::class.java)).isTrue()
|
||||
context.setVariable("key", "")
|
||||
assertThat(expr.getValue(context, Boolean::class.java)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `gh-26867 SpEL can process Kotlin suspending function parameter`() {
|
||||
val expr = parser.parseExpression("#key.startsWith('hello')")
|
||||
context.registerFunction("suspendingGet", Config::class.java.getMethod("suspendingGet", String::class.java, Continuation::class.java))
|
||||
context.setVariable("key", "hello world")
|
||||
assertThat(expr .getValue(context, Boolean::class.java)).isTrue()
|
||||
context.setVariable("key", "")
|
||||
assertThat(expr.getValue(context, Boolean::class.java)).isFalse()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class Config {
|
||||
|
||||
fun kotlinSupplier(): () -> String {
|
||||
return { "test" }
|
||||
}
|
||||
|
||||
fun get(key: String): Any {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
suspend fun suspendingGet(key: String) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+20
-2
@@ -346,8 +346,26 @@ public class SingleConnectionFactory implements ConnectionFactory, QueueConnecti
|
||||
if (this.connection != null) {
|
||||
closeConnection(this.connection);
|
||||
}
|
||||
this.connection = doCreateConnection();
|
||||
prepareConnection(this.connection);
|
||||
// Create new (method local) connection, which is later assigned to instance connection
|
||||
// - prevention to hold instance connection without exception listener, in case when
|
||||
// some subsequent methods (after creation of connection) throws JMSException
|
||||
Connection con = doCreateConnection();
|
||||
try {
|
||||
prepareConnection(con);
|
||||
this.connection = con;
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
// Attempt to close new (not used) connection to release possible resources
|
||||
try {
|
||||
con.close();
|
||||
}
|
||||
catch(Throwable th) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not close newly obtained JMS Connection that failed to prepare", th);
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
if (this.startedCount > 0) {
|
||||
this.connection.start();
|
||||
}
|
||||
|
||||
+76
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import jakarta.jms.Connection;
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.ExceptionListener;
|
||||
@@ -29,7 +32,10 @@ import jakarta.jms.TopicConnectionFactory;
|
||||
import jakarta.jms.TopicSession;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -341,6 +347,76 @@ public class SingleConnectionFactoryTests {
|
||||
assertThat(listener.getCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithConnectionFactoryAndExceptionListenerAndReconnectOnExceptionWithJMSException() throws Exception {
|
||||
// Throws JMSException on setExceptionListener() method, but only at the first time
|
||||
class FailingTestConnection extends TestConnection {
|
||||
private int setExceptionListenerInvocationCounter;
|
||||
|
||||
@Override
|
||||
public void setExceptionListener(ExceptionListener exceptionListener) throws JMSException {
|
||||
setExceptionListenerInvocationCounter++;
|
||||
// Throw JMSException on first invocation
|
||||
if (setExceptionListenerInvocationCounter == 1) {
|
||||
throw new JMSException("Test JMSException (setExceptionListener())");
|
||||
}
|
||||
super.setExceptionListener(exceptionListener);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare base JMS ConnectionFactory
|
||||
// - createConnection(1st) -> TestConnection,
|
||||
// - createConnection(2nd and next) -> FailingTestConnection
|
||||
TestConnection testCon = new TestConnection();
|
||||
FailingTestConnection failingCon = new FailingTestConnection();
|
||||
AtomicInteger createConnectionMethodCounter = new AtomicInteger();
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
given(cf.createConnection()).willAnswer(invocation -> {
|
||||
int methodInvocationCounter = createConnectionMethodCounter.incrementAndGet();
|
||||
return methodInvocationCounter == 1 ? testCon : failingCon;
|
||||
});
|
||||
|
||||
// Prepare SingleConnectionFactory (setReconnectOnException())
|
||||
// - internal connection exception listener should be registered
|
||||
SingleConnectionFactory scf = new SingleConnectionFactory(cf);
|
||||
scf.setReconnectOnException(true);
|
||||
Field conField = ReflectionUtils.findField(SingleConnectionFactory.class, "connection");
|
||||
conField.setAccessible(true);
|
||||
|
||||
// Get connection (1st)
|
||||
Connection con1 = scf.getConnection();
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(1);
|
||||
assertThat(con1).isNotNull();
|
||||
assertThat(con1.getExceptionListener()).isNotNull();
|
||||
assertThat(con1).isSameAs(testCon);
|
||||
// Get connection again, the same should be returned (shared connection till some problem)
|
||||
Connection con2 = scf.getConnection();
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(1);
|
||||
assertThat(con2.getExceptionListener()).isNotNull();
|
||||
assertThat(con2).isSameAs(con1);
|
||||
|
||||
// Invoke reset connection to simulate problem with connection
|
||||
// - SCF exception listener should be invoked -> connection should be set to null
|
||||
// - next attempt to invoke getConnection() must create new connection
|
||||
scf.resetConnection();
|
||||
assertThat(conField.get(scf)).isNull();
|
||||
|
||||
// Attempt to get connection again
|
||||
// - JMSException should be returned from FailingTestConnection
|
||||
// - connection should be still null (no new connection without exception listener like before fix)
|
||||
assertThatExceptionOfType(JMSException.class).isThrownBy(() -> scf.getConnection());
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(2);
|
||||
assertThat(conField.get(scf)).isNull();
|
||||
|
||||
// Attempt to get connection again -> FailingTestConnection should be returned
|
||||
// - no JMSException is thrown, exception listener should be present
|
||||
Connection con3 = scf.getConnection();
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(3);
|
||||
assertThat(con3).isNotNull();
|
||||
assertThat(con3).isSameAs(failingCon);
|
||||
assertThat(con3.getExceptionListener()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithConnectionFactoryAndLocalExceptionListenerWithCleanup() throws JMSException {
|
||||
ConnectionFactory cf = mock();
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.aot.hint.annotation.Reflective;
|
||||
|
||||
/**
|
||||
* Annotation for handling exceptions thrown from message-handling methods within a
|
||||
* specific handler class.
|
||||
@@ -32,6 +34,7 @@ import java.lang.annotation.Target;
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Reflective(MessageMappingReflectiveProcessor.class)
|
||||
public @interface MessageExceptionHandler {
|
||||
|
||||
/**
|
||||
|
||||
+17
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,20 +33,24 @@ import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
|
||||
/**
|
||||
* {@link ReflectiveProcessor} implementation for {@link MessageMapping}
|
||||
* annotated types. In addition to registering reflection hints for invoking
|
||||
* {@link ReflectiveProcessor} implementation for types annotated
|
||||
* with {@link MessageMapping @MessageMapping},
|
||||
* {@link org.springframework.messaging.simp.annotation.SubscribeMapping @SubscribeMapping}
|
||||
* and {@link MessageExceptionHandler @MessageExceptionHandler}.
|
||||
* In addition to registering reflection hints for invoking
|
||||
* the annotated method, this implementation handles:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Return types</li>
|
||||
* <li>Parameters identified as potential payloads</li>
|
||||
* <li>{@link Message} parameters</li>
|
||||
* <li>Exception classes specified via {@link MessageExceptionHandler @MessageExceptionHandler}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0
|
||||
*/
|
||||
class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
|
||||
public class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
|
||||
|
||||
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
|
||||
|
||||
@@ -58,6 +62,9 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
|
||||
}
|
||||
else if (element instanceof Method method) {
|
||||
registerMethodHints(hints, method);
|
||||
if (element.isAnnotationPresent(MessageExceptionHandler.class)) {
|
||||
registerMessageExceptionHandlerHints(hints, element.getAnnotation(MessageExceptionHandler.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +91,12 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerMessageExceptionHandlerHints(ReflectionHints hints, MessageExceptionHandler annotation) {
|
||||
for (Class<?> exceptionClass : annotation.value()) {
|
||||
hints.registerType(exceptionClass);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean couldBePayload(MethodParameter methodParameter) {
|
||||
return !methodParameter.hasParameterAnnotation(DestinationVariable.class) &&
|
||||
!methodParameter.hasParameterAnnotation(Header.class) &&
|
||||
|
||||
+14
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -170,13 +170,13 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
@SuppressWarnings("unchecked")
|
||||
private Flux<DataBuffer> extractContent(MethodParameter parameter, Message<?> message) {
|
||||
Object payload = message.getPayload();
|
||||
if (payload instanceof DataBuffer) {
|
||||
return Flux.just((DataBuffer) payload);
|
||||
if (payload instanceof DataBuffer dataBuffer) {
|
||||
return Flux.just(dataBuffer);
|
||||
}
|
||||
if (payload instanceof Publisher) {
|
||||
return Flux.from((Publisher<?>) payload).map(value -> {
|
||||
if (value instanceof DataBuffer) {
|
||||
return (DataBuffer) value;
|
||||
if (payload instanceof Publisher<?> publisher) {
|
||||
return Flux.from(publisher).map(value -> {
|
||||
if (value instanceof DataBuffer dataBuffer) {
|
||||
return dataBuffer;
|
||||
}
|
||||
String className = value.getClass().getName();
|
||||
throw getUnexpectedPayloadError(message, parameter, "Publisher<" + className + ">");
|
||||
@@ -204,11 +204,11 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
if (headerValue == null) {
|
||||
return null;
|
||||
}
|
||||
else if (headerValue instanceof String) {
|
||||
return MimeTypeUtils.parseMimeType((String) headerValue);
|
||||
else if (headerValue instanceof String stringHeader) {
|
||||
return MimeTypeUtils.parseMimeType(stringHeader);
|
||||
}
|
||||
else if (headerValue instanceof MimeType) {
|
||||
return (MimeType) headerValue;
|
||||
else if (headerValue instanceof MimeType mimeTypeHeader) {
|
||||
return mimeTypeHeader;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unexpected MimeType value: " + headerValue);
|
||||
@@ -290,12 +290,12 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
|
||||
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
|
||||
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
|
||||
Object[] validationHints = (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints});
|
||||
String name = Conventions.getVariableNameForParameter(parameter);
|
||||
return target -> {
|
||||
BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(target, name);
|
||||
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator) {
|
||||
((SmartValidator) this.validator).validate(target, bindingResult, validationHints);
|
||||
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator sv) {
|
||||
sv.validate(target, bindingResult, validationHints);
|
||||
}
|
||||
else {
|
||||
this.validator.validate(target, bindingResult);
|
||||
|
||||
+5
-5
@@ -172,8 +172,8 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
else if (payload instanceof byte[] bytes) {
|
||||
return bytes.length == 0;
|
||||
}
|
||||
else if (payload instanceof String s) {
|
||||
return !StringUtils.hasText(s);
|
||||
else if (payload instanceof String text) {
|
||||
return !StringUtils.hasText(text);
|
||||
}
|
||||
else if (payload instanceof Optional<?> optional) {
|
||||
return optional.isEmpty();
|
||||
@@ -216,11 +216,11 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
|
||||
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
|
||||
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
|
||||
Object[] validationHints = (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints});
|
||||
BeanPropertyBindingResult bindingResult =
|
||||
new BeanPropertyBindingResult(target, getParameterName(parameter));
|
||||
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator) {
|
||||
((SmartValidator) this.validator).validate(target, bindingResult, validationHints);
|
||||
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator sv) {
|
||||
sv.validate(target, bindingResult, validationHints);
|
||||
}
|
||||
else {
|
||||
this.validator.validate(target, bindingResult);
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,9 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.aot.hint.annotation.Reflective;
|
||||
import org.springframework.messaging.handler.annotation.MessageMappingReflectiveProcessor;
|
||||
|
||||
/**
|
||||
* Annotation for mapping subscription messages onto specific handler methods based
|
||||
* on the destination of a subscription. Supported with STOMP over WebSocket only
|
||||
@@ -54,6 +57,7 @@ import java.lang.annotation.Target;
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Reflective(MessageMappingReflectiveProcessor.class)
|
||||
public @interface SubscribeMapping {
|
||||
|
||||
/**
|
||||
|
||||
+43
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,16 +16,19 @@
|
||||
|
||||
package org.springframework.messaging.handler.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.Principal;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.simp.annotation.SubscribeMapping;
|
||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -39,13 +42,13 @@ public class MessageMappingReflectiveProcessorTests {
|
||||
|
||||
private final MessageMappingReflectiveProcessor processor = new MessageMappingReflectiveProcessor();
|
||||
|
||||
private final ReflectionHints hints = new ReflectionHints();
|
||||
private final RuntimeHints hints = new RuntimeHints();
|
||||
|
||||
@Test
|
||||
void registerReflectiveHintsForMethodWithReturnValue() throws NoSuchMethodException {
|
||||
Method method = SampleController.class.getDeclaredMethod("returnValue");
|
||||
processor.registerReflectionHints(hints, method);
|
||||
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
|
||||
processor.registerReflectionHints(hints.reflection(), method);
|
||||
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
|
||||
typeHint -> {
|
||||
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(OutgoingMessage.class));
|
||||
@@ -62,8 +65,8 @@ public class MessageMappingReflectiveProcessorTests {
|
||||
@Test
|
||||
void registerReflectiveHintsForMethodWithExplicitPayload() throws NoSuchMethodException {
|
||||
Method method = SampleController.class.getDeclaredMethod("explicitPayload", IncomingMessage.class);
|
||||
processor.registerReflectionHints(hints, method);
|
||||
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
|
||||
processor.registerReflectionHints(hints.reflection(), method);
|
||||
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
|
||||
typeHint -> {
|
||||
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class));
|
||||
@@ -80,8 +83,8 @@ public class MessageMappingReflectiveProcessorTests {
|
||||
@Test
|
||||
void registerReflectiveHintsForMethodWithImplicitPayload() throws NoSuchMethodException {
|
||||
Method method = SampleController.class.getDeclaredMethod("implicitPayload", IncomingMessage.class);
|
||||
processor.registerReflectionHints(hints, method);
|
||||
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
|
||||
processor.registerReflectionHints(hints.reflection(), method);
|
||||
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
|
||||
@@ -90,8 +93,8 @@ public class MessageMappingReflectiveProcessorTests {
|
||||
@Test
|
||||
void registerReflectiveHintsForMethodWithMessage() throws NoSuchMethodException {
|
||||
Method method = SampleController.class.getDeclaredMethod("message", Message.class);
|
||||
processor.registerReflectionHints(hints, method);
|
||||
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
|
||||
processor.registerReflectionHints(hints.reflection(), method);
|
||||
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
|
||||
@@ -102,8 +105,8 @@ public class MessageMappingReflectiveProcessorTests {
|
||||
Method method = SampleController.class.getDeclaredMethod("implicitPayloadWithIgnoredAnnotations",
|
||||
IncomingMessage.class, Ignored.class, Ignored.class, Ignored.class, MessageHeaders.class,
|
||||
MessageHeaderAccessor.class, Principal.class);
|
||||
processor.registerReflectionHints(hints, method);
|
||||
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
|
||||
processor.registerReflectionHints(hints.reflection(), method);
|
||||
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
|
||||
@@ -111,11 +114,26 @@ public class MessageMappingReflectiveProcessorTests {
|
||||
|
||||
@Test
|
||||
void registerReflectiveHintsForClass() {
|
||||
processor.registerReflectionHints(hints, SampleAnnotatedController.class);
|
||||
assertThat(hints.typeHints()).singleElement().satisfies(
|
||||
processor.registerReflectionHints(hints.reflection(), SampleAnnotatedController.class);
|
||||
assertThat(hints.reflection().typeHints()).singleElement().satisfies(
|
||||
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleAnnotatedController.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerReflectiveHintsForMethodWithSubscribeMapping() throws NoSuchMethodException {
|
||||
Method method = SampleController.class.getDeclaredMethod("handleSubscribe");
|
||||
processor.registerReflectionHints(hints.reflection(), method);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleController.class, "handleSubscribe")).accepts(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerReflectiveHintsForMethodWithMessageExceptionHandler() throws NoSuchMethodException {
|
||||
Method method = SampleController.class.getDeclaredMethod("handleIOException");
|
||||
processor.registerReflectionHints(hints.reflection(), method);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleController.class, "handleIOException")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(IOException.class)).accepts(hints);
|
||||
}
|
||||
|
||||
|
||||
static class SampleController {
|
||||
|
||||
@@ -145,6 +163,16 @@ public class MessageMappingReflectiveProcessorTests {
|
||||
MessageHeaderAccessor messageHeaderAccessor,
|
||||
Principal principal) {
|
||||
}
|
||||
|
||||
@SubscribeMapping("/foo")
|
||||
public String handleSubscribe() {
|
||||
return "bar";
|
||||
}
|
||||
|
||||
@MessageExceptionHandler(IOException.class)
|
||||
public void handleIOException() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MessageMapping
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -211,6 +211,7 @@ class MetadataEncoderTests {
|
||||
.encode()
|
||||
.block();
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
ByteBuf byteBuf = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT)
|
||||
.wrap(buffer.toByteBuffer())
|
||||
.getNativeBuffer();
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -272,7 +272,7 @@ public abstract class AbstractStompBrokerRelayIntegrationTests {
|
||||
|
||||
public void expectBrokerAvailabilityEvent(boolean isBrokerAvailable) throws InterruptedException {
|
||||
BrokerAvailabilityEvent event = this.eventQueue.poll(20000, TimeUnit.MILLISECONDS);
|
||||
assertThat(event).as("Times out waiting for BrokerAvailabilityEvent[" + isBrokerAvailable + "]").isNotNull();
|
||||
assertThat(event).as("Timed out waiting for BrokerAvailabilityEvent[" + isBrokerAvailable + "]").isNotNull();
|
||||
assertThat(event.isBrokerAvailable()).isEqualTo(isBrokerAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user