mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1526ceec1d | |||
| 739d5ba77b | |||
| 19ab92ef59 | |||
| 04313f062e | |||
| e634ced56b | |||
| 1502c2296e | |||
| cc5c7ba186 | |||
| 0269eb80da | |||
| 9cedcd65ef | |||
| 4e8acb9ef2 | |||
| dbbbf37ac9 | |||
| d8216d719b | |||
| 63ced3f3b4 | |||
| 89391fd94c | |||
| 5168e3a38b | |||
| d4893fb32f | |||
| 104a2033c5 | |||
| cc1514be85 | |||
| 6e9758700a | |||
| 37e8aa76e9 | |||
| 27686dc2e2 | |||
| 3b8efbe5a6 | |||
| 6f2e59a995 | |||
| 644a20ae2a | |||
| 77bc13dc8c | |||
| ab9895b5a9 | |||
| ce5c4f3b4b | |||
| cd0c26b6db | |||
| 1203ace285 | |||
| 1219943e73 | |||
| eeddf9a73a | |||
| b2a4bc8900 | |||
| 3a266e65d6 | |||
| 463138acbc | |||
| b8a4961b81 | |||
| 06d4a8fc4b | |||
| 13b0643828 | |||
| 548b2167b0 | |||
| de417beff7 | |||
| ccc6789cb5 | |||
| f73302a66e | |||
| 348482b5a0 | |||
| 25bb264bd9 | |||
| 9819e1cad5 | |||
| ce2402a581 | |||
| c2d0d292bd | |||
| bbe733def7 | |||
| 03391dfa94 | |||
| 50c29e64f8 | |||
| 22301fd069 | |||
| 11ab0b4351 | |||
| 4734c15d81 | |||
| 02af3e7a17 | |||
| 3c6f1b35ba | |||
| 7a420b28df | |||
| 63723fadb6 | |||
| ab7a2c4e70 | |||
| a87b2e0146 | |||
| bd40f0e6bb | |||
| 04186fdf0e | |||
| b2b731b0ba | |||
| a3b9098850 | |||
| 36815f4021 | |||
| 3b1b7ff594 | |||
| 95080cabfd | |||
| 017892265e | |||
| d096a33f13 | |||
| b6833ff31f | |||
| 3bc55c77ec | |||
| 824aa137f8 | |||
| 0dc44f79d5 | |||
| f481f99516 | |||
| 72ed2eaa31 | |||
| 0676c958f7 | |||
| 482519d03b | |||
| d6b7cea327 | |||
| 9eea227ab9 | |||
| 706c98228d | |||
| b83cb4a947 | |||
| 7e79d0e206 | |||
| e58ba2c665 | |||
| 0c9127d111 | |||
| c2088517b9 | |||
| 59b9057e59 | |||
| 2ec630ef45 | |||
| cf2b59b68b | |||
| f1ca29b05d | |||
| ee6a115a18 | |||
| 95a31c4e8c | |||
| e0b54e244e | |||
| f2b64ad09c | |||
| cff48fff2d | |||
| ab8cb8de6e | |||
| 434da06d1f | |||
| a37447e07f | |||
| b1cb9c5052 | |||
| 057633edb5 | |||
| 7299ff9326 | |||
| 53f1656f56 | |||
| 2691489381 | |||
| f103af4982 | |||
| b9a59853fa | |||
| d4b2a493f9 | |||
| 188cb9b24d | |||
| e3568a3f0a | |||
| e106fc0434 | |||
| ca3adf43eb |
+12
-12
@@ -68,18 +68,18 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/7.2/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/core/release/api/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.0.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/7.2/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/core/release/api/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.0.3/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("13.2.0");
|
||||
checkstyle.setToolVersion("13.3.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -49,8 +49,8 @@ javadoc {
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
classpath += files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ tasks.withType(KotlinCompilationTask.class).configureEach {
|
||||
javaSources.from = []
|
||||
compilerOptions.jvmTarget = JvmTarget.JVM_17
|
||||
compilerOptions.freeCompilerArgs.addAll(
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -330,9 +330,10 @@
|
||||
*** xref:testing/testcontext-framework/application-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/ctx-management.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
|
||||
@@ -81,6 +81,11 @@ resolvable otherwise. See
|
||||
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.http.response.flush.enabled`
|
||||
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
|
||||
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
|
||||
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
|
||||
+10
-10
@@ -151,17 +151,17 @@ injected into a `Set<MovieCatalog>` annotated with `@Qualifier("action")`.
|
||||
[TIP]
|
||||
====
|
||||
Letting qualifier values select against target bean names, within the type-matching
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point.
|
||||
If there is no other resolution indicator (such as a qualifier or a primary marker),
|
||||
for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses
|
||||
the same-named candidate, if any (either by bean name or by associated alias).
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point. If there
|
||||
is no other resolution indicator (such as a qualifier, a primary marker, or a fallback
|
||||
marker), for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses the
|
||||
same-named candidate, if any (either by bean name or by associated alias).
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
As of 6.2, the container applies fast shortcut resolution for bean name matches,
|
||||
bypassing the full type matching algorithm when the parameter name matches the
|
||||
bean name and no type, qualifier or primary conditions override the match. It is
|
||||
therefore recommendable for your parameter names to match the target bean names.
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present. As
|
||||
of 6.2, the container applies fast shortcut resolution for bean name matches, bypassing
|
||||
the full type matching algorithm when the parameter name matches the bean name and no
|
||||
type, qualifier, primary, or fallback conditions override the match. It is therefore
|
||||
recommendable for your parameter names to match the target bean names.
|
||||
====
|
||||
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
|
||||
@@ -309,7 +309,8 @@ set of multiple matches for the specific bean type (as returned by the factory m
|
||||
|
||||
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
|
||||
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
|
||||
through `@Order` values in combination with `@Primary` on a single bean for each type.
|
||||
through `@Order` values in combination with `@Primary` or `@Fallback` on a single bean
|
||||
for each type.
|
||||
====
|
||||
|
||||
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
|
||||
|
||||
+2
-1
@@ -27,4 +27,5 @@ with the `CustomAutowireConfigurer`
|
||||
|
||||
When multiple beans qualify as autowire candidates, the determination of a "`primary`" is
|
||||
as follows: If exactly one bean definition among the candidates has a `primary`
|
||||
attribute set to `true`, it is selected.
|
||||
attribute set to `true`, it is selected. For annotation-based configuration, see
|
||||
xref:core/beans/annotation-config/autowired-primary.adoc[Fine-tuning with `@Primary` or `@Fallback`].
|
||||
|
||||
@@ -556,18 +556,11 @@ Kotlin::
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`]
|
||||
bean-naming strategy. First, implement either the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`] or
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following example annotation
|
||||
and bean definition show.
|
||||
|
||||
TIP: If you run into naming conflicts due to multiple autodetected components having the
|
||||
same non-qualified class name (i.e., classes with identical names but residing in
|
||||
different packages), you may need to configure a `BeanNameGenerator` that defaults to the
|
||||
fully qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` located in package
|
||||
`org.springframework.context.annotation` can be used for such purposes.
|
||||
qualified class name when configuring the scanner, as the following examples show.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -602,6 +595,27 @@ Kotlin::
|
||||
</beans>
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you run into naming conflicts due to multiple autodetected components having the same
|
||||
non-qualified class name (for example, classes with identical names but residing in
|
||||
different packages), you can configure a `BeanNameGenerator` that defaults to the
|
||||
fully-qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` can be used for such purposes.
|
||||
|
||||
As of Spring Framework 7.0, if you encounter naming conflicts among `@Bean` methods in
|
||||
`@Configuration` classes, you can alternatively configure a
|
||||
`ConfigurationBeanNameGenerator` that generates unique bean names for `@Bean` methods.
|
||||
The `FullyQualifiedConfigurationBeanNameGenerator` can be used to generate
|
||||
fully-qualified default bean names for `@Bean` methods without an explicit `name`
|
||||
attribute — for example, `com.example.MyConfig.myBean` for an `@Bean` method named
|
||||
`myBean()` declared in `@Configuration` class `com.example.MyConfig`.
|
||||
|
||||
The `FullyQualifiedAnnotationBeanNameGenerator` and
|
||||
`FullyQualifiedConfigurationBeanNameGenerator` both reside in the
|
||||
`org.springframework.context.annotation` package.
|
||||
====
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
@@ -67,6 +67,13 @@ interface, clearly indicating the post-processor nature of that bean. Otherwise,
|
||||
Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the
|
||||
initialization of other beans in the context, this early type detection is critical.
|
||||
|
||||
Furthermore, when registering a `BeanPostProcessor` via an `@Bean` factory method,
|
||||
declare the method as `static` and ideally with no dependencies. Doing so avoids eager
|
||||
initialization of the configuration class and other beans, which would make them
|
||||
ineligible for full post-processing (such as auto-proxying). See the
|
||||
"BeanPostProcessor-returning `@Bean` methods" section in the
|
||||
{spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc for details.
|
||||
|
||||
[[beans-factory-programmatically-registering-beanpostprocessors]]
|
||||
.Programmatically registering `BeanPostProcessor` instances
|
||||
NOTE: While the recommended approach for `BeanPostProcessor` registration is through
|
||||
@@ -80,7 +87,7 @@ of execution. Note also that `BeanPostProcessor` instances registered programmat
|
||||
are always processed before those registered through auto-detection, regardless of any
|
||||
explicit ordering.
|
||||
|
||||
.`BeanPostProcessor` instances and AOP auto-proxying
|
||||
.`BeanPostProcessor` instances and early initialization
|
||||
[NOTE]
|
||||
====
|
||||
Classes that implement the `BeanPostProcessor` interface are special and are treated
|
||||
@@ -90,17 +97,23 @@ of the `ApplicationContext`. Next, all `BeanPostProcessor` instances are registe
|
||||
in a sorted fashion and applied to all further beans in the container. Because AOP
|
||||
auto-proxying is implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessor`
|
||||
instances nor the beans they directly reference are eligible for auto-proxying and,
|
||||
thus, do not have aspects woven into them.
|
||||
thus, do not have aspects woven into them. More generally, any bean that is instantiated
|
||||
during this early phase is not eligible for full post-processing by all
|
||||
`BeanPostProcessor` instances.
|
||||
|
||||
For any such bean, you should see an informational log message: `Bean someBean is not
|
||||
eligible for getting processed by all BeanPostProcessor interfaces (for example: not
|
||||
eligible for auto-proxying)`.
|
||||
For any such bean, you should see a WARN-level log message similar to the following.
|
||||
|
||||
If you have beans wired into your `BeanPostProcessor` by using autowiring or
|
||||
`@Resource` (which may fall back to autowiring), Spring might access unexpected beans
|
||||
when searching for type-matching dependency candidates and, therefore, make them
|
||||
ineligible for auto-proxying or other kinds of bean post-processing. For example, if you
|
||||
have a dependency annotated with `@Resource` where the field or setter name does not
|
||||
[quote]
|
||||
Bean 'someBean' of type [org.example.SomeType] is not eligible for getting processed by
|
||||
all BeanPostProcessors (for example: not eligible for auto-proxying).
|
||||
|
||||
To minimize the number of beans affected, register a `BeanPostProcessor` with a
|
||||
`static` `@Bean` method that has no dependencies (see the note above). If you have
|
||||
beans wired into your `BeanPostProcessor` by using autowiring or `@Resource` (which
|
||||
may fall back to autowiring), Spring might access unexpected beans when searching
|
||||
for type-matching dependency candidates and, therefore, make them ineligible for
|
||||
auto-proxying or other kinds of bean post-processing. For example, if you have a
|
||||
dependency annotated with `@Resource` where the field or setter name does not
|
||||
directly correspond to the declared name of a bean and no name attribute is used,
|
||||
Spring accesses other beans for matching them by type.
|
||||
====
|
||||
@@ -135,7 +148,7 @@ Java::
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean);
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -164,7 +177,48 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
|
||||
You can register the `InstantiationTracingBeanPostProcessor` with Java configuration
|
||||
by using a `static` `@Bean` method (recommended to avoid eager initialization of the
|
||||
configuration class and other beans):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public static InstantiationTracingBeanPostProcessor instantiationTracingBeanPostProcessor() {
|
||||
return new InstantiationTracingBeanPostProcessor();
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun instantiationTracingBeanPostProcessor() = InstantiationTracingBeanPostProcessor()
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via the
|
||||
`bean` element with XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -192,7 +246,7 @@ The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
|
||||
----
|
||||
|
||||
Notice how the `InstantiationTracingBeanPostProcessor` is merely defined. It does not
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as you would any
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as with any
|
||||
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
|
||||
script.)
|
||||
|
||||
@@ -300,6 +354,23 @@ implement the `BeanFactoryPostProcessor` interface. It uses these beans as bean
|
||||
post-processors, at the appropriate time. You can deploy these post-processor beans as
|
||||
you would any other bean.
|
||||
|
||||
When registering a `BeanFactoryPostProcessor` via an `@Bean` factory method in a
|
||||
`@Configuration` class, declare the method as `static` to avoid lifecycle conflicts
|
||||
with annotation processing (such as `@Autowired`, `@Value`, and `@PostConstruct`) in the
|
||||
configuration class. See the "BeanFactoryPostProcessor-returning `@Bean` methods"
|
||||
section in the {spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc
|
||||
for details and an example.
|
||||
|
||||
For any non-static `@Bean` factory method with a `BeanFactoryPostProcessor` return type,
|
||||
you should see an INFO-level log message similar to the following.
|
||||
|
||||
[quote]
|
||||
@Bean method MyConfig.myBfpp is non-static and returns an object assignable to Spring's
|
||||
BeanFactoryPostProcessor interface. This will result in a failure to process annotations
|
||||
such as @Autowired, @Resource, and @PostConstruct within the method's declaring
|
||||
@Configuration class. Add the 'static' modifier to this method to avoid these container
|
||||
lifecycle issues; see @Bean javadoc for complete details.
|
||||
|
||||
NOTE: As with ``BeanPostProcessor``s , you typically do not want to configure
|
||||
``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a
|
||||
`Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all.
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
`@Bean` is a method-level annotation and a direct analog of the XML `<bean/>` element.
|
||||
The annotation supports some of the attributes offered by `<bean/>`, such as:
|
||||
|
||||
* xref:core/beans/definition.adoc#beans-beanname[name]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-initializingbean[init-method]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-disposablebean[destroy-method]
|
||||
* xref:core/beans/dependencies/factory-autowire.adoc[autowiring]
|
||||
* `name`.
|
||||
|
||||
You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
`@Component`-annotated class.
|
||||
@@ -17,9 +17,10 @@ You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
== Declaring a Bean
|
||||
|
||||
To declare a bean, you can annotate a method with the `@Bean` annotation. You use this
|
||||
method to register a bean definition within an `ApplicationContext` of the type
|
||||
specified as the method's return value. By default, the bean name is the same as
|
||||
the method name. The following example shows a `@Bean` method declaration:
|
||||
method to register a bean definition within an `ApplicationContext` of the type specified
|
||||
by the method's return type. By default, the bean name is the same as the method name
|
||||
(unless a different xref:#beans-java-customizing-bean-naming[bean name generator] is
|
||||
configured). The following example shows a `@Bean` method declaration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -126,7 +127,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
However, this limits the visibility for advance type prediction to the specified
|
||||
However, this limits the visibility for advanced type prediction to the specified
|
||||
interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`)
|
||||
known to the container only once the affected singleton bean has been instantiated.
|
||||
Non-lazy singleton beans get instantiated according to their declaration order,
|
||||
@@ -473,8 +474,15 @@ Kotlin::
|
||||
== Customizing Bean Naming
|
||||
|
||||
By default, configuration classes use a `@Bean` method's name as the name of the
|
||||
resulting bean. This functionality can be overridden, however, with the `name` attribute,
|
||||
as the following example shows:
|
||||
resulting bean. However, as of Spring Framework 7.0, you can change this default strategy
|
||||
by configuring a custom
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
when bootstrapping the context or configuring component scanning. For example,
|
||||
{spring-framework-api}/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.html[`FullyQualifiedConfigurationBeanNameGenerator`]
|
||||
can be used to generate fully-qualified default bean names for `@Bean` methods without an
|
||||
explicit `name` attribute. For individual `@Bean` methods, the default or
|
||||
generator-derived name can be overridden with the `name` attribute, as the following
|
||||
example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -505,6 +513,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Bean("myThing")` is equivalent to `@Bean(name = "myThing")`.
|
||||
|
||||
[[beans-java-bean-aliasing]]
|
||||
== Bean Aliasing
|
||||
|
||||
@@ -53,7 +53,7 @@ Kotlin::
|
||||
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
|
||||
|
||||
// uses CustomValue:::compareTo
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java);
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -167,7 +167,7 @@ Kotlin::
|
||||
[CAUTION]
|
||||
====
|
||||
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>`.
|
||||
|
||||
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
|
||||
to `false`.
|
||||
@@ -312,13 +312,13 @@ Kotlin::
|
||||
|
||||
// evaluates to 'a'
|
||||
val ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(Character::class.java);
|
||||
.getValue(Char::class.java)
|
||||
|
||||
// -- Repeat --
|
||||
|
||||
// evaluates to "abcabc"
|
||||
val repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String::class.java);
|
||||
.getValue(String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -485,7 +485,7 @@ Kotlin::
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
----
|
||||
======
|
||||
|
||||
@@ -541,32 +541,7 @@ For example, if we want to overload the `ADD` operator to allow two lists to be
|
||||
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
|
||||
follows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
pubic class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@Override
|
||||
public boolean overridesOperation(Operation operation, Object left, Object right) {
|
||||
return (operation == Operation.ADD &&
|
||||
left instanceof List && right instanceof List);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object operate(Operation operation, Object left, Object right) {
|
||||
if (operation == Operation.ADD &&
|
||||
left instanceof List list1 && right instanceof List list2) {
|
||||
|
||||
List result = new ArrayList(list1);
|
||||
result.addAll(list2);
|
||||
return result;
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"No overload for operation %s and operands [%s] and [%s]"
|
||||
.formatted(operation, left, right));
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./ListConcatenation[]
|
||||
|
||||
If we register `ListConcatenation` as the `OperatorOverloader` in a
|
||||
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
|
||||
@@ -589,8 +564,8 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
StandardEvaluationContext context = StandardEvaluationContext()
|
||||
context.setOperatorOverloader(ListConcatenation())
|
||||
val context = StandardEvaluationContext()
|
||||
context.operatorOverloader = ListConcatenation()
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
|
||||
|
||||
@@ -183,9 +183,6 @@ By default, a retryable operation will be retried for any exception thrown: with
|
||||
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
|
||||
between attempts.
|
||||
|
||||
If you only need to customize the number of retry attempts, you can use the
|
||||
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A retryable operation will be executed at least once and retried at most `maxRetries`
|
||||
@@ -196,6 +193,9 @@ For example, if `maxRetries` is set to `4`, the retryable operation will be invo
|
||||
least once and at most 5 times.
|
||||
====
|
||||
|
||||
If you only need to customize the number of retry attempts, you can use the
|
||||
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1>
|
||||
|
||||
+13
-42
@@ -8,7 +8,7 @@ danger of undue coupling, because code that is meant to be used transactionally
|
||||
almost always deployed that way anyway.
|
||||
|
||||
NOTE: The standard `jakarta.transaction.Transactional` annotation is also supported as
|
||||
a drop-in replacement to Spring's own annotation. Please refer to the JTA documentation
|
||||
a drop-in replacement for Spring's own annotation. Please refer to the JTA documentation
|
||||
for more details.
|
||||
|
||||
The ease-of-use afforded by the use of the `@Transactional` annotation is best
|
||||
@@ -89,47 +89,16 @@ annotation in a `@Configuration` class. See the
|
||||
{spring-framework-api}/transaction/annotation/EnableTransactionManagement.html[javadoc]
|
||||
for full details.
|
||||
|
||||
In XML configuration, the `<tx:annotation-driven/>` tag provides similar convenience:
|
||||
The following example shows the configuration needed to enable annotation-driven transaction management:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- from the file 'context.xml' -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
include-code::./AppConfig[tag=snippet,indent=0]
|
||||
|
||||
<!-- this is the service object that we want to make transactional -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- enable the configuration of transactional behavior based on annotations -->
|
||||
<!-- a TransactionManager is still required -->
|
||||
<tx:annotation-driven transaction-manager="txManager"/> <1>
|
||||
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<!-- (this dependency is defined somewhere else) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
<1> The line that makes the bean instance transactional.
|
||||
|
||||
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>`
|
||||
tag if the bean name of the `TransactionManager` that you want to wire in has the name
|
||||
`transactionManager`. If the `TransactionManager` bean that you want to dependency-inject
|
||||
has any other name, you have to use the `transaction-manager` attribute, as in the
|
||||
preceding example.
|
||||
TIP: In programmatic configuration, the `@EnableTransactionManagement` annotation uses any
|
||||
`TransactionManager` bean in the context. In XML configuration, you can omit
|
||||
the `transaction-manager` attribute in the `<tx:annotation-driven/>` tag if the bean
|
||||
name of the `TransactionManager` that you want to wire in has the name `transactionManager`.
|
||||
If the `TransactionManager` bean has any other name, you have to use the
|
||||
`transaction-manager` attribute explicitly, as in the preceding example.
|
||||
|
||||
Reactive transactional methods use reactive return types in contrast to imperative
|
||||
programming arrangements as the following listing shows:
|
||||
@@ -234,8 +203,10 @@ on an interface, a class definition, or a method on a class. However, the mere p
|
||||
of the `@Transactional` annotation is not enough to activate the transactional behavior.
|
||||
The `@Transactional` annotation is merely metadata that can be consumed by corresponding
|
||||
runtime infrastructure which uses that metadata to configure the appropriate beans with
|
||||
transactional behavior. In the preceding example, the `<tx:annotation-driven/>` element
|
||||
switches on actual transaction management at runtime.
|
||||
transactional behavior. In the preceding examples that use programmatic configuration,
|
||||
the `@EnableTransactionManagement` annotation switches on actual transaction management
|
||||
at runtime. Whereas, in the preceding example that uses XML configuration, the
|
||||
`<tx:annotation-driven/>` element switches on actual transaction management at runtime.
|
||||
|
||||
TIP: The Spring team recommends that you annotate methods of concrete classes with the
|
||||
`@Transactional` annotation, rather than relying on annotated methods in interfaces,
|
||||
|
||||
+15
-15
@@ -49,7 +49,6 @@ Kotlin::
|
||||
<1> Injecting the `ApplicationContext`.
|
||||
======
|
||||
|
||||
|
||||
Similarly, if your test is configured to load a `WebApplicationContext`, you can inject
|
||||
the web application context into your test, as follows:
|
||||
|
||||
@@ -87,7 +86,6 @@ Kotlin::
|
||||
<2> Injecting the `WebApplicationContext`.
|
||||
======
|
||||
|
||||
|
||||
Dependency injection by using `@Autowired` is provided by the
|
||||
`DependencyInjectionTestExecutionListener`, which is configured by default
|
||||
(see xref:testing/testcontext-framework/fixture-di.adoc[Dependency Injection of Test Fixtures]).
|
||||
@@ -96,22 +94,24 @@ Dependency injection by using `@Autowired` is provided by the
|
||||
Test classes that use the TestContext framework do not need to extend any particular
|
||||
class or implement a specific interface to configure their application context. Instead,
|
||||
configuration is achieved by declaring the `@ContextConfiguration` annotation at the
|
||||
class level. If your test class does not explicitly declare application context resource
|
||||
locations or component classes, the configured `ContextLoader` determines how to load a
|
||||
context from a default location or default configuration classes. In addition to context
|
||||
resource locations and component classes, an application context can also be configured
|
||||
through application context initializers.
|
||||
class level. If your test class does not explicitly declare component classes or resource
|
||||
locations, the configured `ContextLoader` determines how to load a context from _default_
|
||||
configuration classes or a _default_ location. In addition to component classes and
|
||||
context resource locations, an application context can also be configured through
|
||||
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[context customizers]
|
||||
or xref:testing/testcontext-framework/ctx-management/initializers.adoc[context initializers].
|
||||
|
||||
The following sections explain how to use Spring's `@ContextConfiguration` annotation to
|
||||
configure a test `ApplicationContext` by using XML configuration files, Groovy scripts,
|
||||
component classes (typically `@Configuration` classes), or context initializers.
|
||||
Alternatively, you can implement and configure your own custom `SmartContextLoader` for
|
||||
advanced use cases.
|
||||
The following sections explain how to use `@ContextConfiguration` and related annotations
|
||||
to configure a test `ApplicationContext` by using component classes (typically
|
||||
`@Configuration` classes), XML configuration files, Groovy scripts, context customizers,
|
||||
or context initializers. Alternatively, you can implement and configure your own custom
|
||||
`SmartContextLoader` for advanced use cases.
|
||||
|
||||
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML resources]
|
||||
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
|
||||
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
|
||||
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing XML, Groovy Scripts, and Component Classes]
|
||||
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML Resources]
|
||||
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
|
||||
* xref:testing/testcontext-framework/ctx-management/default-config.adoc[Default Context Configuration]
|
||||
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing Component Classes, XML, and Groovy Scripts]
|
||||
* xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[Context Configuration with Context Customizers]
|
||||
* xref:testing/testcontext-framework/ctx-management/initializers.adoc[Context Configuration with Context Initializers]
|
||||
* xref:testing/testcontext-framework/ctx-management/inheritance.adoc[Context Configuration Inheritance]
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
[[testcontext-ctx-management-default-config]]
|
||||
= Default Context Configuration
|
||||
|
||||
As explained in the sections on
|
||||
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[component classes],
|
||||
xref:testing/testcontext-framework/ctx-management/xml.adoc[XML resources], and
|
||||
xref:testing/testcontext-framework/ctx-management/groovy.adoc[Groovy scripts], the
|
||||
TestContext framework will attempt to locate _default_ context configuration if you do
|
||||
not explicitly specify `@Configuration` classes, XML configuration files, or Groovy
|
||||
scripts from which the test's `ApplicationContext` should be loaded.
|
||||
|
||||
However, due to a bug in the detection algorithm, default context configuration for a
|
||||
superclass or enclosing class is currently ignored if the type hierarchy or enclosing
|
||||
class hierarchy (for `@Nested` test classes) does not declare `@ContextConfiguration`.
|
||||
|
||||
Beginning with Spring Framework 7.1, the TestContext framework will reliably detect
|
||||
**all** _default_ context configuration within a type hierarchy or enclosing class
|
||||
hierarchy above a given test class in such scenarios. Consequently, test suites may
|
||||
encounter issues after the upgrade to 7.1. For example, if a static nested
|
||||
`@Configuration` class in a superclass or enclosing class is ignored due to the
|
||||
aforementioned bug, after the bug has been fixed in 7.1 that `@Configuration` class will
|
||||
no longer be ignored, which may lead to unexpected beans in the resulting
|
||||
`ApplicationContext` our outright failures in tests.
|
||||
|
||||
In the interim, the TestContext framework logs a warning whenever it encounters _default_
|
||||
context configuration that is currently ignored — for example, a `@Configuration` class
|
||||
or XML configuration file. The remainder of this section provides guidance on how to
|
||||
address such issues if you encounter warnings in your test suite.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Annotating the affected subclass or `@Nested` class with `@ContextConfiguration` allows
|
||||
you to take matters into your own hands and specify which classes in the hierarchy are
|
||||
actually intended to contribute context configuration.
|
||||
====
|
||||
|
||||
If you do not want static nested `@Configuration` classes to be processed, you can:
|
||||
|
||||
- Remove the `@Configuration` declaration.
|
||||
- Apply `@ContextConfiguration` only where you actually want such classes to be processed.
|
||||
- Move the static nested `@Configuration` classes to standalone top-level classes so that
|
||||
they cannot be accidentally interpreted as _default_ configuration classes.
|
||||
|
||||
Similarly, if you encounter issues with _default_ XML configuration files or Groovy
|
||||
scripts being detected and you do not want them to be processed, you can:
|
||||
|
||||
- Apply `@ContextConfiguration` only where you actually want such resources to be
|
||||
processed.
|
||||
- Rename the resource files to something that does not match the default naming
|
||||
convention (such as `*-context.xml` for XML configuration) so that they cannot be
|
||||
accidentally interpreted as _default_ configuration files.
|
||||
- Move the affected resource files to a different package or filesystem location within
|
||||
your project.
|
||||
+21
-22
@@ -1,33 +1,32 @@
|
||||
[[testcontext-ctx-management-mixed-config]]
|
||||
= Mixing XML, Groovy Scripts, and Component Classes
|
||||
= Mixing Component Classes, XML, and Groovy Scripts
|
||||
|
||||
It may sometimes be desirable to mix XML configuration files, Groovy scripts, and
|
||||
component classes (typically `@Configuration` classes) to configure an
|
||||
`ApplicationContext` for your tests. For example, if you use XML configuration in
|
||||
production, you may decide that you want to use `@Configuration` classes to configure
|
||||
It may sometimes be desirable to mix component classes (typically `@Configuration`
|
||||
classes), XML configuration files, or Groovy scripts to configure an `ApplicationContext`
|
||||
for your tests. For example, if you use XML configuration in production for legacy
|
||||
reasons, you may decide that you want to use `@Configuration` classes to configure
|
||||
specific Spring-managed components for your tests, or vice versa.
|
||||
|
||||
Furthermore, some third-party frameworks (such as Spring Boot) provide first-class
|
||||
support for loading an `ApplicationContext` from different types of resources
|
||||
simultaneously (for example, XML configuration files, Groovy scripts, and
|
||||
`@Configuration` classes). The Spring Framework, historically, has not supported this for
|
||||
standard deployments. Consequently, most of the `SmartContextLoader` implementations that
|
||||
the Spring Framework delivers in the `spring-test` module support only one resource type
|
||||
for each test context. However, this does not mean that you cannot use both. One
|
||||
exception to the general rule is that the `GenericGroovyXmlContextLoader` and
|
||||
simultaneously (for example, `@Configuration` classes, XML configuration files, and
|
||||
Groovy scripts). The Spring Framework, historically, has not supported this for standard
|
||||
deployments. Consequently, most of the `SmartContextLoader` implementations that the
|
||||
Spring Framework delivers in the `spring-test` module support only one resource type for
|
||||
each test context. However, this does not mean that you cannot use a mixture of resource
|
||||
types. One exception to the general rule is that the `GenericGroovyXmlContextLoader` and
|
||||
`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy
|
||||
scripts simultaneously. Furthermore, third-party frameworks may choose to support the
|
||||
declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with
|
||||
declaration of both `classes` and `locations` through `@ContextConfiguration`, and, with
|
||||
the standard testing support in the TestContext framework, you have the following options.
|
||||
|
||||
If you want to use resource locations (for example, XML or Groovy) and `@Configuration`
|
||||
classes to configure your tests, you must pick one as the entry point, and that one must
|
||||
include or import the other. For example, in XML or Groovy scripts, you can include
|
||||
`@Configuration` classes by using component scanning or defining them as normal Spring
|
||||
beans, whereas, in a `@Configuration` class, you can use `@ImportResource` to import XML
|
||||
configuration files or Groovy scripts. Note that this behavior is semantically equivalent
|
||||
If you want to use `@Configuration` classes and resource locations (for example, XML or
|
||||
Groovy) to configure your tests, you must pick one as the entry point, and that one must
|
||||
import or include the other. For example, in a `@Configuration` class, you can use
|
||||
`@ImportResource` to import XML configuration files or Groovy scripts; whereas, in XML or
|
||||
Groovy scripts, you can include `@Configuration` classes by using component scanning or
|
||||
defining them as normal Spring beans. Note that this behavior is semantically equivalent
|
||||
to how you configure your application in production: In production configuration, you
|
||||
define either a set of XML or Groovy resource locations or a set of `@Configuration`
|
||||
classes from which your production `ApplicationContext` is loaded, but you still have the
|
||||
freedom to include or import the other type of configuration.
|
||||
|
||||
define either a set of `@Configuration` classes or a set of XML or Groovy resource
|
||||
locations from which your production `ApplicationContext` is loaded, but you still have
|
||||
the freedom to import or include the other type of configuration.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[[testcontext-ctx-management-xml]]
|
||||
= Context Configuration with XML resources
|
||||
= Context Configuration with XML Resources
|
||||
|
||||
To load an `ApplicationContext` for your tests by using XML configuration files, annotate
|
||||
your test class with `@ContextConfiguration` and configure the `locations` attribute with
|
||||
|
||||
@@ -49,10 +49,9 @@ This strategy resolves the API version from a request. The WebFlux config provid
|
||||
options to resolve from a header, query parameter, media type parameter,
|
||||
or from the URL path. You can also use a custom `ApiVersionResolver`.
|
||||
|
||||
NOTE: The path resolver always resolves the version from the specified path segment, or
|
||||
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
|
||||
resolvers.
|
||||
|
||||
The path resolver selects the version from a path segment specified by index, or
|
||||
raises `InvalidApiVersionException`, and therefore never results in `null` (no version)
|
||||
unless it is configured with a `Predicate<RequestPath>` to determine if a path is versioned.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -251,7 +251,8 @@ Kotlin::
|
||||
======
|
||||
|
||||
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
|
||||
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC config.
|
||||
mark it with `@Primary`, or mark the one declared in the MVC config with `@Fallback`, in
|
||||
order to avoid conflict.
|
||||
|
||||
|
||||
[[webflux-config-content-negotiation]]
|
||||
|
||||
@@ -49,10 +49,9 @@ This strategy resolves the API version from a request. The MVC config provides b
|
||||
options to resolve from a header, query parameter, media type parameter,
|
||||
or from the URL path. You can also use a custom `ApiVersionResolver`.
|
||||
|
||||
NOTE: The path resolver always resolves the version from the specified path segment, or
|
||||
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
|
||||
resolvers.
|
||||
|
||||
The path resolver selects the version from a path segment specified by index, or
|
||||
raises `InvalidApiVersionException`, and therefore never results in `null` (no version)
|
||||
unless it is configured with a `Predicate<RequestPath>` to determine if a path is versioned.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19,4 +19,5 @@ example shows:
|
||||
include-code::./MyController[tag=snippet,indent=0]
|
||||
|
||||
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
|
||||
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC configuration.
|
||||
mark it with `@Primary`, or mark the one declared in the MVC configuration with
|
||||
`@Fallback`, in order to avoid conflict.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[.small]#xref:web/webflux/uri-building.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
This section describes various options available in the Spring Framework to work with URI's.
|
||||
This section describes various options available in the Spring Framework to work with URIs.
|
||||
|
||||
include::partial$web/web-uris.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= UriComponents
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
`UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows:
|
||||
`UriComponentsBuilder` helps to build URIs from URI templates with variables, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -128,7 +128,7 @@ Kotlin::
|
||||
= UriBuilder
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
<<web-uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
|
||||
<<uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
|
||||
`UriBuilder`, in turn, with a `UriBuilderFactory`. Together, `UriBuilderFactory` and
|
||||
`UriBuilder` provide a pluggable mechanism to build URIs from URI templates, based on
|
||||
shared configuration, such as a base URL, encoding preferences, and other details.
|
||||
@@ -247,7 +247,7 @@ and treats deviations from the syntax as illegal.
|
||||
https://github.com/web-platform-tests/wpt/tree/master/url[URL parsing algorithm] in the
|
||||
https://url.spec.whatwg.org[WhatWG URL Living standard]. It provides lenient handling of
|
||||
a wide range of cases of unexpected input. Browsers implement this in order to handle
|
||||
leniently user typed URL's. For more details, see the URL Living Standard and URL parsing
|
||||
leniently user typed URLs. For more details, see the URL Living Standard and URL parsing
|
||||
https://github.com/web-platform-tests/wpt/tree/master/url[test cases].
|
||||
|
||||
By default, `RestClient`, `WebClient`, and `RestTemplate` use the RFC parser type, and
|
||||
@@ -255,10 +255,10 @@ expect applications to provide with URL templates that conform to RFC syntax. To
|
||||
that you can customize the `UriBuilderFactory` on any of the clients.
|
||||
|
||||
Applications and frameworks may further rely on `UriComponentsBuilder` for their own needs
|
||||
to parse user provided URL's in order to inspect and possibly validated URI components
|
||||
to parse user provided URLs in order to inspect and possibly validated URI components
|
||||
such as the scheme, host, port, path, and query. Such components can decide to use the
|
||||
WhatWG parser type in order to handle URL's more leniently, and to align with the way
|
||||
browsers parse URI's, in case of a redirect to the input URL or if it is included in a
|
||||
WhatWG parser type in order to handle URLs more leniently, and to align with the way
|
||||
browsers parse URIs, in case of a redirect to the input URL or if it is included in a
|
||||
response to a browser.
|
||||
|
||||
|
||||
@@ -373,14 +373,14 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String baseUrl = "https://example.com";
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
|
||||
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
|
||||
|
||||
// Customize the RestTemplate..
|
||||
// Customize the RestTemplate.
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(factory);
|
||||
|
||||
// Customize the WebClient..
|
||||
// Customize the WebClient.
|
||||
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
|
||||
----
|
||||
|
||||
@@ -393,12 +393,12 @@ Kotlin::
|
||||
encodingMode = EncodingMode.TEMPLATE_AND_VALUES
|
||||
}
|
||||
|
||||
// Customize the RestTemplate..
|
||||
// Customize the RestTemplate.
|
||||
val restTemplate = RestTemplate().apply {
|
||||
uriTemplateHandler = factory
|
||||
}
|
||||
|
||||
// Customize the WebClient..
|
||||
// Customize the WebClient.
|
||||
val client = WebClient.builder().uriBuilderFactory(factory).build()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"@antora/collector-extension": "1.0.2",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.7",
|
||||
"fast-xml-parser": "5.3.4",
|
||||
"fast-xml-parser": "5.3.8",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
|
||||
}
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.core.expressions.languageref.expressionsoperatorsoverloaded;
|
||||
|
||||
import org.springframework.expression.Operation;
|
||||
import org.springframework.expression.OperatorOverloader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@Override
|
||||
public boolean overridesOperation(Operation operation, Object left, Object right) {
|
||||
return (operation == Operation.ADD && left instanceof List && right instanceof List);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public Object operate(Operation operation, Object left, Object right) {
|
||||
if (operation == Operation.ADD &&
|
||||
left instanceof List list1 && right instanceof List list2) {
|
||||
|
||||
List result = new ArrayList(list1);
|
||||
result.addAll(list2);
|
||||
return result;
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"No overload for operation %s and operands [%s] and [%s]"
|
||||
.formatted(operation, left, right));
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public FooService fooService() {
|
||||
return new DefaultFooService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager txManager(DataSource dataSource) {
|
||||
return new DataSourceTransactionManager(dataSource);
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations;
|
||||
|
||||
public class DefaultFooService implements FooService {
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations;
|
||||
|
||||
public interface FooService {
|
||||
}
|
||||
-1
@@ -21,7 +21,6 @@ import org.springframework.test.web.servlet.assertj.MockMvcTester;
|
||||
import org.springframework.test.web.servlet.assertj.MvcTestResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
|
||||
-2
@@ -17,9 +17,7 @@
|
||||
package org.springframework.docs.web.websocket.stomp.websocketstompconfigurationperformance;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
|
||||
|
||||
|
||||
-2
@@ -17,9 +17,7 @@
|
||||
package org.springframework.docs.web.websocket.stomp.websocketstompconfigurationperformance;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
|
||||
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
|
||||
// tag::snippet[]
|
||||
|
||||
-5
@@ -19,13 +19,8 @@ package org.springframework.docs.web.websocket.stomp.websocketstompmessageflow;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
|
||||
// tag::snippet[]
|
||||
@Controller
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
package org.springframework.docs.web.websocket.stomp.websocketstomporderedmessages;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.core.expressions.languageref.expressionsoperatorsoverloaded
|
||||
|
||||
import org.springframework.expression.Operation
|
||||
import org.springframework.expression.OperatorOverloader
|
||||
|
||||
class ListConcatenation: OperatorOverloader {
|
||||
|
||||
override fun overridesOperation(operation: Operation, left: Any?, right: Any?): Boolean {
|
||||
return operation == Operation.ADD && left is List<*> && right is List<*>
|
||||
}
|
||||
|
||||
override fun operate(operation: Operation, left: Any?, right: Any?): Any {
|
||||
if (operation == Operation.ADD && left is List<*> && right is List<*>) {
|
||||
return left + right
|
||||
}
|
||||
|
||||
throw UnsupportedOperationException(
|
||||
"No overload for operation $operation and operands [$left] and [$right]")
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager
|
||||
import org.springframework.transaction.PlatformTransactionManager
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement
|
||||
import javax.sql.DataSource
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
fun fooService(): FooService {
|
||||
return DefaultFooService()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun txManager(dataSource: DataSource): PlatformTransactionManager {
|
||||
return DataSourceTransactionManager(dataSource)
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations
|
||||
|
||||
class DefaultFooService : FooService {
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations
|
||||
|
||||
interface FooService {
|
||||
}
|
||||
-3
@@ -16,12 +16,9 @@
|
||||
|
||||
package org.springframework.docs.testing.mockmvc.assertj.mockmvctesterintegration
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.springframework.test.web.servlet.assertj.MockMvcTester
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
|
||||
|
||||
/**
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- tag::snippet[] -->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!-- this is the service object that we want to make transactional -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- enable the configuration of transactional behavior based on annotations -->
|
||||
<!-- a TransactionManager is still required -->
|
||||
<tx:annotation-driven transaction-manager="txManager"/>
|
||||
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<!-- (this dependency is defined somewhere else) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
<!-- end::snippet[] -->
|
||||
@@ -8,19 +8,19 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.20.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.16.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.16.4"))
|
||||
api(platform("io.netty:netty-bom:4.2.10.Final"))
|
||||
api(platform("io.projectreactor:reactor-bom:2025.0.3"))
|
||||
api(platform("io.projectreactor:reactor-bom:2025.0.4"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:5.0.4"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.25.3"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.7"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.1.6"))
|
||||
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.6"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.1.7"))
|
||||
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.7"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0"))
|
||||
api(platform("org.junit:junit-bom:6.0.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.21.0"))
|
||||
api(platform("org.junit:junit-bom:6.0.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.22.0"))
|
||||
api(platform("tools.jackson:jackson-bom:3.0.4"))
|
||||
|
||||
constraints {
|
||||
@@ -31,8 +31,8 @@ dependencies {
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.13.2")
|
||||
api("com.google.protobuf:protobuf-java-util:4.33.4")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.google.protobuf:protobuf-java-util:4.34.0")
|
||||
api("com.h2database:h2:2.4.240")
|
||||
api("com.jayway.jsonpath:json-path:2.10.0")
|
||||
api("com.networknt:json-schema-validator:1.5.3")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
@@ -49,7 +49,7 @@ dependencies {
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.mockk:mockk:1.14.5")
|
||||
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-h2:1.1.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.12")
|
||||
@@ -94,7 +94,7 @@ dependencies {
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.6")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.4")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.4.2")
|
||||
api("org.apache.poi:poi-ooxml:5.5.1")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.18")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.18")
|
||||
@@ -111,7 +111,7 @@ dependencies {
|
||||
api("org.easymock:easymock:5.6.0")
|
||||
api("org.eclipse.angus:angus-mail:2.0.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.1.4")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-B13")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-RC1")
|
||||
api("org.eclipse:yasson:3.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
@@ -120,7 +120,7 @@ dependencies {
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.1")
|
||||
api("org.hamcrest:hamcrest:3.0")
|
||||
api("org.hibernate.orm:hibernate-core:7.2.4.Final")
|
||||
api("org.hibernate.orm:hibernate-core:7.2.6.Final")
|
||||
api("org.hibernate.validator:hibernate-validator:9.1.0.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.4")
|
||||
api("org.htmlunit:htmlunit:4.21.0")
|
||||
@@ -134,14 +134,14 @@ dependencies {
|
||||
api("org.python:jython-standalone:2.7.4")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.reactivestreams:reactive-streams:1.0.4")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.40.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.40.0")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.41.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.41.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.testng:testng:7.12.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-lite:1.1.0")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.4")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.4")
|
||||
api("org.yaml:snakeyaml:2.5")
|
||||
api("org.yaml:snakeyaml:2.6")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=7.0.5-SNAPSHOT
|
||||
version=7.0.6
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
@@ -61,4 +61,4 @@ void configureDeploymentRepository(Project project) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
||||
+7
@@ -40,6 +40,13 @@ import org.springframework.beans.BeansException;
|
||||
* A {@code BeanFactoryPostProcessor} may also be registered programmatically
|
||||
* with a {@code ConfigurableApplicationContext}.
|
||||
*
|
||||
* <p>When registering a {@code BeanFactoryPostProcessor} via an {@code @Bean} method
|
||||
* in a {@code @Configuration} class, use a {@code static} method to avoid eager
|
||||
* initialization of other beans in the configuration class. See the
|
||||
* "BeanFactoryPostProcessor-returning {@code @Bean} methods" section in
|
||||
* {@link org.springframework.context.annotation.Bean @Bean}'s javadoc for details
|
||||
* and an example.
|
||||
*
|
||||
* <h3>Ordering</h3>
|
||||
* <p>{@code BeanFactoryPostProcessor} beans that are autodetected in an
|
||||
* {@code ApplicationContext} will be ordered according to
|
||||
|
||||
+8
@@ -35,6 +35,14 @@ import org.springframework.beans.BeansException;
|
||||
* created. A plain {@code BeanFactory} allows for programmatic registration of
|
||||
* post-processors, applying them to all beans created through the bean factory.
|
||||
*
|
||||
* <p>When registering a {@code BeanPostProcessor} via an {@code @Bean} method in
|
||||
* a {@code @Configuration} class, use a {@code static} method with ideally no
|
||||
* dependencies in order to avoid eager initialization that can make other beans
|
||||
* ineligible for full post-processing. See the "BeanPostProcessor-returning
|
||||
* {@code @Bean} methods" section in
|
||||
* {@link org.springframework.context.annotation.Bean @Bean}'s javadoc for details
|
||||
* and an example.
|
||||
*
|
||||
* <h3>Ordering</h3>
|
||||
* <p>{@code BeanPostProcessor} beans that are autodetected in an
|
||||
* {@code ApplicationContext} will be ordered according to
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0.3
|
||||
* @see org.springframework.context.annotation.ConfigurationBeanNameGenerator
|
||||
*/
|
||||
public interface BeanNameGenerator {
|
||||
|
||||
|
||||
+3
-1
@@ -109,7 +109,9 @@ public class AnnotatedBeanDefinitionReader {
|
||||
|
||||
/**
|
||||
* Set the {@code BeanNameGenerator} to use for detected bean classes.
|
||||
* <p>The default is a {@link AnnotationBeanNameGenerator}.
|
||||
* <p>The default is an {@link AnnotationBeanNameGenerator}.
|
||||
* @see FullyQualifiedAnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
*/
|
||||
public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
|
||||
this.beanNameGenerator =
|
||||
|
||||
+8
-2
@@ -118,14 +118,20 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
|
||||
|
||||
/**
|
||||
* Provide a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader}
|
||||
* and/or {@link ClassPathBeanDefinitionScanner}, if any.
|
||||
* <p>Default is {@link AnnotationBeanNameGenerator}.
|
||||
* and/or {@link ClassPathBeanDefinitionScanner}.
|
||||
* <p>Default is {@code AnnotationBeanNameGenerator}.
|
||||
* <p>When processing {@link Configuration @Configuration} classes, a
|
||||
* {@link ConfigurationBeanNameGenerator} (such as
|
||||
* {@link FullyQualifiedConfigurationBeanNameGenerator}) also determines the
|
||||
* default names for {@link Bean @Bean} methods without an explicit {@code name}
|
||||
* attribute.
|
||||
* <p>Any call to this method must occur prior to calls to {@link #register(Class...)}
|
||||
* and/or {@link #scan(String...)}.
|
||||
* @see AnnotatedBeanDefinitionReader#setBeanNameGenerator
|
||||
* @see ClassPathBeanDefinitionScanner#setBeanNameGenerator
|
||||
* @see AnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedAnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
*/
|
||||
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
|
||||
this.reader.setBeanNameGenerator(beanNameGenerator);
|
||||
|
||||
@@ -35,48 +35,47 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* example:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }
|
||||
* </pre>
|
||||
* @Bean
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Bean Names</h3>
|
||||
*
|
||||
* <p>While a {@link #name} attribute is available, the default strategy for
|
||||
* determining the name of a bean is to use the name of the {@code @Bean} method.
|
||||
* This is convenient and intuitive, but if explicit naming is desired, the
|
||||
* {@code name} attribute (or its alias {@code value}) may be used. Also note
|
||||
* that {@code name} accepts an array of Strings, allowing for multiple names
|
||||
* (i.e. a primary bean name plus one or more aliases) for a single bean.
|
||||
* This default can be overridden by configuring a {@link ConfigurationBeanNameGenerator}
|
||||
* — for example, {@link FullyQualifiedConfigurationBeanNameGenerator} for
|
||||
* fully-qualified names. If explicit naming is desired for an individual bean, the
|
||||
* {@code name} attribute (or its alias {@link #value}) may be used. Also note that
|
||||
* {@code name} accepts an array of Strings, allowing for multiple names (i.e., a
|
||||
* primary bean name plus one or more aliases) for a single bean.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }
|
||||
* </pre>
|
||||
* @Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Profile, Scope, Lazy, DependsOn, Primary, Order</h3>
|
||||
* <h3>Profile, Scope, Lazy, DependsOn, Primary, Fallback, Order</h3>
|
||||
*
|
||||
* <p>Note that the {@code @Bean} annotation does not provide attributes for profile,
|
||||
* scope, lazy, depends-on or primary. Rather, it should be used in conjunction with
|
||||
* {@link Scope @Scope}, {@link Lazy @Lazy}, {@link DependsOn @DependsOn} and
|
||||
* scope, lazy, depends-on, or primary. Rather, it should be used in conjunction with
|
||||
* {@link Scope @Scope}, {@link Lazy @Lazy}, {@link DependsOn @DependsOn}, and
|
||||
* {@link Primary @Primary} annotations to declare those semantics. For example:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* @Profile("production")
|
||||
* @Scope("prototype")
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }
|
||||
* </pre>
|
||||
* @Bean
|
||||
* @Profile("production")
|
||||
* @Scope("prototype")
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }</pre>
|
||||
*
|
||||
* The semantics of the above-mentioned annotations match their use at the component
|
||||
* The semantics of the aforementioned annotations match their use at the component
|
||||
* class level: {@code @Profile} allows for selective inclusion of certain beans.
|
||||
* {@code @Scope} changes the bean's scope from singleton to the specified scope.
|
||||
* {@code @Lazy} only has an actual effect in case of the default singleton scope.
|
||||
@@ -85,6 +84,9 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* through direct references, which is typically helpful for singleton startup.
|
||||
* {@code @Primary} is a mechanism to resolve ambiguity at the injection point level
|
||||
* if a single target component needs to be injected but several beans match by type.
|
||||
* {@link Fallback @Fallback} marks a bean as a fallback candidate in such scenarios;
|
||||
* if all beans but one among multiple matching candidates are marked as fallback, the
|
||||
* remaining bean will be selected.
|
||||
*
|
||||
* <p>Additionally, {@code @Bean} methods may also declare qualifier annotations
|
||||
* and {@link org.springframework.core.annotation.Order @Order} values, to be
|
||||
@@ -100,8 +102,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* orthogonal concern determined by dependency relationships and {@code @DependsOn}
|
||||
* declarations as mentioned above. Also, {@link jakarta.annotation.Priority} is not
|
||||
* available at this level since it cannot be declared on methods; its semantics can
|
||||
* be modeled through {@code @Order} values in combination with {@code @Primary} on
|
||||
* a single bean per type.
|
||||
* be modeled through {@code @Order} values in combination with {@code @Primary} or
|
||||
* {@code @Fallback} on a single bean per type.
|
||||
*
|
||||
* <h3>{@code @Bean} Methods in {@code @Configuration} Classes</h3>
|
||||
*
|
||||
@@ -119,17 +121,17 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* @Configuration
|
||||
* public class AppConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public FooService fooService() {
|
||||
* return new FooService(fooRepository());
|
||||
* }
|
||||
* @Bean
|
||||
* public FooService fooService() {
|
||||
* return new FooService(fooRepository());
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public FooRepository fooRepository() {
|
||||
* return new JdbcFooRepository(dataSource());
|
||||
* }
|
||||
* @Bean
|
||||
* public FooRepository fooRepository() {
|
||||
* return new JdbcFooRepository(dataSource());
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* // ...
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>{@code @Bean} <em>Lite</em> Mode</h3>
|
||||
@@ -158,20 +160,21 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* <pre class="code">
|
||||
* @Component
|
||||
* public class Calculator {
|
||||
* public int sum(int a, int b) {
|
||||
* return a+b;
|
||||
* }
|
||||
* public int sum(int a, int b) {
|
||||
* return a+b;
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public MyBean myBean() {
|
||||
* return new MyBean();
|
||||
* }
|
||||
* @Bean
|
||||
* public MyBean myBean() {
|
||||
* return new MyBean();
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Bootstrapping</h3>
|
||||
*
|
||||
* <p>See the @{@link Configuration} javadoc for further details including how to bootstrap
|
||||
* the container using {@link AnnotationConfigApplicationContext} and friends.
|
||||
* <p>See the {@link Configuration @Configuration} javadoc for further details
|
||||
* including how to bootstrap the container using
|
||||
* {@link AnnotationConfigApplicationContext} and friends.
|
||||
*
|
||||
* <h3>{@code BeanFactoryPostProcessor}-returning {@code @Bean} methods</h3>
|
||||
*
|
||||
@@ -183,20 +186,44 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* lifecycle issues, mark {@code BFPP}-returning {@code @Bean} methods as {@code static}. For example:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* public static PropertySourcesPlaceholderConfigurer pspc() {
|
||||
* // instantiate, configure and return pspc ...
|
||||
* }
|
||||
* </pre>
|
||||
* @Bean
|
||||
* public static PropertySourcesPlaceholderConfigurer pspc() {
|
||||
* // instantiate, configure and return pspc ...
|
||||
* }</pre>
|
||||
*
|
||||
* By marking this method as {@code static}, it can be invoked without causing instantiation of its
|
||||
* declaring {@code @Configuration} class, thus avoiding the above-mentioned lifecycle conflicts.
|
||||
* declaring {@code @Configuration} class, thus avoiding the aforementioned lifecycle conflicts.
|
||||
* Note however that {@code static} {@code @Bean} methods will not be enhanced for scoping and AOP
|
||||
* semantics as mentioned above. This works out in {@code BFPP} cases, as they are not typically
|
||||
* referenced by other {@code @Bean} methods. As a reminder, an INFO-level log message will be
|
||||
* issued for any non-static {@code @Bean} methods having a return type assignable to
|
||||
* {@code BeanFactoryPostProcessor}.
|
||||
*
|
||||
* <h3>{@code BeanPostProcessor}-returning {@code @Bean} methods</h3>
|
||||
*
|
||||
* <p>Similarly, special consideration must be taken for {@code @Bean} methods that return Spring
|
||||
* {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor}
|
||||
* ({@code BPP}) types. Because {@code BPP} objects must be instantiated early in the container
|
||||
* lifecycle, a non-static {@code @Bean} method that returns a {@code BPP} will cause eager
|
||||
* initialization of its declaring {@code @Configuration} class, which can make other beans in the
|
||||
* {@code @Configuration} class (as well as depencencies of those beans) ineligible for full
|
||||
* post-processing. To avoid these lifecycle issues, mark {@code BPP}-returning {@code @Bean}
|
||||
* methods as {@code static}. For example:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* public static MyBeanPostProcessor myBeanPostProcessor() {
|
||||
* return new MyBeanPostProcessor();
|
||||
* }</pre>
|
||||
*
|
||||
* By marking this method as {@code static}, it can be invoked without causing instantiation of its
|
||||
* declaring {@code @Configuration} class. Furthermore, the method should ideally not declare any
|
||||
* dependencies so that the container does not need to instantiate other beans to create the
|
||||
* post-processor, which would make those beans ineligible for post-processing as well. For any such
|
||||
* bean, you should see a WARN-level log message similar to the following: "Bean 'someBean' of type
|
||||
* [org.example.SomeType] is not eligible for getting processed by all BeanPostProcessors (for example:
|
||||
* not eligible for auto-proxying)".
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Costin Leau
|
||||
* @author Chris Beams
|
||||
@@ -208,9 +235,13 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* @see DependsOn
|
||||
* @see Lazy
|
||||
* @see Primary
|
||||
* @see Fallback
|
||||
* @see org.springframework.stereotype.Component
|
||||
* @see org.springframework.beans.factory.annotation.Autowired
|
||||
* @see org.springframework.beans.factory.annotation.Value
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator
|
||||
* @see ComponentScan#nameGenerator()
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -229,10 +260,11 @@ public @interface Bean {
|
||||
|
||||
/**
|
||||
* The name of this bean, or if several names, a primary bean name plus aliases.
|
||||
* <p>If left unspecified, the name of the bean is the name of the annotated method.
|
||||
* If specified, the method name is ignored.
|
||||
* <p>See the "Bean Names" section in the {@linkplain Bean class-level documentation}
|
||||
* for details on how the bean name is determined if this attribute is left
|
||||
* unspecified.
|
||||
* <p>The bean name and aliases may also be configured via the {@link #value}
|
||||
* attribute if no other attributes are declared.
|
||||
* attribute.
|
||||
* @see #value
|
||||
*/
|
||||
@AliasFor("value")
|
||||
|
||||
+9
-6
@@ -43,11 +43,11 @@ import org.springframework.util.PatternMatchUtils;
|
||||
* or {@code ApplicationContext}).
|
||||
*
|
||||
* <p>Candidate classes are detected through configurable type filters. The
|
||||
* default filters include classes that are annotated with Spring's
|
||||
* {@link org.springframework.stereotype.Component @Component},
|
||||
* default filters include classes that are annotated or meta-annotated with Spring's
|
||||
* {@link org.springframework.stereotype.Component @Component} annotation, such as the
|
||||
* {@link org.springframework.stereotype.Repository @Repository},
|
||||
* {@link org.springframework.stereotype.Service @Service}, or
|
||||
* {@link org.springframework.stereotype.Controller @Controller} stereotype.
|
||||
* {@link org.springframework.stereotype.Service @Service}, and
|
||||
* {@link org.springframework.stereotype.Controller @Controller} stereotypes.
|
||||
*
|
||||
* <p>Also supports JSR-330's {@link jakarta.inject.Named} annotations, if available.
|
||||
*
|
||||
@@ -204,8 +204,11 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BeanNameGenerator to use for detected bean classes.
|
||||
* <p>Default is a {@link AnnotationBeanNameGenerator}.
|
||||
* Set the {@link BeanNameGenerator} to use for detected bean classes.
|
||||
* <p>Default is an {@code AnnotationBeanNameGenerator}.
|
||||
* @see AnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedAnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
*/
|
||||
public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
|
||||
this.beanNameGenerator =
|
||||
|
||||
+6
-2
@@ -109,14 +109,18 @@ public @interface ComponentScan {
|
||||
/**
|
||||
* The {@link BeanNameGenerator} class to be used for naming detected components
|
||||
* within the Spring container.
|
||||
* <p>The default value of the {@link BeanNameGenerator} interface itself indicates
|
||||
* <p>The default value of the {@code BeanNameGenerator} interface itself indicates
|
||||
* that the scanner used to process this {@code @ComponentScan} annotation should
|
||||
* use its inherited bean name generator, for example, the default
|
||||
* {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
|
||||
* application context at bootstrap time.
|
||||
* application context at bootstrap time. If a {@link ConfigurationBeanNameGenerator}
|
||||
* is used (such as {@link FullyQualifiedConfigurationBeanNameGenerator}), it
|
||||
* also affects the default names for {@link Bean @Bean} methods in
|
||||
* {@link Configuration @Configuration} classes.
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
|
||||
* @see AnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedAnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
*/
|
||||
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
|
||||
|
||||
|
||||
@@ -437,6 +437,8 @@ public @interface Configuration {
|
||||
* <p>Alias for {@link Component#value}.
|
||||
* @return the explicit component name, if any (or empty String otherwise)
|
||||
* @see AnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedAnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
*/
|
||||
@AliasFor(annotation = Component.class)
|
||||
String value() default "";
|
||||
|
||||
+3
-2
@@ -37,9 +37,10 @@ public interface ConfigurationBeanNameGenerator extends BeanNameGenerator {
|
||||
|
||||
/**
|
||||
* Derive a default bean name for the given {@link Bean @Bean} method,
|
||||
* providing the {@link Bean#name() name} attribute specified.
|
||||
* taking into account the specified {@link Bean#name() name} attribute.
|
||||
* @param beanMethod the method metadata for the {@link Bean @Bean} method
|
||||
* @param beanName the {@link Bean#name() name} attribute or {@code null} if non is specified
|
||||
* @param beanName the {@link Bean#name() name} attribute or {@code null} if
|
||||
* none is specified
|
||||
* @return the default bean name to use
|
||||
*/
|
||||
String deriveBeanName(MethodMetadata beanMethod, @Nullable String beanName);
|
||||
|
||||
+5
-5
@@ -209,7 +209,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
String beanName = (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) ? explicitNames[0] : null;
|
||||
String localBeanName = defaultBeanName(beanName, methodName);
|
||||
beanName = (this.importBeanNameGenerator instanceof ConfigurationBeanNameGenerator cbng ?
|
||||
cbng.deriveBeanName(metadata, beanName) : defaultBeanName(beanName, methodName));
|
||||
cbng.deriveBeanName(metadata, beanName) : localBeanName);
|
||||
if (explicitNames.length > 0) {
|
||||
// Register aliases even when overridden below.
|
||||
for (int i = 1; i < explicitNames.length; i++) {
|
||||
@@ -303,8 +303,8 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Registering bean definition for @Bean method %s.%s()"
|
||||
.formatted(configClass.getMetadata().getClassName(), beanName));
|
||||
logger.trace("Registering bean definition for @Bean method %s.%s() with bean name '%s'"
|
||||
.formatted(configClass.getMetadata().getClassName(), methodName, beanName));
|
||||
}
|
||||
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
|
||||
}
|
||||
@@ -368,8 +368,8 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
"@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Skipping bean definition for %s: a definition for bean '%s' already exists. " +
|
||||
"This top-level bean definition is considered as an override.".formatted(beanMethod, beanName));
|
||||
logger.debug(("Skipping bean definition for %s: a definition for bean '%s' already exists. " +
|
||||
"This top-level bean definition is considered as an override.").formatted(beanMethod, beanName));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+7
-7
@@ -387,13 +387,13 @@ class ConfigurationClassEnhancer {
|
||||
// create the bean instance.
|
||||
if (logger.isInfoEnabled() &&
|
||||
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
|
||||
logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
|
||||
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
|
||||
"result in a failure to process annotations such as @Autowired, " +
|
||||
"@Resource and @PostConstruct within the method's declaring " +
|
||||
"@Configuration class. Add the 'static' modifier to this method to avoid " +
|
||||
"these container lifecycle issues; see @Bean javadoc for complete details.",
|
||||
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
|
||||
logger.info("""
|
||||
@Bean method %s.%s is non-static and returns an object assignable to Spring's \
|
||||
BeanFactoryPostProcessor interface. This will result in a failure to process \
|
||||
annotations such as @Autowired, @Resource, and @PostConstruct within the method's \
|
||||
declaring @Configuration class. Add the 'static' modifier to this method to avoid \
|
||||
these container lifecycle issues; see @Bean javadoc for complete details."""
|
||||
.formatted(beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
|
||||
}
|
||||
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
|
||||
}
|
||||
|
||||
+10
-4
@@ -242,12 +242,15 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
|
||||
/**
|
||||
* Set the {@link BeanNameGenerator} to be used when triggering component scanning
|
||||
* from {@link Configuration} classes and when registering {@link Import}'ed
|
||||
* configuration classes. The default is a standard {@link AnnotationBeanNameGenerator}
|
||||
* for scanned components (compatible with the default in {@link ClassPathBeanDefinitionScanner})
|
||||
* from {@link Configuration @Configuration} classes and when registering
|
||||
* {@link Import @Import}'ed configuration classes.
|
||||
* <p>The default is a standard {@link AnnotationBeanNameGenerator} for scanned
|
||||
* components (compatible with the default in {@link ClassPathBeanDefinitionScanner})
|
||||
* and a variant thereof for imported configuration classes (using unique fully-qualified
|
||||
* class names instead of standard component overriding).
|
||||
* <p>Note that this strategy does <em>not</em> apply to {@link Bean} methods.
|
||||
* <p>If the supplied bean name generator is a {@link ConfigurationBeanNameGenerator}
|
||||
* (such as {@link FullyQualifiedConfigurationBeanNameGenerator}), it also affects the
|
||||
* default names for {@link Bean @Bean} methods in configuration classes.
|
||||
* <p>This setter is typically only appropriate when configuring the post-processor as a
|
||||
* standalone bean definition in XML, for example, not using the dedicated {@code AnnotationConfig*}
|
||||
* application contexts or the {@code <context:annotation-config>} element. Any bean name
|
||||
@@ -255,6 +258,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
* @since 3.1.1
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
|
||||
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
|
||||
* @see AnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedAnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
*/
|
||||
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
|
||||
Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null");
|
||||
|
||||
+34
-5
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.support;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
@@ -32,6 +33,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* configuration methods and corresponding semantic definitions.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 4.3
|
||||
* @see ResourceBundleMessageSource
|
||||
* @see ReloadableResourceBundleMessageSource
|
||||
@@ -40,7 +42,7 @@ public abstract class AbstractResourceBasedMessageSource extends AbstractMessage
|
||||
|
||||
private final Set<String> basenameSet = new LinkedHashSet<>(4);
|
||||
|
||||
private @Nullable String defaultEncoding;
|
||||
private @Nullable Charset defaultCharset;
|
||||
|
||||
private boolean fallbackToSystemLocale = true;
|
||||
|
||||
@@ -53,7 +55,7 @@ public abstract class AbstractResourceBasedMessageSource extends AbstractMessage
|
||||
* Set a single basename, following the basic ResourceBundle convention
|
||||
* of not specifying file extension or language codes. The resource location
|
||||
* format is up to the specific {@code MessageSource} implementation.
|
||||
* <p>Regular and XMl properties files are supported: for example, "messages" will find
|
||||
* <p>Regular and XML properties files are supported: for example, "messages" will find
|
||||
* a "messages.properties", "messages_en.properties" etc arrangement as well
|
||||
* as "messages.xml", "messages_en.xml" etc.
|
||||
* @param basename the single basename
|
||||
@@ -118,25 +120,52 @@ public abstract class AbstractResourceBasedMessageSource extends AbstractMessage
|
||||
|
||||
/**
|
||||
* Set the default charset to use for parsing properties files.
|
||||
* Used if no file-specific charset is specified for a file.
|
||||
* <p>Used if no file-specific charset is specified for a file.
|
||||
* <p>The effective default is the {@code java.util.Properties}
|
||||
* default encoding: ISO-8859-1. A {@code null} value indicates
|
||||
* the platform default encoding.
|
||||
* <p>Only applies to classic properties files, not to XML files.
|
||||
* @param defaultEncoding the default charset
|
||||
* @see #setDefaultCharset(Charset)
|
||||
*/
|
||||
public void setDefaultEncoding(@Nullable String defaultEncoding) {
|
||||
this.defaultEncoding = defaultEncoding;
|
||||
this.defaultCharset = (defaultEncoding != null ? Charset.forName(defaultEncoding) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default charset to use for parsing properties files, if any.
|
||||
* @since 4.3
|
||||
* @see #getDefaultCharset()
|
||||
*/
|
||||
protected @Nullable String getDefaultEncoding() {
|
||||
return this.defaultEncoding;
|
||||
return (this.defaultCharset != null ? this.defaultCharset.name() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default {@link Charset} to use for parsing properties files.
|
||||
* <p>Used if no file-specific charset is specified for a file.
|
||||
* <p>The effective default is the {@code java.util.Properties}
|
||||
* default encoding: ISO-8859-1. A {@code null} value indicates
|
||||
* the platform default encoding.
|
||||
* <p>Only applies to classic properties files, not to XML files.
|
||||
* @param defaultCharset the default charset
|
||||
* @since 7.0.6
|
||||
* @see #setDefaultEncoding(String)
|
||||
*/
|
||||
public void setDefaultCharset(@Nullable Charset defaultCharset) {
|
||||
this.defaultCharset = defaultCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default charset to use for parsing properties files, if any.
|
||||
* @since 7.0.6
|
||||
* @see #setDefaultCharset(Charset)
|
||||
*/
|
||||
protected @Nullable Charset getDefaultCharset() {
|
||||
return this.defaultCharset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether to fall back to the system Locale if no files for a specific
|
||||
* Locale have been found. Default is "true"; if this is turned off, the only
|
||||
|
||||
+4
-1
@@ -24,6 +24,8 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
@@ -98,7 +100,8 @@ class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor,
|
||||
try {
|
||||
ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster();
|
||||
multicaster.removeApplicationListener(applicationListener);
|
||||
multicaster.removeApplicationListenerBean(beanName);
|
||||
multicaster.removeApplicationListenerBean(
|
||||
bean instanceof FactoryBean ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// ApplicationEventMulticaster not initialized yet - no need to remove a listener
|
||||
|
||||
+26
-19
@@ -19,6 +19,7 @@ package org.springframework.context.support;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -80,9 +81,10 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @author Sam Brannen
|
||||
* @see #setCacheSeconds
|
||||
* @see #setBasenames
|
||||
* @see #setDefaultEncoding
|
||||
* @see #setDefaultCharset
|
||||
* @see #setFileEncodings
|
||||
* @see #setPropertiesPersister
|
||||
* @see #setResourceLoader
|
||||
@@ -93,10 +95,12 @@ import org.springframework.util.StringUtils;
|
||||
public class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource
|
||||
implements ResourceLoaderAware {
|
||||
|
||||
private static final String PROPERTIES_EXTENSION = ".properties";
|
||||
|
||||
private static final String XML_EXTENSION = ".xml";
|
||||
|
||||
|
||||
private List<String> fileExtensions = List.of(".properties", XML_EXTENSION);
|
||||
private List<String> fileExtensions = List.of(PROPERTIES_EXTENSION, XML_EXTENSION);
|
||||
|
||||
private @Nullable Properties fileEncodings;
|
||||
|
||||
@@ -135,7 +139,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
|
||||
/**
|
||||
* Set per-file charsets to use for parsing properties files.
|
||||
* <p>Only applies to classic properties files, not to XML files.
|
||||
* @param fileEncodings a Properties with filenames as keys and charset
|
||||
* @param fileEncodings a Properties object with filenames as keys and charset
|
||||
* names as values. Filenames have to match the basename syntax,
|
||||
* with optional locale-specific components: for example, "WEB-INF/messages"
|
||||
* or "WEB-INF/messages_en".
|
||||
@@ -378,18 +382,18 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
|
||||
StringBuilder temp = new StringBuilder(basename);
|
||||
|
||||
temp.append('_');
|
||||
if (language.length() > 0) {
|
||||
if (!language.isEmpty()) {
|
||||
temp.append(language);
|
||||
result.add(0, temp.toString());
|
||||
}
|
||||
|
||||
temp.append('_');
|
||||
if (country.length() > 0) {
|
||||
if (!country.isEmpty()) {
|
||||
temp.append(country);
|
||||
result.add(0, temp.toString());
|
||||
}
|
||||
|
||||
if (variant.length() > 0 && (language.length() > 0 || country.length() > 0)) {
|
||||
if (!variant.isEmpty() && (!language.isEmpty() || !country.isEmpty())) {
|
||||
temp.append('_').append(variant);
|
||||
result.add(0, temp.toString());
|
||||
}
|
||||
@@ -558,33 +562,36 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
|
||||
*/
|
||||
protected Properties loadProperties(Resource resource, String filename) throws IOException {
|
||||
Properties props = newProperties();
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
String resourceFilename = resource.getFilename();
|
||||
if (resourceFilename != null && resourceFilename.endsWith(XML_EXTENSION)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loading properties [" + resource.getFilename() + "]");
|
||||
}
|
||||
this.propertiesPersister.loadFromXml(props, is);
|
||||
this.propertiesPersister.loadFromXml(props, inputStream);
|
||||
}
|
||||
else {
|
||||
String encoding = null;
|
||||
Charset charset = null;
|
||||
if (this.fileEncodings != null) {
|
||||
encoding = this.fileEncodings.getProperty(filename);
|
||||
}
|
||||
if (encoding == null) {
|
||||
encoding = getDefaultEncoding();
|
||||
}
|
||||
if (encoding != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'");
|
||||
String charsetName = this.fileEncodings.getProperty(filename);
|
||||
if (charsetName != null) {
|
||||
charset = Charset.forName(charsetName);
|
||||
}
|
||||
this.propertiesPersister.load(props, new InputStreamReader(is, encoding));
|
||||
}
|
||||
if (charset == null) {
|
||||
charset = getDefaultCharset();
|
||||
}
|
||||
if (charset != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + charset + "'");
|
||||
}
|
||||
this.propertiesPersister.load(props, new InputStreamReader(inputStream, charset));
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loading properties [" + resource.getFilename() + "]");
|
||||
}
|
||||
this.propertiesPersister.load(props, is);
|
||||
this.propertiesPersister.load(props, inputStream);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
|
||||
+18
-15
@@ -22,6 +22,8 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -57,11 +59,11 @@ import org.springframework.util.ClassUtils;
|
||||
* This means that "test.theme" is effectively equivalent to "test/theme".
|
||||
*
|
||||
* <p>On the classpath, bundle resources will be read with the locally configured
|
||||
* {@link #setDefaultEncoding encoding}: by default, ISO-8859-1; consider switching
|
||||
* {@link #setDefaultCharset Charset}: by default, ISO-8859-1; consider switching
|
||||
* this to UTF-8, or to {@code null} for the platform default encoding. On the JDK 9+
|
||||
* module path where locally provided {@code ResourceBundle.Control} handles are not
|
||||
* supported, this MessageSource always falls back to {@link ResourceBundle#getBundle}
|
||||
* retrieval with the platform default encoding: UTF-8 with a ISO-8859-1 fallback on
|
||||
* retrieval with the platform default encoding: UTF-8 with an ISO-8859-1 fallback on
|
||||
* JDK 9+ (configurable through the "java.util.PropertyResourceBundle.encoding" system
|
||||
* property). Note that {@link #loadBundle(Reader)}/{@link #loadBundle(InputStream)}
|
||||
* won't be called in this case either, effectively ignoring overrides in subclasses.
|
||||
@@ -70,6 +72,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Qimiao Chen
|
||||
* @author Sam Brannen
|
||||
* @see #setBasenames
|
||||
* @see ReloadableResourceBundleMessageSource
|
||||
* @see java.util.ResourceBundle
|
||||
@@ -83,7 +86,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
|
||||
/**
|
||||
* Cache to hold loaded ResourceBundles.
|
||||
* This Map is keyed with the bundle basename, which holds a Map that is
|
||||
* <p>This Map is keyed with the bundle basename, which holds a Map that is
|
||||
* keyed with the Locale and in turn holds the ResourceBundle instances.
|
||||
* This allows for very efficient hash lookups, significantly faster
|
||||
* than the ResourceBundle class's own cache.
|
||||
@@ -93,7 +96,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
|
||||
/**
|
||||
* Cache to hold already generated MessageFormats.
|
||||
* This Map is keyed with the ResourceBundle, which holds a Map that is
|
||||
* <p>This Map is keyed with the ResourceBundle, which holds a Map that is
|
||||
* keyed with the message code, which in turn holds a Map that is keyed
|
||||
* with the Locale and holds the MessageFormat values. This allows for
|
||||
* very efficient hash lookups without concatenated keys.
|
||||
@@ -106,7 +109,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
|
||||
|
||||
public ResourceBundleMessageSource() {
|
||||
setDefaultEncoding("ISO-8859-1");
|
||||
setDefaultCharset(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
|
||||
@@ -239,12 +242,12 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
catch (UnsupportedOperationException ex) {
|
||||
// Probably in a Java Module System environment on JDK 9+
|
||||
this.control = null;
|
||||
String encoding = getDefaultEncoding();
|
||||
if (encoding != null && logger.isInfoEnabled()) {
|
||||
Charset charset = getDefaultCharset();
|
||||
if (charset != null && logger.isInfoEnabled()) {
|
||||
logger.info("ResourceBundleMessageSource is configured to read resources with encoding '" +
|
||||
encoding + "' but ResourceBundle.Control is not supported in current system environment: " +
|
||||
charset + "' but ResourceBundle.Control is not supported in current system environment: " +
|
||||
ex.getMessage() + " - falling back to plain ResourceBundle.getBundle retrieval with the " +
|
||||
"platform default encoding. Consider setting the 'defaultEncoding' property to 'null' " +
|
||||
"platform default encoding. Consider setting the 'defaultCharset' property to 'null' " +
|
||||
"for participating in the platform default and therefore avoiding this log message.");
|
||||
}
|
||||
}
|
||||
@@ -256,7 +259,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
|
||||
/**
|
||||
* Load a property-based resource bundle from the given reader.
|
||||
* <p>This will be called in case of a {@link #setDefaultEncoding "defaultEncoding"},
|
||||
* <p>This will be called in case of a {@linkplain #setDefaultCharset "defaultCharset"},
|
||||
* including {@link ResourceBundleMessageSource}'s default ISO-8859-1 encoding.
|
||||
* Note that this method can only be called with a {@code ResourceBundle.Control}:
|
||||
* When running on the JDK 9+ module path where such control handles are not
|
||||
@@ -276,9 +279,9 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
/**
|
||||
* Load a property-based resource bundle from the given input stream,
|
||||
* picking up the default properties encoding on JDK 9+.
|
||||
* <p>This will only be called with {@link #setDefaultEncoding "defaultEncoding"}
|
||||
* <p>This will only be called with {@linkplain #setDefaultCharset "defaultCharset"}
|
||||
* set to {@code null}, explicitly enforcing the platform default encoding
|
||||
* (which is UTF-8 with a ISO-8859-1 fallback on JDK 9+ but configurable
|
||||
* (which is UTF-8 with an ISO-8859-1 fallback on JDK 9+ but configurable
|
||||
* through the "java.util.PropertyResourceBundle.encoding" system property).
|
||||
* Note that this method can only be called with a {@code ResourceBundle.Control}:
|
||||
* When running on the JDK 9+ module path where such control handles are not
|
||||
@@ -404,9 +407,9 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
inputStream = classLoader.getResourceAsStream(resourceName);
|
||||
}
|
||||
if (inputStream != null) {
|
||||
String encoding = getDefaultEncoding();
|
||||
if (encoding != null) {
|
||||
try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
|
||||
Charset charset = getDefaultCharset();
|
||||
if (charset != null) {
|
||||
try (InputStreamReader bundleReader = new InputStreamReader(inputStream, charset)) {
|
||||
return loadBundle(bundleReader);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -239,8 +239,8 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
|
||||
* @since 6.1.4
|
||||
* @see #initiateShutdown()
|
||||
*/
|
||||
public void setStrictEarlyShutdown(boolean defaultEarlyShutdown) {
|
||||
this.strictEarlyShutdown = defaultEarlyShutdown;
|
||||
public void setStrictEarlyShutdown(boolean strictEarlyShutdown) {
|
||||
this.strictEarlyShutdown = strictEarlyShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+25
@@ -30,6 +30,7 @@ import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
@@ -435,12 +436,15 @@ class ApplicationContextEventTests extends AbstractApplicationEventListenerTests
|
||||
RootBeanDefinition listener1Def = new RootBeanDefinition(MyOrderedListener1.class);
|
||||
listener1Def.setDependsOn("nestedChild");
|
||||
context.registerBeanDefinition("listener1", listener1Def);
|
||||
context.registerBeanDefinition("listenerFb", new RootBeanDefinition(MyFactoryBeanListener.class));
|
||||
context.refresh();
|
||||
|
||||
MyOrderedListener1 listener1 = context.getBean("listener1", MyOrderedListener1.class);
|
||||
MyFactoryBeanListener listenerFb = context.getBean("&listenerFb", MyFactoryBeanListener.class);
|
||||
MyEvent event1 = new MyEvent(context);
|
||||
context.publishEvent(event1);
|
||||
assertThat(listener1.seenEvents).contains(event1);
|
||||
assertThat(listenerFb.seenEvents).contains(event1);
|
||||
|
||||
SimpleApplicationEventMulticaster multicaster = context.getBean(SimpleApplicationEventMulticaster.class);
|
||||
assertThat(multicaster.getApplicationListeners()).isNotEmpty();
|
||||
@@ -782,6 +786,27 @@ class ApplicationContextEventTests extends AbstractApplicationEventListenerTests
|
||||
}
|
||||
|
||||
|
||||
public static class MyFactoryBeanListener implements FactoryBean<String>, ApplicationListener<ApplicationEvent> {
|
||||
|
||||
public final List<ApplicationEvent> seenEvents = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
this.seenEvents.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getObject() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return String.class;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class EventPublishingBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
+20
-10
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.support;
|
||||
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -30,14 +31,18 @@ import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
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.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResourceBundleMessageSource} and {@link ReloadableResourceBundleMessageSource}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @author Sam Brannen
|
||||
* @since 03.02.2004
|
||||
*/
|
||||
class ResourceBundleMessageSourceTests {
|
||||
@@ -272,23 +277,28 @@ class ResourceBundleMessageSourceTests {
|
||||
assertThat(ms.getMessage("code2", null, Locale.GERMAN)).isEqualTo("nachricht2");
|
||||
}
|
||||
|
||||
@Test // gh-36413
|
||||
void resourceBundleMessageSourceWithInvalidDefaultCharsetName() {
|
||||
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
|
||||
assertThatExceptionOfType(UnsupportedCharsetException.class).isThrownBy(() -> ms.setDefaultEncoding("BOGUS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceBundleMessageSourceWithDefaultCharset() {
|
||||
void resourceBundleMessageSourceWithDefaultCharsetName() {
|
||||
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
|
||||
ms.setBasename("org/springframework/context/support/messages");
|
||||
ms.setDefaultEncoding("ISO-8859-1");
|
||||
ms.setDefaultEncoding(ISO_8859_1.name());
|
||||
assertThat(ms.getMessage("code1", null, Locale.ENGLISH)).isEqualTo("message1");
|
||||
assertThat(ms.getMessage("code2", null, Locale.GERMAN)).isEqualTo("nachricht2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceBundleMessageSourceWithInappropriateDefaultCharset() {
|
||||
@Test // gh-36413
|
||||
void resourceBundleMessageSourceWithDefaultCharset() {
|
||||
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
|
||||
ms.setBasename("org/springframework/context/support/messages");
|
||||
ms.setDefaultEncoding("argh");
|
||||
ms.setFallbackToSystemLocale(false);
|
||||
assertThatExceptionOfType(NoSuchMessageException.class).isThrownBy(() ->
|
||||
ms.getMessage("code1", null, Locale.ENGLISH));
|
||||
ms.setDefaultCharset(ISO_8859_1);
|
||||
assertThat(ms.getMessage("code1", null, Locale.ENGLISH)).isEqualTo("message1");
|
||||
assertThat(ms.getMessage("code2", null, Locale.GERMAN)).isEqualTo("nachricht2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -353,13 +363,13 @@ class ResourceBundleMessageSourceTests {
|
||||
void reloadableResourceBundleMessageSourceWithDefaultCharset() {
|
||||
ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
|
||||
ms.setBasename("org/springframework/context/support/messages");
|
||||
ms.setDefaultEncoding("ISO-8859-1");
|
||||
ms.setDefaultCharset(ISO_8859_1);
|
||||
assertThat(ms.getMessage("code1", null, Locale.ENGLISH)).isEqualTo("message1");
|
||||
assertThat(ms.getMessage("code2", null, Locale.GERMAN)).isEqualTo("nachricht2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reloadableResourceBundleMessageSourceWithInappropriateDefaultCharset() {
|
||||
void reloadableResourceBundleMessageSourceWithInappropriateDefaultCharsetName() {
|
||||
ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
|
||||
ms.setBasename("org/springframework/context/support/messages");
|
||||
ms.setDefaultEncoding("unicode");
|
||||
|
||||
@@ -12,8 +12,8 @@ dependencies {
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Premain-Class': 'org.springframework.aot.agent.RuntimeHintsAgent',
|
||||
'Can-Redefine-Classes': 'true'
|
||||
'Premain-Class': 'org.springframework.aot.agent.RuntimeHintsAgent',
|
||||
'Can-Redefine-Classes': 'true'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.aot.agent;
|
||||
|
||||
import org.springframework.aot.hint.JavaSerializationHint;
|
||||
import org.springframework.aot.hint.JdkProxyHint;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.ResourceBundleHint;
|
||||
@@ -51,7 +50,9 @@ public enum HintType {
|
||||
/**
|
||||
* Java serialization hint, as described by {@link org.springframework.aot.hint.JavaSerializationHint}.
|
||||
*/
|
||||
JAVA_SERIALIZATION(JavaSerializationHint.class),
|
||||
@Deprecated(since = "7.0.5", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
JAVA_SERIALIZATION(org.springframework.aot.hint.JavaSerializationHint.class),
|
||||
|
||||
/**
|
||||
* JDK proxies hint, as described by {@link org.springframework.aot.hint.ProxyHints#jdkProxyHints()}.
|
||||
|
||||
@@ -92,7 +92,7 @@ dependencies {
|
||||
optional("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
optional("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
testCompileOnly("com.github.ben-manes.caffeine:caffeine")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
testFixturesImplementation("io.projectreactor:reactor-test")
|
||||
testFixturesImplementation("org.assertj:assertj-core")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter")
|
||||
@@ -100,7 +100,6 @@ dependencies {
|
||||
testFixturesImplementation("org.xmlunit:xmlunit-assertj")
|
||||
testImplementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
testImplementation("com.fasterxml.woodstox:woodstox-core")
|
||||
testImplementation("com.google.code.findbugs:jsr305")
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver3")
|
||||
testImplementation("io.projectreactor:reactor-test")
|
||||
testImplementation("io.projectreactor.tools:blockhound")
|
||||
|
||||
@@ -27,7 +27,9 @@ import org.jspecify.annotations.Nullable;
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
* @deprecated in favor of {@link TypeHint}
|
||||
*/
|
||||
@Deprecated(since = "7.0.6", forRemoval = true)
|
||||
public final class JavaSerializationHint implements ConditionalHint {
|
||||
|
||||
private final TypeReference type;
|
||||
|
||||
@@ -37,10 +37,13 @@ public final class JdkProxyHint implements ConditionalHint {
|
||||
|
||||
private final @Nullable TypeReference reachableType;
|
||||
|
||||
private final boolean javaSerialization;
|
||||
|
||||
|
||||
private JdkProxyHint(Builder builder) {
|
||||
this.proxiedInterfaces = List.copyOf(builder.proxiedInterfaces);
|
||||
this.reachableType = builder.reachableType;
|
||||
this.javaSerialization = builder.javaSerialization;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,11 +78,21 @@ public final class JdkProxyHint implements ConditionalHint {
|
||||
return this.reachableType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this hint registers the proxy for Java serialization.
|
||||
* @return whether the proxy is registered for Java serialization
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public boolean hasJavaSerialization() {
|
||||
return this.javaSerialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof JdkProxyHint that &&
|
||||
this.proxiedInterfaces.equals(that.proxiedInterfaces) &&
|
||||
Objects.equals(this.reachableType, that.reachableType)));
|
||||
Objects.equals(this.reachableType, that.reachableType) &&
|
||||
Objects.equals(this.javaSerialization, that.javaSerialization)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,6 +110,8 @@ public final class JdkProxyHint implements ConditionalHint {
|
||||
|
||||
private @Nullable TypeReference reachableType;
|
||||
|
||||
private boolean javaSerialization;
|
||||
|
||||
Builder() {
|
||||
this.proxiedInterfaces = new LinkedList<>();
|
||||
}
|
||||
@@ -131,6 +146,17 @@ public final class JdkProxyHint implements ConditionalHint {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if this proxy should be registered for Java serialization.
|
||||
* @param javaSerialization whether to register this proxy for Java serialization
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public Builder withJavaSerialization(boolean javaSerialization) {
|
||||
this.javaSerialization = javaSerialization;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link JdkProxyHint} based on the state of this builder.
|
||||
* @return a JDK proxy hint
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.aot.hint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A hint that describes the need of reflection for a Lambda.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public final class LambdaHint implements ConditionalHint {
|
||||
|
||||
private final TypeReference declaringClass;
|
||||
|
||||
private final @Nullable TypeReference reachableType;
|
||||
|
||||
private final @Nullable DeclaringMethod declaringMethod;
|
||||
|
||||
private final List<TypeReference> interfaces;
|
||||
|
||||
private LambdaHint(Builder builder) {
|
||||
this.declaringClass = builder.declaringClass;
|
||||
this.reachableType = builder.reachableType;
|
||||
this.declaringMethod = builder.declaringMethod;
|
||||
this.interfaces = List.copyOf(builder.interfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the class declaring the lambda.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @return a builder for the hint
|
||||
*/
|
||||
public static Builder of(TypeReference declaringClass) {
|
||||
return new Builder(declaringClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the class declaring the lambda.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @return a builder for the hint
|
||||
*/
|
||||
public static Builder of(Class<?> declaringClass) {
|
||||
return new Builder(TypeReference.of(declaringClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type declaring the lambda.
|
||||
* @return the declaring class
|
||||
*/
|
||||
public TypeReference getDeclaringClass() {
|
||||
return this.declaringClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TypeReference getReachableType() {
|
||||
return this.reachableType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method in which the lambda is defined, if any.
|
||||
* @return the declaring method
|
||||
*/
|
||||
public @Nullable DeclaringMethod getDeclaringMethod() {
|
||||
return this.declaringMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interfaces that are implemented by the lambda.
|
||||
* @return the interfaces
|
||||
*/
|
||||
public List<TypeReference> getInterfaces() {
|
||||
return this.interfaces;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final TypeReference declaringClass;
|
||||
|
||||
private @Nullable TypeReference reachableType;
|
||||
|
||||
private @Nullable DeclaringMethod declaringMethod;
|
||||
|
||||
private final List<TypeReference> interfaces = new ArrayList<>();
|
||||
|
||||
Builder(TypeReference declaringClass) {
|
||||
this.declaringClass = declaringClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this hint conditional on the fact that the specified type is in a
|
||||
* reachable code path from a static analysis point of view.
|
||||
* @param reachableType the type that should be reachable for this hint to apply
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder onReachableType(TypeReference reachableType) {
|
||||
this.reachableType = reachableType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this hint conditional on the fact that the specified type is in a
|
||||
* reachable code path from a static analysis point of view.
|
||||
* @param reachableType the type that should be reachable for this hint to apply
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder onReachableType(Class<?> reachableType) {
|
||||
this.reachableType = TypeReference.of(reachableType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method that declares the lambda.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types, if any.
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withDeclaringMethod(String name, List<TypeReference> parameterTypes) {
|
||||
this.declaringMethod = new DeclaringMethod(name, parameterTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method that declares the lambda.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types, if any.
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withDeclaringMethod(String name, Class<?>... parameterTypes) {
|
||||
return withDeclaringMethod(name, Arrays.stream(parameterTypes).map(TypeReference::of).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the lambda should implement.
|
||||
* @param interfaces the interfaces the lambda should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withInterfaces(TypeReference... interfaces) {
|
||||
this.interfaces.addAll(Arrays.asList(interfaces));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the lambda should implement.
|
||||
* @param interfaces the interfaces the lambda should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withInterfaces(Class<?>... interfaces) {
|
||||
this.interfaces.addAll(Arrays.stream(interfaces).map(TypeReference::of).toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaHint build() {
|
||||
return new LambdaHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe a method.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types
|
||||
*/
|
||||
public record DeclaringMethod(String name, List<TypeReference> parameterTypes) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,8 +21,10 @@ import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -45,6 +47,7 @@ public class ReflectionHints {
|
||||
|
||||
private final Map<TypeReference, TypeHint.Builder> types = new HashMap<>();
|
||||
|
||||
private final Set<LambdaHint> lambdaHints = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Return the types that require reflection.
|
||||
@@ -54,6 +57,16 @@ public class ReflectionHints {
|
||||
return this.types.values().stream().map(TypeHint.Builder::build);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the lambda hints.
|
||||
* @return a stream of {@link LambdaHint}
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public Stream<LambdaHint> lambdaHints() {
|
||||
return this.lambdaHints.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reflection hints for the type defined by the specified
|
||||
* {@link TypeReference}.
|
||||
@@ -231,6 +244,42 @@ public class ReflectionHints {
|
||||
typeHint -> typeHint.withMethod(method.getName(), mapParameters(method), mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for Java Serialization on the specified type.
|
||||
* @param type the type that should be serializable
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public ReflectionHints registerJavaSerialization(Class<?> type) {
|
||||
return registerType(TypeReference.of(type),
|
||||
typeHint -> typeHint.withJavaSerialization(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link LambdaHint}.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @param lambdaHint the consumer of the hint builder
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public ReflectionHints registerLambda(TypeReference declaringClass, Consumer<LambdaHint.Builder> lambdaHint) {
|
||||
LambdaHint.Builder builder = LambdaHint.of(declaringClass);
|
||||
lambdaHint.accept(builder);
|
||||
this.lambdaHints.add(builder.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link LambdaHint}.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @param lambdaHint the consumer of the hint builder
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public ReflectionHints registerLambda(Class<?> declaringClass, Consumer<LambdaHint.Builder> lambdaHint) {
|
||||
return this.registerLambda(TypeReference.of(declaringClass), lambdaHint);
|
||||
}
|
||||
|
||||
private List<TypeReference> mapParameters(Executable executable) {
|
||||
return TypeReference.listOf(executable.getParameterTypes());
|
||||
}
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
package org.springframework.aot.hint;
|
||||
|
||||
/**
|
||||
* Gather hints that can be used to optimize the application runtime.
|
||||
* Hints that can be used to optimize the application runtime.
|
||||
*
|
||||
* <p>Use of reflection can be recorded for individual members of a type, as
|
||||
* well as broader {@linkplain MemberCategory member categories}. Access to
|
||||
* resources can be specified using patterns or the base name of a resource
|
||||
* bundle.
|
||||
* <p>The use of reflection can be recorded for individual members of a type,
|
||||
* lambdas, or broader {@linkplain MemberCategory member categories}.
|
||||
*
|
||||
* <p>Hints that require the need for Java serialization of proxies can be
|
||||
* recorded as well.
|
||||
* <p>Access to resources can be specified using patterns or the base name of a
|
||||
* resource bundle.
|
||||
*
|
||||
* <p>The need for Java serialization or proxies can be recorded as well.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Janne Valkealahti
|
||||
@@ -37,6 +37,7 @@ public class RuntimeHints {
|
||||
|
||||
private final ResourceHints resources = new ResourceHints();
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private final SerializationHints serialization = new SerializationHints();
|
||||
|
||||
private final ProxyHints proxies = new ProxyHints();
|
||||
@@ -63,7 +64,10 @@ public class RuntimeHints {
|
||||
/**
|
||||
* Provide access to serialization-based hints.
|
||||
* @return serialization hints
|
||||
* @deprecated in favor of {@link #reflection()}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "7.0.6", forRemoval = true)
|
||||
public SerializationHints serialization() {
|
||||
return this.serialization;
|
||||
}
|
||||
@@ -77,8 +81,8 @@ public class RuntimeHints {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to jni-based hints.
|
||||
* @return jni hints
|
||||
* Provide access to JNI-based hints.
|
||||
* @return JNI hints
|
||||
*/
|
||||
public ReflectionHints jni() {
|
||||
return this.jni;
|
||||
|
||||
@@ -30,7 +30,10 @@ import org.jspecify.annotations.Nullable;
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see Serializable
|
||||
* @deprecated in favor of {@link ReflectionHints}
|
||||
*/
|
||||
@Deprecated(since = "7.0.6", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public class SerializationHints {
|
||||
|
||||
private final Set<JavaSerializationHint> javaSerializationHints;
|
||||
|
||||
@@ -53,6 +53,8 @@ public final class TypeHint implements ConditionalHint {
|
||||
|
||||
private final Set<MemberCategory> memberCategories;
|
||||
|
||||
private final boolean javaSerialization;
|
||||
|
||||
|
||||
private TypeHint(Builder builder) {
|
||||
this.type = builder.type;
|
||||
@@ -61,6 +63,7 @@ public final class TypeHint implements ConditionalHint {
|
||||
this.fields = builder.fields.stream().map(FieldHint::new).collect(Collectors.toSet());
|
||||
this.constructors = builder.constructors.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet());
|
||||
this.methods = builder.methods.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet());
|
||||
this.javaSerialization = builder.javaSerialization;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,6 +123,15 @@ public final class TypeHint implements ConditionalHint {
|
||||
return this.memberCategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this hint registers the type for Java serialization.
|
||||
* @return whether the type is registered for Java serialization
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public boolean hasJavaSerialization() {
|
||||
return this.javaSerialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return TypeHint.class.getSimpleName() + "[type=" + this.type + "]";
|
||||
@@ -153,6 +165,8 @@ public final class TypeHint implements ConditionalHint {
|
||||
|
||||
private final Set<MemberCategory> memberCategories = new HashSet<>();
|
||||
|
||||
private boolean javaSerialization;
|
||||
|
||||
Builder(TypeReference type) {
|
||||
this.type = type;
|
||||
}
|
||||
@@ -260,6 +274,17 @@ public final class TypeHint implements ConditionalHint {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if this type should be registered for Java serialization.
|
||||
* @param javaSerialization whether to register this type for Java serialization.
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public Builder withJavaSerialization(boolean javaSerialization) {
|
||||
this.javaSerialization = javaSerialization;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link TypeHint} based on the state of this builder.
|
||||
* @return a type hint
|
||||
|
||||
+51
-2
@@ -260,7 +260,7 @@ public class ReflectionHintsPredicates {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that checks whether an invocation hint is registered for the field.
|
||||
* Return a predicate that checks whether a reflective field access hint is registered for the field.
|
||||
* This looks up a field on the given type with the expected name, if present.
|
||||
* @param className the name of the class holding the field
|
||||
* @param fieldName the field name
|
||||
@@ -287,7 +287,8 @@ public class ReflectionHintsPredicates {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that checks whether an invocation hint is registered for the given field.
|
||||
* Return a predicate that checks whether a reflective field access hint is
|
||||
* registered for the given field.
|
||||
* @param field the field
|
||||
* @return the {@link RuntimeHints} predicate
|
||||
* @since 7.0
|
||||
@@ -297,6 +298,32 @@ public class ReflectionHintsPredicates {
|
||||
return new FieldHintPredicate(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that checks whether Java serialization is configured
|
||||
* for the type according to the given flag.
|
||||
* @param type the type to check
|
||||
* @param javaSerialization whether the type is expected to be registered for Java serialization
|
||||
* @return the {@link RuntimeHints} predicate
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public Predicate<RuntimeHints> onJavaSerialization(Class<?> type, boolean javaSerialization) {
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
return new JavaSerializationHintPredicate(TypeReference.of(type), javaSerialization);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that checks whether Java serialization is configured
|
||||
* for the type according to the given flag.
|
||||
* @param typeReference the type reference to check
|
||||
* @param javaSerialization whether the type is expected to be registered for Java serialization
|
||||
* @return the {@link RuntimeHints} predicate
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public Predicate<RuntimeHints> onJavaSerialization(TypeReference typeReference, boolean javaSerialization) {
|
||||
Assert.notNull(typeReference, "'typeReference' must not be null");
|
||||
return new JavaSerializationHintPredicate(typeReference, javaSerialization);
|
||||
}
|
||||
|
||||
|
||||
public static class TypeHintPredicate implements Predicate<RuntimeHints> {
|
||||
|
||||
@@ -509,4 +536,26 @@ public class ReflectionHintsPredicates {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class JavaSerializationHintPredicate implements Predicate<RuntimeHints> {
|
||||
|
||||
private final TypeReference typeReference;
|
||||
|
||||
private final boolean javaSerialization;
|
||||
|
||||
JavaSerializationHintPredicate(TypeReference typeReference, boolean javaSerialization) {
|
||||
this.typeReference = typeReference;
|
||||
this.javaSerialization = javaSerialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(RuntimeHints runtimeHints) {
|
||||
TypeHint typeHint = runtimeHints.reflection().getTypeHint(this.typeReference);
|
||||
if (typeHint == null) {
|
||||
return false;
|
||||
}
|
||||
return typeHint.hasJavaSerialization() == this.javaSerialization;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -46,6 +46,7 @@ public abstract class RuntimeHintsPredicates {
|
||||
|
||||
private static final ResourceHintsPredicates resource = new ResourceHintsPredicates();
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static final SerializationHintsPredicates serialization = new SerializationHintsPredicates();
|
||||
|
||||
private static final ProxyHintsPredicates proxies = new ProxyHintsPredicates();
|
||||
@@ -73,7 +74,10 @@ public abstract class RuntimeHintsPredicates {
|
||||
/**
|
||||
* Return a predicate generator for {@link SerializationHints serialization hints}.
|
||||
* @return the predicate generator
|
||||
* @deprecated in favor of {@link #reflection()}
|
||||
*/
|
||||
@Deprecated(since = "7.0.6", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public static SerializationHintsPredicates serialization() {
|
||||
return serialization;
|
||||
}
|
||||
|
||||
+3
@@ -29,7 +29,10 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @deprecated in favor of {@link ReflectionHintsPredicates}
|
||||
*/
|
||||
@Deprecated(since = "7.0.6", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public class SerializationHintsPredicates {
|
||||
|
||||
SerializationHintsPredicates() {
|
||||
|
||||
+8
-3
@@ -44,12 +44,17 @@ public abstract class NativeConfigurationWriter {
|
||||
}
|
||||
|
||||
private boolean hasAnyHint(RuntimeHints hints) {
|
||||
return (hints.serialization().javaSerializationHints().findAny().isPresent() ||
|
||||
hints.proxies().jdkProxyHints().findAny().isPresent() ||
|
||||
return (hints.proxies().jdkProxyHints().findAny().isPresent() ||
|
||||
hints.reflection().typeHints().findAny().isPresent() ||
|
||||
hints.resources().resourcePatternHints().findAny().isPresent() ||
|
||||
hints.resources().resourceBundleHints().findAny().isPresent() ||
|
||||
hints.jni().typeHints().findAny().isPresent());
|
||||
hints.jni().typeHints().findAny().isPresent() ||
|
||||
hasAnyDeprecatedHint(hints));
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private boolean hasAnyDeprecatedHint(RuntimeHints hints) {
|
||||
return hints.serialization().javaSerializationHints().findAny().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+61
-4
@@ -22,6 +22,7 @@ import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -32,7 +33,9 @@ import org.springframework.aot.hint.ConditionalHint;
|
||||
import org.springframework.aot.hint.ExecutableHint;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldHint;
|
||||
import org.springframework.aot.hint.JavaSerializationHint;
|
||||
import org.springframework.aot.hint.JdkProxyHint;
|
||||
import org.springframework.aot.hint.LambdaHint;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
@@ -61,17 +64,37 @@ class ReflectionHintsAttributes {
|
||||
return leftSignature.compareTo(rightSignature);
|
||||
};
|
||||
|
||||
private static final Comparator<LambdaHint> LAMBDA_HINT_COMPARATOR =
|
||||
Comparator.comparing(LambdaHint::getDeclaringClass)
|
||||
.thenComparing(LambdaHint::getDeclaringMethod, Comparator.nullsFirst(
|
||||
Comparator.comparing(LambdaHint.DeclaringMethod::name)));
|
||||
|
||||
|
||||
public List<Map<String, Object>> reflection(RuntimeHints hints) {
|
||||
List<Map<String, Object>> reflectionHints = new ArrayList<>();
|
||||
reflectionHints.addAll(hints.reflection().typeHints()
|
||||
.sorted(Comparator.comparing(TypeHint::getType))
|
||||
.map(this::toAttributes).toList());
|
||||
List<Map<String, Object>> reflectionHints = new ArrayList<>(reflectionHints(hints));
|
||||
reflectionHints.addAll(hints.proxies().jdkProxyHints()
|
||||
.sorted(JDK_PROXY_HINT_COMPARATOR)
|
||||
.map(this::toAttributes).toList());
|
||||
return reflectionHints;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private List<Map<String, Object>> reflectionHints(RuntimeHints hints) {
|
||||
Map<TypeReference, Map<String, Object>> allTypeHints = hints.reflection().typeHints()
|
||||
.map(this::toAttributes).collect(Collectors.toMap((attributes -> (TypeReference) Objects.requireNonNull(attributes.get("type"))),
|
||||
attributes -> attributes));
|
||||
hints.serialization().javaSerializationHints().forEach(hint -> {
|
||||
allTypeHints.merge(hint.getType(), toAttributes(hint),
|
||||
(currentAttributes, newAttributes) -> {
|
||||
handleSerializable(currentAttributes, true);
|
||||
return currentAttributes;
|
||||
});
|
||||
});
|
||||
return Stream.concat(
|
||||
allTypeHints.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue),
|
||||
hints.reflection().lambdaHints().sorted(LAMBDA_HINT_COMPARATOR).map(this::toAttributes)).toList();
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> jni(RuntimeHints hints) {
|
||||
List<Map<String, Object>> jniHints = new ArrayList<>();
|
||||
jniHints.addAll(hints.jni().typeHints()
|
||||
@@ -88,6 +111,33 @@ class ReflectionHintsAttributes {
|
||||
handleFields(attributes, hint.fields());
|
||||
handleExecutables(attributes, Stream.concat(
|
||||
hint.constructors(), hint.methods()).sorted().toList());
|
||||
handleSerializable(attributes, hint.hasJavaSerialization());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private Map<String, Object> toAttributes(LambdaHint hint) {
|
||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
||||
Map<String, Object> lambdaAttributes = new LinkedHashMap<>();
|
||||
lambdaAttributes.put("declaringClass", hint.getDeclaringClass());
|
||||
LambdaHint.DeclaringMethod declaringMethod = hint.getDeclaringMethod();
|
||||
if (declaringMethod != null) {
|
||||
Map<String, Object> methodAttributes = new LinkedHashMap<>();
|
||||
methodAttributes.put("name", declaringMethod.name());
|
||||
methodAttributes.put("parameterTypes", declaringMethod.parameterTypes());
|
||||
lambdaAttributes.put("declaringMethod", methodAttributes);
|
||||
}
|
||||
lambdaAttributes.put("interfaces", hint.getInterfaces());
|
||||
|
||||
attributes.put("lambda", lambdaAttributes);
|
||||
return Map.of("type", attributes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private Map<String, Object> toAttributes(JavaSerializationHint serializationHint) {
|
||||
LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
|
||||
handleCondition(attributes, serializationHint);
|
||||
attributes.put("type", serializationHint.getType());
|
||||
handleSerializable(attributes, true);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@@ -148,7 +198,14 @@ class ReflectionHintsAttributes {
|
||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
||||
handleCondition(attributes, hint);
|
||||
attributes.put("type", Map.of("proxy", hint.getProxiedInterfaces()));
|
||||
handleSerializable(attributes, hint.hasJavaSerialization());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void handleSerializable(Map<String, Object> attributes, boolean serializable) {
|
||||
if (serializable) {
|
||||
attributes.put("serializable", serializable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-10
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.aot.nativex;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -35,8 +36,9 @@ import org.springframework.aot.hint.ResourcePatternHints;
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/Resources/">Accessing Resources in Native Images</a>
|
||||
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
|
||||
* @see <a href="https://www.graalvm.org/jdk25/reference-manual/native-image/metadata/#resources">Accessing Resources in Native Images</a>
|
||||
* @see <a href="https://www.graalvm.org/jdk25/reference-manual/native-image/metadata/#resource-bundles">Accessing Resource Bundles in Native Images</a>
|
||||
* @see <a href="https://www.graalvm.org/jdk25/reference-manual/native-image/overview/BuildConfiguration">Native Image Build Configuration</a>
|
||||
*/
|
||||
class ResourceHintsAttributes {
|
||||
|
||||
@@ -48,22 +50,21 @@ class ResourceHintsAttributes {
|
||||
|
||||
|
||||
public List<Map<String, Object>> resources(ResourceHints hint) {
|
||||
return hint.resourcePatternHints()
|
||||
List<Map<String, Object>> resourceHints = new ArrayList<>();
|
||||
resourceHints.addAll(hint.resourcePatternHints()
|
||||
.map(ResourcePatternHints::getIncludes).flatMap(List::stream).distinct()
|
||||
.sorted(RESOURCE_PATTERN_HINT_COMPARATOR)
|
||||
.map(this::toAttributes).toList();
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> resourceBundles(ResourceHints hint) {
|
||||
return hint.resourceBundleHints()
|
||||
.map(this::toAttributes).toList());
|
||||
resourceHints.addAll(hint.resourceBundleHints()
|
||||
.sorted(RESOURCE_BUNDLE_HINT_COMPARATOR)
|
||||
.map(this::toAttributes).toList();
|
||||
.map(this::toAttributes).toList());
|
||||
return resourceHints;
|
||||
}
|
||||
|
||||
private Map<String, Object> toAttributes(ResourceBundleHint hint) {
|
||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
||||
handleCondition(attributes, hint);
|
||||
attributes.put("name", hint.getBaseName());
|
||||
attributes.put("bundle", hint.getBaseName());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ import org.springframework.core.SpringVersion;
|
||||
* GraalVM {@code native-image} compiler, typically named {@code reachability-metadata.json}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
* @since 7.0
|
||||
* @see <a href="https://www.graalvm.org/jdk23/reference-manual/native-image/metadata/#specifying-metadata-with-json">GraalVM Reachability Metadata</a>
|
||||
* @see <a href="https://www.graalvm.org/jdk25/reference-manual/native-image/metadata">GraalVM Reachability Metadata</a>
|
||||
*/
|
||||
class RuntimeHintsWriter {
|
||||
|
||||
@@ -51,14 +52,6 @@ class RuntimeHintsWriter {
|
||||
if (!resourceHints.isEmpty()) {
|
||||
document.put("resources", resourceHints);
|
||||
}
|
||||
List<Map<String, Object>> resourceBundles = new ResourceHintsAttributes().resourceBundles(hints.resources());
|
||||
if (!resourceBundles.isEmpty()) {
|
||||
document.put("bundles", resourceBundles);
|
||||
}
|
||||
List<Map<String, Object>> serialization = new SerializationHintsAttributes().toAttributes(hints.serialization());
|
||||
if (!serialization.isEmpty()) {
|
||||
document.put("serialization", serialization);
|
||||
}
|
||||
|
||||
writer.writeObject(document);
|
||||
}
|
||||
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.aot.nativex;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.aot.hint.ConditionalHint;
|
||||
import org.springframework.aot.hint.JavaSerializationHint;
|
||||
import org.springframework.aot.hint.SerializationHints;
|
||||
|
||||
/**
|
||||
* Collect {@link SerializationHints} as map attributes ready for JSON serialization for the GraalVM
|
||||
* {@code native-image} compiler.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
* @see <a href="https://www.graalvm.org/jdk23/reference-manual/native-image/overview/BuildConfiguration/">Native Image Build Configuration</a>
|
||||
*/
|
||||
class SerializationHintsAttributes {
|
||||
|
||||
private static final Comparator<JavaSerializationHint> JAVA_SERIALIZATION_HINT_COMPARATOR =
|
||||
Comparator.comparing(JavaSerializationHint::getType);
|
||||
|
||||
public List<Map<String, Object>> toAttributes(SerializationHints hints) {
|
||||
return hints.javaSerializationHints()
|
||||
.sorted(JAVA_SERIALIZATION_HINT_COMPARATOR)
|
||||
.map(this::toAttributes).toList();
|
||||
}
|
||||
|
||||
private Map<String, Object> toAttributes(JavaSerializationHint serializationHint) {
|
||||
LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
|
||||
handleCondition(attributes, serializationHint);
|
||||
attributes.put("type", serializationHint.getType());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void handleCondition(Map<String, Object> attributes, ConditionalHint hint) {
|
||||
if (hint.getReachableType() != null) {
|
||||
Map<String, Object> conditionAttributes = new LinkedHashMap<>();
|
||||
conditionAttributes.put("typeReached", hint.getReachableType());
|
||||
attributes.put("condition", conditionAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+15
-9
@@ -137,14 +137,20 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
|
||||
String string = this.string;
|
||||
if (string == null) {
|
||||
StringBuilder builder = new StringBuilder("@").append(getName(this.type)).append('(');
|
||||
for (int i = 0; i < this.attributes.size(); i++) {
|
||||
Method attribute = this.attributes.get(i);
|
||||
if (i > 0) {
|
||||
builder.append(", ");
|
||||
if (this.attributes.size() == 1 && this.attributes.get(0).getName().equals(MergedAnnotation.VALUE)) {
|
||||
// Don't prepend "value=" for an annotation that only declares a "value" attribute.
|
||||
builder.append(toString(getAttributeValue(this.attributes.get(0))));
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < this.attributes.size(); i++) {
|
||||
Method attribute = this.attributes.get(i);
|
||||
if (i > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(attribute.getName());
|
||||
builder.append('=');
|
||||
builder.append(toString(getAttributeValue(attribute)));
|
||||
}
|
||||
builder.append(attribute.getName());
|
||||
builder.append('=');
|
||||
builder.append(toString(getAttributeValue(attribute)));
|
||||
}
|
||||
builder.append(')');
|
||||
string = builder.toString();
|
||||
@@ -187,7 +193,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
|
||||
return '\'' + value.toString() + '\'';
|
||||
}
|
||||
if (type == Byte.class) {
|
||||
return String.format("(byte) 0x%02X", value);
|
||||
return String.format("(byte)0x%02x", value);
|
||||
}
|
||||
if (type == Long.class) {
|
||||
return Long.toString((Long) value) + 'L';
|
||||
@@ -196,7 +202,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
|
||||
return Float.toString((Float) value) + 'f';
|
||||
}
|
||||
if (type == Double.class) {
|
||||
return Double.toString((Double) value) + 'd';
|
||||
return Double.toString((Double) value);
|
||||
}
|
||||
if (value instanceof Enum<?> e) {
|
||||
return e.name();
|
||||
|
||||
+33
-10
@@ -19,10 +19,12 @@ package org.springframework.core.task;
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -94,7 +96,9 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
|
||||
private boolean rejectTasksWhenLimitReached = false;
|
||||
|
||||
private volatile boolean active = true;
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
|
||||
private boolean cancelled = false; // within activeThreads synchronization
|
||||
|
||||
|
||||
/**
|
||||
@@ -269,7 +273,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
* @see #close()
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return this.active;
|
||||
return !this.closed.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,14 +311,15 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
public void execute(Runnable task, long startTimeout) {
|
||||
Assert.notNull(task, "Runnable must not be null");
|
||||
if (!isActive()) {
|
||||
throw new TaskRejectedException(getClass().getSimpleName() + " has been closed already");
|
||||
throw new TaskRejectedException(getClass().getSimpleName() + " is not active");
|
||||
}
|
||||
|
||||
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
|
||||
Future<?> future = (task instanceof Future<?> f ? f : null);
|
||||
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
|
||||
this.concurrencyThrottle.beforeAccess();
|
||||
try {
|
||||
doExecute(new TaskTrackingRunnable(taskToUse));
|
||||
doExecute(new TaskTrackingRunnable(taskToUse, future));
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Release concurrency permit if thread creation fails
|
||||
@@ -324,7 +329,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
}
|
||||
}
|
||||
else if (this.activeThreads != null) {
|
||||
doExecute(new TaskTrackingRunnable(taskToUse));
|
||||
doExecute(new TaskTrackingRunnable(taskToUse, future));
|
||||
}
|
||||
else {
|
||||
doExecute(taskToUse);
|
||||
@@ -384,11 +389,13 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.active) {
|
||||
this.active = false;
|
||||
if (this.closed.compareAndSet(false, true)) {
|
||||
Set<Thread> threads = this.activeThreads;
|
||||
if (threads != null) {
|
||||
if (this.cancelRemainingTasksOnClose) {
|
||||
synchronized (threads) {
|
||||
this.cancelled = true;
|
||||
}
|
||||
// Early interrupt for remaining tasks on close
|
||||
threads.forEach(Thread::interrupt);
|
||||
}
|
||||
@@ -402,6 +409,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
this.cancelled = true;
|
||||
}
|
||||
if (!this.cancelRemainingTasksOnClose) {
|
||||
// Late interrupt for remaining tasks after timeout
|
||||
@@ -412,6 +420,15 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCancelled(@Nullable Future<?> future) {
|
||||
if (this.cancelled) { // within synchronization from TaskTrackingRunnable
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
throw new CancellationException(getClass().getSimpleName() + " has cancelled all remaining tasks");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subclass of the general ConcurrencyThrottleSupport class,
|
||||
@@ -453,9 +470,12 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
|
||||
private final Runnable task;
|
||||
|
||||
public TaskTrackingRunnable(Runnable task) {
|
||||
private final @Nullable Future<?> future;
|
||||
|
||||
public TaskTrackingRunnable(Runnable task, @Nullable Future<?> future) {
|
||||
Assert.notNull(task, "Task must not be null");
|
||||
this.task = task;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -464,7 +484,10 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
Thread thread = null;
|
||||
if (threads != null) {
|
||||
thread = Thread.currentThread();
|
||||
threads.add(thread);
|
||||
synchronized (threads) {
|
||||
checkCancelled(this.future);
|
||||
threads.add(thread);
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.task.run();
|
||||
@@ -472,7 +495,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
finally {
|
||||
if (threads != null) {
|
||||
threads.remove(thread);
|
||||
if (!isActive()) {
|
||||
if (closed.get()) {
|
||||
synchronized (threads) {
|
||||
if (threads.isEmpty()) {
|
||||
threads.notify();
|
||||
|
||||
+1
@@ -117,6 +117,7 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
|
||||
return new MergedAnnotationReadingVisitor<>(this.classLoader, this.source, type, consumer);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <A extends Annotation> @Nullable AnnotationVisitor get(@Nullable ClassLoader classLoader,
|
||||
@Nullable Object source, String descriptor, boolean visible,
|
||||
|
||||
+1
-1
@@ -32,7 +32,6 @@ import org.springframework.core.io.ResourceLoader;
|
||||
*/
|
||||
public class SimpleMetadataReaderFactory extends AbstractMetadataReaderFactory {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SimpleMetadataReaderFactory for the default class loader.
|
||||
*/
|
||||
@@ -57,6 +56,7 @@ public class SimpleMetadataReaderFactory extends AbstractMetadataReaderFactory {
|
||||
super(classLoader);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MetadataReader getMetadataReader(Resource resource) throws IOException {
|
||||
return new SimpleMetadataReader(resource, getResourceLoader().getClassLoader());
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.Properties;
|
||||
* should already apply proper decoding/encoding of characters. If you prefer
|
||||
* to escape unicode characters in your properties files, do <i>not</i> specify
|
||||
* an encoding for a Reader/Writer (like ReloadableResourceBundleMessageSource's
|
||||
* "defaultEncoding" and "fileEncodings" properties).
|
||||
* "defaultCharset" and "fileEncodings" properties).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
@@ -695,10 +694,4 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
return MimeTypeUtils.parseMimeType(value);
|
||||
}
|
||||
|
||||
private static Map<String, String> addCharsetParameter(Charset charset, Map<String, String> parameters) {
|
||||
Map<String, String> map = new LinkedHashMap<>(parameters);
|
||||
map.put(PARAM_CHARSET, charset.name());
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -221,9 +221,13 @@ public abstract class StreamUtils {
|
||||
* Return a variant of the given {@link InputStream} where calling
|
||||
* {@link InputStream#close() close()} has no effect.
|
||||
* @param in the InputStream to decorate
|
||||
* @return a version of the InputStream that ignores calls to close
|
||||
* @return a version of the InputStream that ignores calls to close,
|
||||
* or the given InputStream if it is non-closing already
|
||||
*/
|
||||
public static InputStream nonClosing(InputStream in) {
|
||||
if (in instanceof NonClosingInputStream) {
|
||||
return in;
|
||||
}
|
||||
Assert.notNull(in, "No InputStream specified");
|
||||
return new NonClosingInputStream(in);
|
||||
}
|
||||
@@ -232,15 +236,38 @@ public abstract class StreamUtils {
|
||||
* Return a variant of the given {@link OutputStream} where calling
|
||||
* {@link OutputStream#close() close()} has no effect.
|
||||
* @param out the OutputStream to decorate
|
||||
* @return a version of the OutputStream that ignores calls to close
|
||||
* @return a version of the OutputStream that ignores calls to close,
|
||||
* or the given OutputStream if it is non-closing already
|
||||
* @see #nonFlushing(OutputStream)
|
||||
*/
|
||||
public static OutputStream nonClosing(OutputStream out) {
|
||||
if (out instanceof NonClosingOutputStream) {
|
||||
return out;
|
||||
}
|
||||
Assert.notNull(out, "No OutputStream specified");
|
||||
return new NonClosingOutputStream(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a variant of the given {@link OutputStream} where calling
|
||||
* {@link OutputStream#flush() flush()} and/or
|
||||
* {@link OutputStream#close() close()} has no effect.
|
||||
* @param out the OutputStream to decorate
|
||||
* @return a version of the OutputStream that ignores calls to flush/close,
|
||||
* or the given OutputStream if it is non-flushing already
|
||||
* @since 7.0.6
|
||||
* @see #nonClosing(OutputStream)
|
||||
*/
|
||||
public static OutputStream nonFlushing(OutputStream out) {
|
||||
if (out instanceof NonFlushingOutputStream) {
|
||||
return out;
|
||||
}
|
||||
Assert.notNull(out, "No OutputStream specified");
|
||||
return new NonFlushingOutputStream(out);
|
||||
}
|
||||
|
||||
private static final class NonClosingInputStream extends FilterInputStream {
|
||||
|
||||
private static class NonClosingInputStream extends FilterInputStream {
|
||||
|
||||
public NonClosingInputStream(InputStream in) {
|
||||
super(in);
|
||||
@@ -272,7 +299,7 @@ public abstract class StreamUtils {
|
||||
}
|
||||
|
||||
|
||||
private static final class NonClosingOutputStream extends FilterOutputStream {
|
||||
private static class NonClosingOutputStream extends FilterOutputStream {
|
||||
|
||||
public NonClosingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
@@ -289,4 +316,16 @@ public abstract class StreamUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NonFlushingOutputStream extends NonClosingOutputStream {
|
||||
|
||||
public NonFlushingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.type.classreading;
|
||||
|
||||
import java.lang.classfile.Annotation;
|
||||
import java.lang.classfile.AnnotationElement;
|
||||
import java.lang.classfile.AnnotationValue;
|
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationFilter;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Parse {@link RuntimeVisibleAnnotationsAttribute} into {@link MergedAnnotations}
|
||||
* instances.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
*/
|
||||
abstract class ClassFileAnnotationDelegate {
|
||||
|
||||
static MergedAnnotations createMergedAnnotations(
|
||||
String className, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) {
|
||||
|
||||
Set<MergedAnnotation<?>> annotations = annotationAttribute.annotations()
|
||||
.stream()
|
||||
.map(ann -> createMergedAnnotation(className, ann, classLoader))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
return MergedAnnotations.of(annotations);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <A extends java.lang.annotation.Annotation> @Nullable MergedAnnotation<A> createMergedAnnotation(
|
||||
String className, Annotation annotation, @Nullable ClassLoader classLoader) {
|
||||
|
||||
String typeName = fromTypeDescriptor(annotation.className().stringValue());
|
||||
if (AnnotationFilter.PLAIN.matches(typeName)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Fail early when annotation type is not loadable (before resolving annotation values)
|
||||
Class<A> annotationType = (Class<A>) ClassUtils.forName(typeName, classLoader);
|
||||
Map<String, Object> attributes = new LinkedHashMap<>(4);
|
||||
for (AnnotationElement element : annotation.elements()) {
|
||||
Object annotationValue = readAnnotationValue(className, element.value(), classLoader);
|
||||
if (annotationValue != null) {
|
||||
attributes.put(element.name().stringValue(), annotationValue);
|
||||
}
|
||||
}
|
||||
Map<String, Object> compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes);
|
||||
return MergedAnnotation.of(classLoader, new Source(annotation), annotationType, compactedAttributes);
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
// Non-loadable annotation type -> ignore.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Object readAnnotationValue(
|
||||
String className, AnnotationValue elementValue, @Nullable ClassLoader classLoader) {
|
||||
|
||||
switch (elementValue) {
|
||||
case AnnotationValue.OfConstant constantValue -> {
|
||||
return constantValue.resolvedValue();
|
||||
}
|
||||
case AnnotationValue.OfAnnotation annotationValue -> {
|
||||
return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
|
||||
}
|
||||
case AnnotationValue.OfClass classValue -> {
|
||||
return fromTypeDescriptor(classValue.className().stringValue());
|
||||
}
|
||||
case AnnotationValue.OfEnum enumValue -> {
|
||||
return parseEnum(enumValue, classLoader);
|
||||
}
|
||||
case AnnotationValue.OfArray arrayValue -> {
|
||||
return parseArrayValue(className, classLoader, arrayValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String fromTypeDescriptor(String descriptor) {
|
||||
ClassDesc classDesc = ClassDesc.ofDescriptor(descriptor);
|
||||
return (classDesc.isPrimitive() ? classDesc.displayName() :
|
||||
classDesc.packageName() + "." + classDesc.displayName());
|
||||
}
|
||||
|
||||
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
|
||||
if (arrayValue.values().isEmpty()) {
|
||||
return new Object[0];
|
||||
}
|
||||
Stream<AnnotationValue> stream = arrayValue.values().stream();
|
||||
switch (arrayValue.values().getFirst()) {
|
||||
case AnnotationValue.OfInt _ -> {
|
||||
return stream.map(AnnotationValue.OfInt.class::cast).mapToInt(AnnotationValue.OfInt::intValue).toArray();
|
||||
}
|
||||
case AnnotationValue.OfDouble _ -> {
|
||||
return stream.map(AnnotationValue.OfDouble.class::cast).mapToDouble(AnnotationValue.OfDouble::doubleValue).toArray();
|
||||
}
|
||||
case AnnotationValue.OfLong _ -> {
|
||||
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
|
||||
}
|
||||
default -> {
|
||||
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
|
||||
return stream
|
||||
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
|
||||
.toArray(length -> (Object[]) Array.newInstance(arrayElementType, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Enum<E>> Enum<E> parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
|
||||
Class<E> enumClass = (Class<E>) loadEnumClass(enumValue, classLoader);
|
||||
return Enum.valueOf(enumClass, enumValue.constantName().stringValue());
|
||||
}
|
||||
|
||||
private static Class<?> loadEnumClass(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
|
||||
String className = fromTypeDescriptor(enumValue.className().stringValue());
|
||||
return ClassUtils.resolveClassName(className, classLoader);
|
||||
}
|
||||
|
||||
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
|
||||
AnnotationValue firstValue = values.getFirst();
|
||||
switch (firstValue) {
|
||||
case AnnotationValue.OfConstant constantValue -> {
|
||||
return constantValue.resolvedValue().getClass();
|
||||
}
|
||||
case AnnotationValue.OfAnnotation _ -> {
|
||||
return MergedAnnotation.class;
|
||||
}
|
||||
case AnnotationValue.OfClass _ -> {
|
||||
return String.class;
|
||||
}
|
||||
case AnnotationValue.OfEnum enumValue -> {
|
||||
return loadEnumClass(enumValue, classLoader);
|
||||
}
|
||||
default -> {
|
||||
return Object.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
record Source(Annotation annotation) {
|
||||
}
|
||||
|
||||
}
|
||||
+274
-134
@@ -16,165 +16,305 @@
|
||||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.lang.classfile.Annotation;
|
||||
import java.lang.classfile.AnnotationElement;
|
||||
import java.lang.classfile.AnnotationValue;
|
||||
import java.lang.classfile.AccessFlags;
|
||||
import java.lang.classfile.ClassModel;
|
||||
import java.lang.classfile.Interfaces;
|
||||
import java.lang.classfile.MethodModel;
|
||||
import java.lang.classfile.Superclass;
|
||||
import java.lang.classfile.attribute.InnerClassInfo;
|
||||
import java.lang.classfile.attribute.InnerClassesAttribute;
|
||||
import java.lang.classfile.attribute.NestHostAttribute;
|
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.classfile.constantpool.ClassEntry;
|
||||
import java.lang.reflect.AccessFlag;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationFilter;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Parse {@link RuntimeVisibleAnnotationsAttribute} into {@link MergedAnnotations}
|
||||
* instances.
|
||||
* {@link AnnotationMetadata} implementation that leverages
|
||||
* the {@link java.lang.classfile.ClassFile} API.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
*/
|
||||
abstract class ClassFileAnnotationMetadata {
|
||||
final class ClassFileAnnotationMetadata implements AnnotationMetadata {
|
||||
|
||||
static MergedAnnotations createMergedAnnotations(
|
||||
String className, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) {
|
||||
private final String className;
|
||||
|
||||
Set<MergedAnnotation<?>> annotations = annotationAttribute.annotations()
|
||||
.stream()
|
||||
.map(ann -> createMergedAnnotation(className, ann, classLoader))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
return MergedAnnotations.of(annotations);
|
||||
private final AccessFlags accessFlags;
|
||||
|
||||
private final @Nullable String enclosingClassName;
|
||||
|
||||
private final @Nullable String superClassName;
|
||||
|
||||
private final boolean independentInnerClass;
|
||||
|
||||
private final Set<String> interfaceNames;
|
||||
|
||||
private final Set<String> memberClassNames;
|
||||
|
||||
private final Set<MethodMetadata> declaredMethods;
|
||||
|
||||
private final MergedAnnotations mergedAnnotations;
|
||||
|
||||
private @Nullable Set<String> annotationTypes;
|
||||
|
||||
|
||||
ClassFileAnnotationMetadata(String className, AccessFlags accessFlags, @Nullable String enclosingClassName,
|
||||
@Nullable String superClassName, boolean independentInnerClass, Set<String> interfaceNames,
|
||||
Set<String> memberClassNames, Set<MethodMetadata> declaredMethods, MergedAnnotations mergedAnnotations) {
|
||||
|
||||
this.className = className;
|
||||
this.accessFlags = accessFlags;
|
||||
this.enclosingClassName = enclosingClassName;
|
||||
this.superClassName = (!className.endsWith(".package-info")) ? superClassName : null;
|
||||
this.independentInnerClass = independentInnerClass;
|
||||
this.interfaceNames = interfaceNames;
|
||||
this.memberClassNames = memberClassNames;
|
||||
this.declaredMethods = declaredMethods;
|
||||
this.mergedAnnotations = mergedAnnotations;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <A extends java.lang.annotation.Annotation> @Nullable MergedAnnotation<A> createMergedAnnotation(
|
||||
String className, Annotation annotation, @Nullable ClassLoader classLoader) {
|
||||
|
||||
String typeName = fromTypeDescriptor(annotation.className().stringValue());
|
||||
if (AnnotationFilter.PLAIN.matches(typeName)) {
|
||||
return null;
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return this.className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterface() {
|
||||
return this.accessFlags.has(AccessFlag.INTERFACE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnnotation() {
|
||||
return this.accessFlags.has(AccessFlag.ANNOTATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbstract() {
|
||||
return this.accessFlags.has(AccessFlag.ABSTRACT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinal() {
|
||||
return this.accessFlags.has(AccessFlag.FINAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndependent() {
|
||||
return (this.enclosingClassName == null || this.independentInnerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getEnclosingClassName() {
|
||||
return this.enclosingClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSuperClassName() {
|
||||
return this.superClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getInterfaceNames() {
|
||||
return StringUtils.toStringArray(this.interfaceNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getMemberClassNames() {
|
||||
return StringUtils.toStringArray(this.memberClassNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergedAnnotations getAnnotations() {
|
||||
return this.mergedAnnotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAnnotationTypes() {
|
||||
Set<String> annotationTypes = this.annotationTypes;
|
||||
if (annotationTypes == null) {
|
||||
annotationTypes = Collections.unmodifiableSet(AnnotationMetadata.super.getAnnotationTypes());
|
||||
this.annotationTypes = annotationTypes;
|
||||
}
|
||||
Map<String, Object> attributes = new LinkedHashMap<>(4);
|
||||
try {
|
||||
for (AnnotationElement element : annotation.elements()) {
|
||||
Object annotationValue = readAnnotationValue(className, element.value(), classLoader);
|
||||
if (annotationValue != null) {
|
||||
attributes.put(element.name().stringValue(), annotationValue);
|
||||
return annotationTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
|
||||
Set<MethodMetadata> result = new LinkedHashSet<>(4);
|
||||
for (MethodMetadata annotatedMethod : this.declaredMethods) {
|
||||
if (annotatedMethod.isAnnotated(annotationName)) {
|
||||
result.add(annotatedMethod);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MethodMetadata> getDeclaredMethods() {
|
||||
return Collections.unmodifiableSet(this.declaredMethods);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof ClassFileAnnotationMetadata that &&
|
||||
this.className.equals(that.className)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.className.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.className;
|
||||
}
|
||||
|
||||
|
||||
static ClassFileAnnotationMetadata of(ClassModel classModel, @Nullable ClassLoader classLoader) {
|
||||
Builder builder = new Builder(classLoader);
|
||||
builder.classEntry(classModel.thisClass());
|
||||
String currentClassName = classModel.thisClass().name().stringValue();
|
||||
classModel.elementStream().forEach(classElement -> {
|
||||
switch (classElement) {
|
||||
case AccessFlags flags -> {
|
||||
builder.accessFlags(flags);
|
||||
}
|
||||
case NestHostAttribute _ -> {
|
||||
builder.enclosingClassFromNestHost(classModel.thisClass());
|
||||
}
|
||||
case InnerClassesAttribute innerClasses -> {
|
||||
builder.nestMembers(currentClassName, innerClasses);
|
||||
}
|
||||
case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> {
|
||||
builder.mergedAnnotations(ClassFileAnnotationDelegate.createMergedAnnotations(
|
||||
ClassUtils.convertResourcePathToClassName(currentClassName), annotationsAttribute, classLoader));
|
||||
}
|
||||
case Superclass superclass -> {
|
||||
builder.superClass(superclass);
|
||||
}
|
||||
case Interfaces interfaces -> {
|
||||
builder.interfaces(interfaces);
|
||||
}
|
||||
case MethodModel method -> {
|
||||
builder.method(method);
|
||||
}
|
||||
default -> {
|
||||
// ignore class element
|
||||
}
|
||||
}
|
||||
Map<String, Object> compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes);
|
||||
Class<A> annotationType = (Class<A>) ClassUtils.forName(typeName, classLoader);
|
||||
return MergedAnnotation.of(classLoader, new Source(annotation), annotationType, compactedAttributes);
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Object readAnnotationValue(
|
||||
String className, AnnotationValue elementValue, @Nullable ClassLoader classLoader) {
|
||||
|
||||
switch (elementValue) {
|
||||
case AnnotationValue.OfConstant constantValue -> {
|
||||
return constantValue.resolvedValue();
|
||||
}
|
||||
case AnnotationValue.OfAnnotation annotationValue -> {
|
||||
return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
|
||||
}
|
||||
case AnnotationValue.OfClass classValue -> {
|
||||
return fromTypeDescriptor(classValue.className().stringValue());
|
||||
}
|
||||
case AnnotationValue.OfEnum enumValue -> {
|
||||
return parseEnum(enumValue, classLoader);
|
||||
}
|
||||
case AnnotationValue.OfArray arrayValue -> {
|
||||
return parseArrayValue(className, classLoader, arrayValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String fromTypeDescriptor(String descriptor) {
|
||||
ClassDesc classDesc = ClassDesc.ofDescriptor(descriptor);
|
||||
return classDesc.isPrimitive() ? classDesc.displayName() :
|
||||
classDesc.packageName() + "." + classDesc.displayName();
|
||||
}
|
||||
|
||||
private static Class<?> loadClass(String className, @Nullable ClassLoader classLoader) {
|
||||
String name = fromTypeDescriptor(className);
|
||||
return ClassUtils.resolveClassName(name, classLoader);
|
||||
}
|
||||
|
||||
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
|
||||
if (arrayValue.values().isEmpty()) {
|
||||
return new Object[0];
|
||||
}
|
||||
Stream<AnnotationValue> stream = arrayValue.values().stream();
|
||||
switch (arrayValue.values().getFirst()) {
|
||||
case AnnotationValue.OfInt _ -> {
|
||||
return stream.map(AnnotationValue.OfInt.class::cast).mapToInt(AnnotationValue.OfInt::intValue).toArray();
|
||||
}
|
||||
case AnnotationValue.OfDouble _ -> {
|
||||
return stream.map(AnnotationValue.OfDouble.class::cast).mapToDouble(AnnotationValue.OfDouble::doubleValue).toArray();
|
||||
}
|
||||
case AnnotationValue.OfLong _ -> {
|
||||
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
|
||||
}
|
||||
default -> {
|
||||
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
|
||||
return stream
|
||||
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
|
||||
.toArray(s -> (Object[]) Array.newInstance(arrayElementType, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static @Nullable <E extends Enum<E>> Enum<E> parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
|
||||
String enumClassName = fromTypeDescriptor(enumValue.className().stringValue());
|
||||
try {
|
||||
Class<E> enumClass = (Class<E>) ClassUtils.forName(enumClassName, classLoader);
|
||||
return Enum.valueOf(enumClass, enumValue.constantName().stringValue());
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
|
||||
AnnotationValue firstValue = values.getFirst();
|
||||
switch (firstValue) {
|
||||
case AnnotationValue.OfConstant constantValue -> {
|
||||
return constantValue.resolvedValue().getClass();
|
||||
}
|
||||
case AnnotationValue.OfAnnotation _ -> {
|
||||
return MergedAnnotation.class;
|
||||
}
|
||||
case AnnotationValue.OfClass _ -> {
|
||||
return String.class;
|
||||
}
|
||||
case AnnotationValue.OfEnum enumValue -> {
|
||||
return loadClass(enumValue.className().stringValue(), classLoader);
|
||||
}
|
||||
default -> {
|
||||
return Object.class;
|
||||
}
|
||||
}
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
record Source(Annotation entryName) {
|
||||
static class Builder {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private String className;
|
||||
|
||||
private AccessFlags accessFlags;
|
||||
|
||||
private Set<AccessFlag> innerAccessFlags;
|
||||
|
||||
private @Nullable String enclosingClassName;
|
||||
|
||||
private @Nullable String superClassName;
|
||||
|
||||
private Set<String> interfaceNames = new LinkedHashSet<>(4);
|
||||
|
||||
private Set<String> memberClassNames = new LinkedHashSet<>(4);
|
||||
|
||||
private Set<MethodMetadata> declaredMethods = new LinkedHashSet<>(4);
|
||||
|
||||
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet());
|
||||
|
||||
public Builder(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
void classEntry(ClassEntry classEntry) {
|
||||
this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue());
|
||||
}
|
||||
|
||||
void accessFlags(AccessFlags accessFlags) {
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
|
||||
void enclosingClassFromNestHost(ClassEntry thisClass) {
|
||||
if (this.enclosingClassName != null) {
|
||||
return;
|
||||
}
|
||||
String thisClassName = thisClass.name().stringValue();
|
||||
int currentClassIndex = thisClassName.lastIndexOf('$');
|
||||
if (currentClassIndex > 0) {
|
||||
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(
|
||||
thisClassName.substring(0, currentClassIndex));
|
||||
}
|
||||
}
|
||||
|
||||
void superClass(Superclass superClass) {
|
||||
this.superClassName = ClassUtils.convertResourcePathToClassName(
|
||||
superClass.superclassEntry().name().stringValue());
|
||||
}
|
||||
|
||||
void interfaces(Interfaces interfaces) {
|
||||
for (ClassEntry entry : interfaces.interfaces()) {
|
||||
this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue()));
|
||||
}
|
||||
}
|
||||
|
||||
void nestMembers(String currentClassName, InnerClassesAttribute innerClasses) {
|
||||
for (InnerClassInfo classInfo : innerClasses.classes()) {
|
||||
String innerClassName = classInfo.innerClass().name().stringValue();
|
||||
if (currentClassName.equals(innerClassName)) {
|
||||
// the current class is an inner class
|
||||
this.innerAccessFlags = classInfo.flags();
|
||||
classInfo.outerClass().ifPresent(outerClass ->
|
||||
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(outerClass.name().stringValue()));
|
||||
}
|
||||
classInfo.outerClass().ifPresent(outerClass -> {
|
||||
if (outerClass.name().stringValue().equals(currentClassName)) {
|
||||
// collecting data about actual inner classes
|
||||
this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void mergedAnnotations(MergedAnnotations mergedAnnotations) {
|
||||
this.mergedAnnotations = mergedAnnotations;
|
||||
}
|
||||
|
||||
void method(MethodModel method) {
|
||||
ClassFileMethodMetadata classFileMethodMetadata = ClassFileMethodMetadata.of(method, this.classLoader);
|
||||
if (!classFileMethodMetadata.isSynthetic() && !classFileMethodMetadata.isDefaultConstructor()) {
|
||||
this.declaredMethods.add(classFileMethodMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
ClassFileAnnotationMetadata build() {
|
||||
boolean independentInnerClass = (this.enclosingClassName != null) &&
|
||||
this.innerAccessFlags.contains(AccessFlag.STATIC);
|
||||
return new ClassFileAnnotationMetadata(this.className, this.accessFlags, this.enclosingClassName,
|
||||
this.superClassName, independentInnerClass, this.interfaceNames, this.memberClassNames,
|
||||
this.declaredMethods, this.mergedAnnotations);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-308
@@ -1,308 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.type.classreading;
|
||||
|
||||
import java.lang.classfile.AccessFlags;
|
||||
import java.lang.classfile.ClassModel;
|
||||
import java.lang.classfile.Interfaces;
|
||||
import java.lang.classfile.MethodModel;
|
||||
import java.lang.classfile.Superclass;
|
||||
import java.lang.classfile.attribute.InnerClassInfo;
|
||||
import java.lang.classfile.attribute.InnerClassesAttribute;
|
||||
import java.lang.classfile.attribute.NestHostAttribute;
|
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||
import java.lang.classfile.constantpool.ClassEntry;
|
||||
import java.lang.reflect.AccessFlag;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AnnotationMetadata} implementation that leverages
|
||||
* the {@link java.lang.classfile.ClassFile} API.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 7.0
|
||||
*/
|
||||
class ClassFileClassMetadata implements AnnotationMetadata {
|
||||
|
||||
private final String className;
|
||||
|
||||
private final AccessFlags accessFlags;
|
||||
|
||||
private final @Nullable String enclosingClassName;
|
||||
|
||||
private final @Nullable String superClassName;
|
||||
|
||||
private final boolean independentInnerClass;
|
||||
|
||||
private final Set<String> interfaceNames;
|
||||
|
||||
private final Set<String> memberClassNames;
|
||||
|
||||
private final Set<MethodMetadata> declaredMethods;
|
||||
|
||||
private final MergedAnnotations mergedAnnotations;
|
||||
|
||||
private @Nullable Set<String> annotationTypes;
|
||||
|
||||
|
||||
ClassFileClassMetadata(String className, AccessFlags accessFlags, @Nullable String enclosingClassName,
|
||||
@Nullable String superClassName, boolean independentInnerClass, Set<String> interfaceNames,
|
||||
Set<String> memberClassNames, Set<MethodMetadata> declaredMethods, MergedAnnotations mergedAnnotations) {
|
||||
|
||||
this.className = className;
|
||||
this.accessFlags = accessFlags;
|
||||
this.enclosingClassName = enclosingClassName;
|
||||
this.superClassName = (!className.endsWith(".package-info")) ? superClassName : null;
|
||||
this.independentInnerClass = independentInnerClass;
|
||||
this.interfaceNames = interfaceNames;
|
||||
this.memberClassNames = memberClassNames;
|
||||
this.declaredMethods = declaredMethods;
|
||||
this.mergedAnnotations = mergedAnnotations;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return this.className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterface() {
|
||||
return this.accessFlags.has(AccessFlag.INTERFACE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnnotation() {
|
||||
return this.accessFlags.has(AccessFlag.ANNOTATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbstract() {
|
||||
return this.accessFlags.has(AccessFlag.ABSTRACT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinal() {
|
||||
return this.accessFlags.has(AccessFlag.FINAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndependent() {
|
||||
return (this.enclosingClassName == null || this.independentInnerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getEnclosingClassName() {
|
||||
return this.enclosingClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSuperClassName() {
|
||||
return this.superClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getInterfaceNames() {
|
||||
return StringUtils.toStringArray(this.interfaceNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getMemberClassNames() {
|
||||
return StringUtils.toStringArray(this.memberClassNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergedAnnotations getAnnotations() {
|
||||
return this.mergedAnnotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAnnotationTypes() {
|
||||
Set<String> annotationTypes = this.annotationTypes;
|
||||
if (annotationTypes == null) {
|
||||
annotationTypes = Collections.unmodifiableSet(
|
||||
AnnotationMetadata.super.getAnnotationTypes());
|
||||
this.annotationTypes = annotationTypes;
|
||||
}
|
||||
return annotationTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
|
||||
Set<MethodMetadata> result = new LinkedHashSet<>(4);
|
||||
for (MethodMetadata annotatedMethod : this.declaredMethods) {
|
||||
if (annotatedMethod.isAnnotated(annotationName)) {
|
||||
result.add(annotatedMethod);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MethodMetadata> getDeclaredMethods() {
|
||||
return Collections.unmodifiableSet(this.declaredMethods);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof ClassFileClassMetadata that && this.className.equals(that.className)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.className.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.className;
|
||||
}
|
||||
|
||||
|
||||
static ClassFileClassMetadata of(ClassModel classModel, @Nullable ClassLoader classLoader) {
|
||||
Builder builder = new Builder(classLoader);
|
||||
builder.classEntry(classModel.thisClass());
|
||||
String currentClassName = classModel.thisClass().name().stringValue();
|
||||
classModel.elementStream().forEach(classElement -> {
|
||||
switch (classElement) {
|
||||
case AccessFlags flags -> {
|
||||
builder.accessFlags(flags);
|
||||
}
|
||||
case NestHostAttribute _ -> {
|
||||
builder.enclosingClass(classModel.thisClass());
|
||||
}
|
||||
case InnerClassesAttribute innerClasses -> {
|
||||
builder.nestMembers(currentClassName, innerClasses);
|
||||
}
|
||||
case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> {
|
||||
builder.mergedAnnotations(ClassFileAnnotationMetadata.createMergedAnnotations(
|
||||
ClassUtils.convertResourcePathToClassName(currentClassName), annotationsAttribute, classLoader));
|
||||
}
|
||||
case Superclass superclass -> {
|
||||
builder.superClass(superclass);
|
||||
}
|
||||
case Interfaces interfaces -> {
|
||||
builder.interfaces(interfaces);
|
||||
}
|
||||
case MethodModel method -> {
|
||||
builder.method(method);
|
||||
}
|
||||
default -> {
|
||||
// ignore class element
|
||||
}
|
||||
}
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
static class Builder {
|
||||
|
||||
private final ClassLoader clasLoader;
|
||||
|
||||
private String className;
|
||||
|
||||
private AccessFlags accessFlags;
|
||||
|
||||
private Set<AccessFlag> innerAccessFlags;
|
||||
|
||||
private @Nullable String enclosingClassName;
|
||||
|
||||
private @Nullable String superClassName;
|
||||
|
||||
private Set<String> interfaceNames = new LinkedHashSet<>(4);
|
||||
|
||||
private Set<String> memberClassNames = new LinkedHashSet<>(4);
|
||||
|
||||
private Set<MethodMetadata> declaredMethods = new LinkedHashSet<>(4);
|
||||
|
||||
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet());
|
||||
|
||||
public Builder(ClassLoader classLoader) {
|
||||
this.clasLoader = classLoader;
|
||||
}
|
||||
|
||||
void classEntry(ClassEntry classEntry) {
|
||||
this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue());
|
||||
}
|
||||
|
||||
void accessFlags(AccessFlags accessFlags) {
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
|
||||
void enclosingClass(ClassEntry thisClass) {
|
||||
String thisClassName = thisClass.name().stringValue();
|
||||
int currentClassIndex = thisClassName.lastIndexOf('$');
|
||||
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(thisClassName.substring(0, currentClassIndex));
|
||||
}
|
||||
|
||||
void superClass(Superclass superClass) {
|
||||
this.superClassName = ClassUtils.convertResourcePathToClassName(superClass.superclassEntry().name().stringValue());
|
||||
}
|
||||
|
||||
void interfaces(Interfaces interfaces) {
|
||||
for (ClassEntry entry : interfaces.interfaces()) {
|
||||
this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue()));
|
||||
}
|
||||
}
|
||||
|
||||
void nestMembers(String currentClassName, InnerClassesAttribute innerClasses) {
|
||||
for (InnerClassInfo classInfo : innerClasses.classes()) {
|
||||
String innerClassName = classInfo.innerClass().name().stringValue();
|
||||
if (currentClassName.equals(innerClassName)) {
|
||||
// the current class is an inner class
|
||||
this.innerAccessFlags = classInfo.flags();
|
||||
}
|
||||
classInfo.outerClass().ifPresent(outerClass -> {
|
||||
if (outerClass.name().stringValue().equals(currentClassName)) {
|
||||
// collecting data about actual inner classes
|
||||
this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void mergedAnnotations(MergedAnnotations mergedAnnotations) {
|
||||
this.mergedAnnotations = mergedAnnotations;
|
||||
}
|
||||
|
||||
void method(MethodModel method) {
|
||||
ClassFileMethodMetadata classFileMethodMetadata = ClassFileMethodMetadata.of(method, this.clasLoader);
|
||||
if (!classFileMethodMetadata.isSynthetic() && !classFileMethodMetadata.isDefaultConstructor()) {
|
||||
this.declaredMethods.add(classFileMethodMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
ClassFileClassMetadata build() {
|
||||
boolean independentInnerClass = (this.enclosingClassName != null) && this.innerAccessFlags.contains(AccessFlag.STATIC);
|
||||
return new ClassFileClassMetadata(this.className, this.accessFlags, this.enclosingClassName, this.superClassName,
|
||||
independentInnerClass, this.interfaceNames, this.memberClassNames, this.declaredMethods, this.mergedAnnotations);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+3
-4
@@ -42,13 +42,12 @@ final class ClassFileMetadataReader implements MetadataReader {
|
||||
|
||||
ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
|
||||
this.resource = resource;
|
||||
this.annotationMetadata = ClassFileClassMetadata.of(parseClassModel(resource), classLoader);
|
||||
this.annotationMetadata = ClassFileAnnotationMetadata.of(parseClassModel(resource), classLoader);
|
||||
}
|
||||
|
||||
private static ClassModel parseClassModel(Resource resource) throws IOException {
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
byte[] bytes = is.readAllBytes();
|
||||
return ClassFile.of().parse(bytes);
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
return ClassFile.of().parse(inputStream.readAllBytes());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import org.springframework.core.io.ResourceLoader;
|
||||
* @author Brian Clozel
|
||||
* @since 7.0
|
||||
*/
|
||||
class ClassFileMetadataReaderFactory extends AbstractMetadataReaderFactory {
|
||||
final class ClassFileMetadataReaderFactory extends AbstractMetadataReaderFactory {
|
||||
|
||||
/**
|
||||
* Create a new ClassFileMetadataReaderFactory for the default class loader.
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Brian Clozel
|
||||
* @since 7.0
|
||||
*/
|
||||
class ClassFileMethodMetadata implements MethodMetadata {
|
||||
final class ClassFileMethodMetadata implements MethodMetadata {
|
||||
|
||||
private final String methodName;
|
||||
|
||||
@@ -148,7 +148,7 @@ class ClassFileMethodMetadata implements MethodMetadata {
|
||||
MergedAnnotations annotations = methodModel.elementStream()
|
||||
.filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute)
|
||||
.findFirst()
|
||||
.map(element -> ClassFileAnnotationMetadata.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader))
|
||||
.map(element -> ClassFileAnnotationDelegate.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader))
|
||||
.orElse(MergedAnnotations.of(Collections.emptyList()));
|
||||
return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations);
|
||||
}
|
||||
|
||||
+2
@@ -25,5 +25,7 @@ import java.io.Serializable
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0.5
|
||||
*/
|
||||
@Suppress("removal", "DEPRECATION")
|
||||
@Deprecated(level = DeprecationLevel.WARNING, message = "Use ReflectionHints")
|
||||
inline fun <reified T : Serializable> SerializationHints.registerType(noinline serializationHint: (JavaSerializationHint.Builder) -> Unit = {}) =
|
||||
registerType(T::class.java, serializationHint::invoke)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user