Compare commits

..

1 Commits

Author SHA1 Message Date
Stéphane Nicoll 16ecbfc9fe Release v7.0.5 2026-02-18 13:31:03 +01:00
232 changed files with 1859 additions and 4831 deletions
+12 -12
View File
@@ -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.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/"
"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/"
] 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.3.0");
checkstyle.setToolVersion("13.2.0");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+2 -2
View File
@@ -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 })
}
+4 -4
View File
@@ -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
)
}
+1 -2
View File
@@ -330,10 +330,9 @@
*** 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/default-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.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,11 +81,6 @@ 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].
@@ -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, 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).
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).
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.
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.
====
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
@@ -309,8 +309,7 @@ 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` or `@Fallback` on a single bean
for each type.
through `@Order` values in combination with `@Primary` on a single bean for each type.
====
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
@@ -27,5 +27,4 @@ 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. For annotation-based configuration, see
xref:core/beans/annotation-config/autowired-primary.adoc[Fine-tuning with `@Primary` or `@Fallback`].
attribute set to `true`, it is selected.
@@ -556,11 +556,18 @@ Kotlin::
======
If you do not want to rely on the default bean-naming strategy, you can provide a custom
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`]
bean-naming strategy. First, implement the
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`]
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 examples show.
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.
[tabs]
======
@@ -595,27 +602,6 @@ 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,13 +67,6 @@ 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
@@ -87,7 +80,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 early initialization
.`BeanPostProcessor` instances and AOP auto-proxying
[NOTE]
====
Classes that implement the `BeanPostProcessor` interface are special and are treated
@@ -97,23 +90,17 @@ 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. More generally, any bean that is instantiated
during this early phase is not eligible for full post-processing by all
`BeanPostProcessor` instances.
thus, do not have aspects woven into them.
For any such bean, you should see a WARN-level log message similar to the following.
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)`.
[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
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.
====
@@ -148,7 +135,7 @@ Java::
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean);
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
@@ -177,48 +164,7 @@ Kotlin::
----
======
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:
The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -246,7 +192,7 @@ Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via
----
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 with any
even have a name, and, because it is a bean, it can be dependency-injected as you would any
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
script.)
@@ -354,23 +300,6 @@ 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,10 +17,9 @@ 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
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:
method to register a bean definition within an `ApplicationContext` of the type
specified as the method's return value. By default, the bean name is the same as
the method name. The following example shows a `@Bean` method declaration:
[tabs]
======
@@ -127,7 +126,7 @@ Kotlin::
----
======
However, this limits the visibility for advanced type prediction to the specified
However, this limits the visibility for advance 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,
@@ -474,15 +473,8 @@ Kotlin::
== Customizing Bean Naming
By default, configuration classes use a `@Bean` method's name as the name of the
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:
resulting bean. This functionality can be overridden, however, with the `name` attribute,
as the following example shows:
[tabs]
======
@@ -513,7 +505,6 @@ 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(Char::class.java)
.getValue(Character::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,7 +541,32 @@ 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.
include-code::./ListConcatenation[]
[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));
}
}
----
If we register `ListConcatenation` as the `OperatorOverloader` in a
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
@@ -564,8 +589,8 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val context = StandardEvaluationContext()
context.operatorOverloader = ListConcatenation()
StandardEvaluationContext context = StandardEvaluationContext()
context.setOperatorOverloader(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,6 +183,9 @@ 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`
@@ -193,9 +196,6 @@ 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>
@@ -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 for Spring's own annotation. Please refer to the JTA documentation
a drop-in replacement to 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,16 +89,47 @@ annotation in a `@Configuration` class. See the
{spring-framework-api}/transaction/annotation/EnableTransactionManagement.html[javadoc]
for full details.
The following example shows the configuration needed to enable annotation-driven transaction management:
In XML configuration, the `<tx:annotation-driven/>` tag provides similar convenience:
include-code::./AppConfig[tag=snippet,indent=0]
[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">
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.
<!-- 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.
Reactive transactional methods use reactive return types in contrast to imperative
programming arrangements as the following listing shows:
@@ -203,10 +234,8 @@ 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 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.
transactional behavior. In the preceding example, 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,
@@ -49,6 +49,7 @@ 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:
@@ -86,6 +87,7 @@ 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]).
@@ -94,24 +96,22 @@ 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 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].
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.
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.
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.
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML Resources]
* 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/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/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]
@@ -1,53 +0,0 @@
[[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.
@@ -1,32 +1,33 @@
[[testcontext-ctx-management-mixed-config]]
= Mixing Component Classes, XML, and Groovy Scripts
= Mixing XML, Groovy Scripts, and Component Classes
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
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
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, `@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
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
`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy
scripts simultaneously. Furthermore, third-party frameworks may choose to support the
declaration of both `classes` and `locations` through `@ContextConfiguration`, and, with
declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with
the standard testing support in the TestContext framework, you have the following options.
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
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
to how you configure your application in production: In production configuration, you
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.
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.
@@ -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,9 +49,10 @@ 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`.
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.
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.
@@ -251,8 +251,7 @@ Kotlin::
======
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
mark it with `@Primary`, or mark the one declared in the MVC config with `@Fallback`, in
order to avoid conflict.
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC config.
[[webflux-config-content-negotiation]]
@@ -49,9 +49,10 @@ 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`.
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.
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.
@@ -19,5 +19,4 @@ 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`, or mark the one declared in the MVC configuration with
`@Fallback`, in order to avoid conflict.
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC configuration.
@@ -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 URIs.
This section describes various options available in the Spring Framework to work with URI's.
include::partial$web/web-uris.adoc[leveloffset=+1]
@@ -2,7 +2,7 @@
= UriComponents
[.small]#Spring MVC and Spring WebFlux#
`UriComponentsBuilder` helps to build URIs from URI templates with variables, as the following example shows:
`UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows:
[tabs]
======
@@ -128,7 +128,7 @@ Kotlin::
= UriBuilder
[.small]#Spring MVC and Spring WebFlux#
<<uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
<<web-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 URLs. For more details, see the URL Living Standard and URL parsing
leniently user typed URL's. 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 URLs in order to inspect and possibly validated URI components
to parse user provided URL's 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 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
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
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()
----
======
+1 -1
View File
@@ -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.8",
"fast-xml-parser": "5.3.4",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
}
}
@@ -1,47 +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.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));
}
}
@@ -1,42 +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.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[]
@@ -1,20 +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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations;
public class DefaultFooService implements FooService {
}
@@ -1,20 +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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations;
public interface FooService {
}
@@ -21,6 +21,7 @@ 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
@@ -17,7 +17,9 @@
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;
@@ -17,7 +17,9 @@
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;
@@ -22,6 +22,7 @@ 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[]
@@ -19,8 +19,13 @@ 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
@@ -17,6 +17,7 @@
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;
@@ -1,37 +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.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]")
}
}
@@ -1,41 +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.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[]
@@ -1,20 +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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations
class DefaultFooService : FooService {
}
@@ -1,20 +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.docs.dataaccess.transaction.declarative.transactiondeclarativeannotations
interface FooService {
}
@@ -16,9 +16,12 @@
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.*
/**
@@ -1,30 +0,0 @@
<?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[] -->
+15 -15
View File
@@ -8,19 +8,19 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.20.2"))
api(platform("io.micrometer:micrometer-bom:1.16.4"))
api(platform("io.micrometer:micrometer-bom:1.16.3"))
api(platform("io.netty:netty-bom:4.2.10.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.4"))
api(platform("io.projectreactor:reactor-bom:2025.0.3"))
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.7"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.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.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.3"))
api(platform("org.mockito:mockito-bom:5.22.0"))
api(platform("org.junit:junit-bom:6.0.2"))
api(platform("org.mockito:mockito-bom:5.21.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.34.0")
api("com.h2database:h2:2.4.240")
api("com.google.protobuf:protobuf-java-util:4.33.4")
api("com.h2database:h2:2.3.232")
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.1.0.RELEASE")
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
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.2")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.4")
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-RC1")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-B13")
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.6.Final")
api("org.hibernate.orm:hibernate-core:7.2.4.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.41.0")
api("org.seleniumhq.selenium:selenium-java:4.41.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.40.0")
api("org.seleniumhq.selenium:selenium-java:4.40.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.6")
api("org.yaml:snakeyaml:2.5")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=7.0.6
version=7.0.5
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+1 -1
View File
@@ -61,4 +61,4 @@ void configureDeploymentRepository(Project project) {
}
}
}
}
}
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -1
View File
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/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/.
@@ -40,13 +40,6 @@ 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
@@ -35,14 +35,6 @@ 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
@@ -23,7 +23,6 @@ import org.springframework.beans.factory.config.BeanDefinition;
*
* @author Juergen Hoeller
* @since 2.0.3
* @see org.springframework.context.annotation.ConfigurationBeanNameGenerator
*/
public interface BeanNameGenerator {
@@ -109,9 +109,7 @@ public class AnnotatedBeanDefinitionReader {
/**
* Set the {@code BeanNameGenerator} to use for detected bean classes.
* <p>The default is an {@link AnnotationBeanNameGenerator}.
* @see FullyQualifiedAnnotationBeanNameGenerator
* @see FullyQualifiedConfigurationBeanNameGenerator
* <p>The default is a {@link AnnotationBeanNameGenerator}.
*/
public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
this.beanNameGenerator =
@@ -118,20 +118,14 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
/**
* Provide a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader}
* 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.
* and/or {@link ClassPathBeanDefinitionScanner}, if any.
* <p>Default is {@link AnnotationBeanNameGenerator}.
* <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,47 +35,48 @@ import org.springframework.core.annotation.AliasFor;
* example:
*
* <pre class="code">
* &#064;Bean
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }</pre>
* &#064;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 default can be overridden by configuring a {@link ConfigurationBeanNameGenerator}
* &mdash; 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.
* 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.
*
* <pre class="code">
* &#064;Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }</pre>
* &#064;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, Fallback, Order</h3>
* <h3>Profile, Scope, Lazy, DependsOn, Primary, 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">
* &#064;Bean
* &#064;Profile("production")
* &#064;Scope("prototype")
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }</pre>
* &#064;Bean
* &#064;Profile("production")
* &#064;Scope("prototype")
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }
* </pre>
*
* The semantics of the aforementioned annotations match their use at the component
* The semantics of the above-mentioned 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.
@@ -84,9 +85,6 @@ 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
@@ -102,8 +100,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} or
* {@code @Fallback} on a single bean per type.
* be modeled through {@code @Order} values in combination with {@code @Primary} on
* a single bean per type.
*
* <h3>{@code @Bean} Methods in {@code @Configuration} Classes</h3>
*
@@ -121,17 +119,17 @@ import org.springframework.core.annotation.AliasFor;
* &#064;Configuration
* public class AppConfig {
*
* &#064;Bean
* public FooService fooService() {
* return new FooService(fooRepository());
* }
* &#064;Bean
* public FooService fooService() {
* return new FooService(fooRepository());
* }
*
* &#064;Bean
* public FooRepository fooRepository() {
* return new JdbcFooRepository(dataSource());
* }
* &#064;Bean
* public FooRepository fooRepository() {
* return new JdbcFooRepository(dataSource());
* }
*
* // ...
* // ...
* }</pre>
*
* <h3>{@code @Bean} <em>Lite</em> Mode</h3>
@@ -160,21 +158,20 @@ import org.springframework.core.annotation.AliasFor;
* <pre class="code">
* &#064;Component
* public class Calculator {
* public int sum(int a, int b) {
* return a+b;
* }
* public int sum(int a, int b) {
* return a+b;
* }
*
* &#064;Bean
* public MyBean myBean() {
* return new MyBean();
* }
* &#064;Bean
* public MyBean myBean() {
* return new MyBean();
* }
* }</pre>
*
* <h3>Bootstrapping</h3>
*
* <p>See the {@link Configuration @Configuration} javadoc for further details
* including how to bootstrap the container using
* {@link AnnotationConfigApplicationContext} and friends.
* <p>See the @{@link Configuration} javadoc for further details including how to bootstrap
* the container using {@link AnnotationConfigApplicationContext} and friends.
*
* <h3>{@code BeanFactoryPostProcessor}-returning {@code @Bean} methods</h3>
*
@@ -186,44 +183,20 @@ import org.springframework.core.annotation.AliasFor;
* lifecycle issues, mark {@code BFPP}-returning {@code @Bean} methods as {@code static}. For example:
*
* <pre class="code">
* &#064;Bean
* public static PropertySourcesPlaceholderConfigurer pspc() {
* // instantiate, configure and return pspc ...
* }</pre>
* &#064;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 aforementioned lifecycle conflicts.
* declaring {@code @Configuration} class, thus avoiding the above-mentioned 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">
* &#064;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
@@ -235,13 +208,9 @@ 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)
@@ -260,11 +229,10 @@ public @interface Bean {
/**
* The name of this bean, or if several names, a primary bean name plus aliases.
* <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>If left unspecified, the name of the bean is the name of the annotated method.
* If specified, the method name is ignored.
* <p>The bean name and aliases may also be configured via the {@link #value}
* attribute.
* attribute if no other attributes are declared.
* @see #value
*/
@AliasFor("value")
@@ -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 or meta-annotated with Spring's
* {@link org.springframework.stereotype.Component @Component} annotation, such as the
* default filters include classes that are annotated with Spring's
* {@link org.springframework.stereotype.Component @Component},
* {@link org.springframework.stereotype.Repository @Repository},
* {@link org.springframework.stereotype.Service @Service}, and
* {@link org.springframework.stereotype.Controller @Controller} stereotypes.
* {@link org.springframework.stereotype.Service @Service}, or
* {@link org.springframework.stereotype.Controller @Controller} stereotype.
*
* <p>Also supports JSR-330's {@link jakarta.inject.Named} annotations, if available.
*
@@ -204,11 +204,8 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
}
/**
* Set the {@link BeanNameGenerator} to use for detected bean classes.
* <p>Default is an {@code AnnotationBeanNameGenerator}.
* @see AnnotationBeanNameGenerator
* @see FullyQualifiedAnnotationBeanNameGenerator
* @see FullyQualifiedConfigurationBeanNameGenerator
* Set the BeanNameGenerator to use for detected bean classes.
* <p>Default is a {@link AnnotationBeanNameGenerator}.
*/
public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
this.beanNameGenerator =
@@ -109,18 +109,14 @@ public @interface ComponentScan {
/**
* The {@link BeanNameGenerator} class to be used for naming detected components
* within the Spring container.
* <p>The default value of the {@code BeanNameGenerator} interface itself indicates
* <p>The default value of the {@link 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. 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.
* application context at bootstrap time.
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
* @see AnnotationBeanNameGenerator
* @see FullyQualifiedAnnotationBeanNameGenerator
* @see FullyQualifiedConfigurationBeanNameGenerator
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@@ -437,8 +437,6 @@ 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 "";
@@ -37,10 +37,9 @@ public interface ConfigurationBeanNameGenerator extends BeanNameGenerator {
/**
* Derive a default bean name for the given {@link Bean @Bean} method,
* taking into account the specified {@link Bean#name() name} attribute.
* providing the {@link Bean#name() name} attribute specified.
* @param beanMethod the method metadata for the {@link Bean @Bean} method
* @param beanName the {@link Bean#name() name} attribute or {@code null} if
* none is specified
* @param beanName the {@link Bean#name() name} attribute or {@code null} if non is specified
* @return the default bean name to use
*/
String deriveBeanName(MethodMetadata beanMethod, @Nullable String beanName);
@@ -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) : localBeanName);
cbng.deriveBeanName(metadata, beanName) : defaultBeanName(beanName, methodName));
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() with bean name '%s'"
.formatted(configClass.getMetadata().getClassName(), methodName, beanName));
logger.trace("Registering bean definition for @Bean method %s.%s()"
.formatted(configClass.getMetadata().getClassName(), 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;
}
@@ -387,13 +387,13 @@ class ConfigurationClassEnhancer {
// create the bean instance.
if (logger.isInfoEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
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()));
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()));
}
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
@@ -242,15 +242,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
/**
* Set the {@link BeanNameGenerator} to be used when triggering component scanning
* 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})
* 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})
* and a variant thereof for imported configuration classes (using unique fully-qualified
* class names instead of standard component overriding).
* <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>Note that this strategy does <em>not</em> apply to {@link Bean} methods.
* <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
@@ -258,9 +255,6 @@ 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");
@@ -16,7 +16,6 @@
package org.springframework.context.support;
import java.nio.charset.Charset;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
@@ -33,7 +32,6 @@ import org.springframework.util.ObjectUtils;
* configuration methods and corresponding semantic definitions.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 4.3
* @see ResourceBundleMessageSource
* @see ReloadableResourceBundleMessageSource
@@ -42,7 +40,7 @@ public abstract class AbstractResourceBasedMessageSource extends AbstractMessage
private final Set<String> basenameSet = new LinkedHashSet<>(4);
private @Nullable Charset defaultCharset;
private @Nullable String defaultEncoding;
private boolean fallbackToSystemLocale = true;
@@ -55,7 +53,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
@@ -120,52 +118,25 @@ public abstract class AbstractResourceBasedMessageSource extends AbstractMessage
/**
* Set the default charset to use for parsing properties files.
* <p>Used if no file-specific charset is specified for a file.
* 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.defaultCharset = (defaultEncoding != null ? Charset.forName(defaultEncoding) : null);
this.defaultEncoding = defaultEncoding;
}
/**
* Return the default charset to use for parsing properties files, if any.
* @since 4.3
* @see #getDefaultCharset()
*/
protected @Nullable String getDefaultEncoding() {
return (this.defaultCharset != null ? this.defaultCharset.name() : null);
return this.defaultEncoding;
}
/**
* 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
@@ -24,8 +24,6 @@ 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;
@@ -100,8 +98,7 @@ class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor,
try {
ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster();
multicaster.removeApplicationListener(applicationListener);
multicaster.removeApplicationListenerBean(
bean instanceof FactoryBean ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
multicaster.removeApplicationListenerBean(beanName);
}
catch (IllegalStateException ex) {
// ApplicationEventMulticaster not initialized yet - no need to remove a listener
@@ -19,7 +19,6 @@ 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;
@@ -81,10 +80,9 @@ import org.springframework.util.StringUtils;
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @author Sam Brannen
* @see #setCacheSeconds
* @see #setBasenames
* @see #setDefaultCharset
* @see #setDefaultEncoding
* @see #setFileEncodings
* @see #setPropertiesPersister
* @see #setResourceLoader
@@ -95,12 +93,10 @@ 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_EXTENSION, XML_EXTENSION);
private List<String> fileExtensions = List.of(".properties", XML_EXTENSION);
private @Nullable Properties fileEncodings;
@@ -139,7 +135,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 object with filenames as keys and charset
* @param fileEncodings a Properties 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".
@@ -382,18 +378,18 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
StringBuilder temp = new StringBuilder(basename);
temp.append('_');
if (!language.isEmpty()) {
if (language.length() > 0) {
temp.append(language);
result.add(0, temp.toString());
}
temp.append('_');
if (!country.isEmpty()) {
if (country.length() > 0) {
temp.append(country);
result.add(0, temp.toString());
}
if (!variant.isEmpty() && (!language.isEmpty() || !country.isEmpty())) {
if (variant.length() > 0 && (language.length() > 0 || country.length() > 0)) {
temp.append('_').append(variant);
result.add(0, temp.toString());
}
@@ -562,36 +558,33 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
*/
protected Properties loadProperties(Resource resource, String filename) throws IOException {
Properties props = newProperties();
try (InputStream inputStream = resource.getInputStream()) {
try (InputStream is = 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, inputStream);
this.propertiesPersister.loadFromXml(props, is);
}
else {
Charset charset = null;
String encoding = null;
if (this.fileEncodings != null) {
String charsetName = this.fileEncodings.getProperty(filename);
if (charsetName != null) {
charset = Charset.forName(charsetName);
}
encoding = this.fileEncodings.getProperty(filename);
}
if (charset == null) {
charset = getDefaultCharset();
if (encoding == null) {
encoding = getDefaultEncoding();
}
if (charset != null) {
if (encoding != null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + charset + "'");
logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'");
}
this.propertiesPersister.load(props, new InputStreamReader(inputStream, charset));
this.propertiesPersister.load(props, new InputStreamReader(is, encoding));
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Loading properties [" + resource.getFilename() + "]");
}
this.propertiesPersister.load(props, inputStream);
this.propertiesPersister.load(props, is);
}
}
return props;
@@ -22,8 +22,6 @@ 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;
@@ -59,11 +57,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 #setDefaultCharset Charset}: by default, ISO-8859-1; consider switching
* {@link #setDefaultEncoding encoding}: 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 an ISO-8859-1 fallback on
* retrieval with the platform default encoding: UTF-8 with a 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.
@@ -72,7 +70,6 @@ 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
@@ -86,7 +83,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
/**
* Cache to hold loaded ResourceBundles.
* <p>This Map is keyed with the bundle basename, which holds a Map that is
* 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.
@@ -96,7 +93,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
/**
* Cache to hold already generated MessageFormats.
* <p>This Map is keyed with the ResourceBundle, which holds a Map that is
* 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.
@@ -109,7 +106,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
public ResourceBundleMessageSource() {
setDefaultCharset(StandardCharsets.ISO_8859_1);
setDefaultEncoding("ISO-8859-1");
}
@@ -242,12 +239,12 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
catch (UnsupportedOperationException ex) {
// Probably in a Java Module System environment on JDK 9+
this.control = null;
Charset charset = getDefaultCharset();
if (charset != null && logger.isInfoEnabled()) {
String encoding = getDefaultEncoding();
if (encoding != null && logger.isInfoEnabled()) {
logger.info("ResourceBundleMessageSource is configured to read resources with encoding '" +
charset + "' but ResourceBundle.Control is not supported in current system environment: " +
encoding + "' 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 'defaultCharset' property to 'null' " +
"platform default encoding. Consider setting the 'defaultEncoding' property to 'null' " +
"for participating in the platform default and therefore avoiding this log message.");
}
}
@@ -259,7 +256,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 {@linkplain #setDefaultCharset "defaultCharset"},
* <p>This will be called in case of a {@link #setDefaultEncoding "defaultEncoding"},
* 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
@@ -279,9 +276,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 {@linkplain #setDefaultCharset "defaultCharset"}
* <p>This will only be called with {@link #setDefaultEncoding "defaultEncoding"}
* set to {@code null}, explicitly enforcing the platform default encoding
* (which is UTF-8 with an ISO-8859-1 fallback on JDK 9+ but configurable
* (which is UTF-8 with a 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
@@ -407,9 +404,9 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
inputStream = classLoader.getResourceAsStream(resourceName);
}
if (inputStream != null) {
Charset charset = getDefaultCharset();
if (charset != null) {
try (InputStreamReader bundleReader = new InputStreamReader(inputStream, charset)) {
String encoding = getDefaultEncoding();
if (encoding != null) {
try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
return loadBundle(bundleReader);
}
}
@@ -239,8 +239,8 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
* @since 6.1.4
* @see #initiateShutdown()
*/
public void setStrictEarlyShutdown(boolean strictEarlyShutdown) {
this.strictEarlyShutdown = strictEarlyShutdown;
public void setStrictEarlyShutdown(boolean defaultEarlyShutdown) {
this.strictEarlyShutdown = defaultEarlyShutdown;
}
/**
@@ -30,7 +30,6 @@ 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;
@@ -436,15 +435,12 @@ 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();
@@ -786,27 +782,6 @@ 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;
@@ -16,7 +16,6 @@
package org.springframework.context.support;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -31,18 +30,14 @@ 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 {
@@ -277,30 +272,25 @@ 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 resourceBundleMessageSourceWithDefaultCharsetName() {
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setBasename("org/springframework/context/support/messages");
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 // gh-36413
void resourceBundleMessageSourceWithDefaultCharset() {
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setBasename("org/springframework/context/support/messages");
ms.setDefaultCharset(ISO_8859_1);
ms.setDefaultEncoding("ISO-8859-1");
assertThat(ms.getMessage("code1", null, Locale.ENGLISH)).isEqualTo("message1");
assertThat(ms.getMessage("code2", null, Locale.GERMAN)).isEqualTo("nachricht2");
}
@Test
void resourceBundleMessageSourceWithInappropriateDefaultCharset() {
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));
}
@Test
void reloadableResourceBundleMessageSourceStandalone() {
ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
@@ -363,13 +353,13 @@ class ResourceBundleMessageSourceTests {
void reloadableResourceBundleMessageSourceWithDefaultCharset() {
ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
ms.setBasename("org/springframework/context/support/messages");
ms.setDefaultCharset(ISO_8859_1);
ms.setDefaultEncoding("ISO-8859-1");
assertThat(ms.getMessage("code1", null, Locale.ENGLISH)).isEqualTo("message1");
assertThat(ms.getMessage("code2", null, Locale.GERMAN)).isEqualTo("nachricht2");
}
@Test
void reloadableResourceBundleMessageSourceWithInappropriateDefaultCharsetName() {
void reloadableResourceBundleMessageSourceWithInappropriateDefaultCharset() {
ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
ms.setBasename("org/springframework/context/support/messages");
ms.setDefaultEncoding("unicode");
+2 -2
View File
@@ -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,6 +16,7 @@
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;
@@ -50,9 +51,7 @@ public enum HintType {
/**
* Java serialization hint, as described by {@link org.springframework.aot.hint.JavaSerializationHint}.
*/
@Deprecated(since = "7.0.5", forRemoval = true)
@SuppressWarnings("removal")
JAVA_SERIALIZATION(org.springframework.aot.hint.JavaSerializationHint.class),
JAVA_SERIALIZATION(JavaSerializationHint.class),
/**
* JDK proxies hint, as described by {@link org.springframework.aot.hint.ProxyHints#jdkProxyHints()}.
+2 -1
View File
@@ -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.google.code.findbugs:jsr305")
testCompileOnly("com.github.ben-manes.caffeine:caffeine")
testFixturesImplementation("io.projectreactor:reactor-test")
testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("org.junit.jupiter:junit-jupiter")
@@ -100,6 +100,7 @@ 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,9 +27,7 @@ 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,13 +37,10 @@ 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;
}
/**
@@ -78,21 +75,11 @@ 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.javaSerialization, that.javaSerialization)));
Objects.equals(this.reachableType, that.reachableType)));
}
@Override
@@ -110,8 +97,6 @@ public final class JdkProxyHint implements ConditionalHint {
private @Nullable TypeReference reachableType;
private boolean javaSerialization;
Builder() {
this.proxiedInterfaces = new LinkedList<>();
}
@@ -146,17 +131,6 @@ 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
@@ -1,186 +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.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,10 +21,8 @@ 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;
@@ -47,7 +45,6 @@ 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.
@@ -57,16 +54,6 @@ 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}.
@@ -244,42 +231,6 @@ 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;
/**
* Hints that can be used to optimize the application runtime.
* Gather hints that can be used to optimize the application runtime.
*
* <p>The use of reflection can be recorded for individual members of a type,
* lambdas, or broader {@linkplain MemberCategory member categories}.
* <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>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.
* <p>Hints that require the need for Java serialization of proxies can be
* recorded as well.
*
* @author Stephane Nicoll
* @author Janne Valkealahti
@@ -37,7 +37,6 @@ public class RuntimeHints {
private final ResourceHints resources = new ResourceHints();
@SuppressWarnings("removal")
private final SerializationHints serialization = new SerializationHints();
private final ProxyHints proxies = new ProxyHints();
@@ -64,10 +63,7 @@ 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;
}
@@ -81,8 +77,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,10 +30,7 @@ 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,8 +53,6 @@ public final class TypeHint implements ConditionalHint {
private final Set<MemberCategory> memberCategories;
private final boolean javaSerialization;
private TypeHint(Builder builder) {
this.type = builder.type;
@@ -63,7 +61,6 @@ 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;
}
/**
@@ -123,15 +120,6 @@ 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 + "]";
@@ -165,8 +153,6 @@ public final class TypeHint implements ConditionalHint {
private final Set<MemberCategory> memberCategories = new HashSet<>();
private boolean javaSerialization;
Builder(TypeReference type) {
this.type = type;
}
@@ -274,17 +260,6 @@ 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
@@ -260,7 +260,7 @@ public class ReflectionHintsPredicates {
}
/**
* Return a predicate that checks whether a reflective field access hint is registered for the field.
* Return a predicate that checks whether an invocation 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,8 +287,7 @@ public class ReflectionHintsPredicates {
}
/**
* Return a predicate that checks whether a reflective field access hint is
* registered for the given field.
* Return a predicate that checks whether an invocation hint is registered for the given field.
* @param field the field
* @return the {@link RuntimeHints} predicate
* @since 7.0
@@ -298,32 +297,6 @@ 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> {
@@ -536,26 +509,4 @@ 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;
}
}
}
@@ -46,7 +46,6 @@ 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();
@@ -74,10 +73,7 @@ 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;
}
@@ -29,10 +29,7 @@ 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() {
@@ -44,17 +44,12 @@ public abstract class NativeConfigurationWriter {
}
private boolean hasAnyHint(RuntimeHints hints) {
return (hints.proxies().jdkProxyHints().findAny().isPresent() ||
return (hints.serialization().javaSerializationHints().findAny().isPresent() ||
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() ||
hasAnyDeprecatedHint(hints));
}
@SuppressWarnings("removal")
private boolean hasAnyDeprecatedHint(RuntimeHints hints) {
return hints.serialization().javaSerializationHints().findAny().isPresent();
hints.jni().typeHints().findAny().isPresent());
}
/**
@@ -22,7 +22,6 @@ 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;
@@ -33,9 +32,7 @@ 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;
@@ -64,37 +61,17 @@ 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(hints));
List<Map<String, Object>> reflectionHints = new ArrayList<>();
reflectionHints.addAll(hints.reflection().typeHints()
.sorted(Comparator.comparing(TypeHint::getType))
.map(this::toAttributes).toList());
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()
@@ -111,33 +88,6 @@ 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;
}
@@ -198,14 +148,7 @@ 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);
}
}
}
@@ -16,7 +16,6 @@
package org.springframework.aot.nativex;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -36,9 +35,8 @@ import org.springframework.aot.hint.ResourcePatternHints;
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.0
* @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>
* @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>
*/
class ResourceHintsAttributes {
@@ -50,21 +48,22 @@ class ResourceHintsAttributes {
public List<Map<String, Object>> resources(ResourceHints hint) {
List<Map<String, Object>> resourceHints = new ArrayList<>();
resourceHints.addAll(hint.resourcePatternHints()
return hint.resourcePatternHints()
.map(ResourcePatternHints::getIncludes).flatMap(List::stream).distinct()
.sorted(RESOURCE_PATTERN_HINT_COMPARATOR)
.map(this::toAttributes).toList());
resourceHints.addAll(hint.resourceBundleHints()
.map(this::toAttributes).toList();
}
public List<Map<String, Object>> resourceBundles(ResourceHints hint) {
return hint.resourceBundleHints()
.sorted(RESOURCE_BUNDLE_HINT_COMPARATOR)
.map(this::toAttributes).toList());
return resourceHints;
.map(this::toAttributes).toList();
}
private Map<String, Object> toAttributes(ResourceBundleHint hint) {
Map<String, Object> attributes = new LinkedHashMap<>();
handleCondition(attributes, hint);
attributes.put("bundle", hint.getBaseName());
attributes.put("name", hint.getBaseName());
return attributes;
}
@@ -28,9 +28,8 @@ 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/jdk25/reference-manual/native-image/metadata">GraalVM Reachability Metadata</a>
* @see <a href="https://www.graalvm.org/jdk23/reference-manual/native-image/metadata/#specifying-metadata-with-json">GraalVM Reachability Metadata</a>
*/
class RuntimeHintsWriter {
@@ -52,6 +51,14 @@ 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);
}
@@ -0,0 +1,63 @@
/*
* 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);
}
}
}
@@ -137,20 +137,14 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
String string = this.string;
if (string == null) {
StringBuilder builder = new StringBuilder("@").append(getName(this.type)).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)));
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(')');
string = builder.toString();
@@ -193,7 +187,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';
@@ -202,7 +196,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
return Float.toString((Float) value) + 'f';
}
if (type == Double.class) {
return Double.toString((Double) value);
return Double.toString((Double) value) + 'd';
}
if (value instanceof Enum<?> e) {
return e.name();
@@ -19,12 +19,10 @@ 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;
@@ -96,9 +94,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
private boolean rejectTasksWhenLimitReached = false;
private final AtomicBoolean closed = new AtomicBoolean();
private boolean cancelled = false; // within activeThreads synchronization
private volatile boolean active = true;
/**
@@ -273,7 +269,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
* @see #close()
*/
public boolean isActive() {
return !this.closed.get();
return this.active;
}
/**
@@ -311,15 +307,14 @@ 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() + " is not active");
throw new TaskRejectedException(getClass().getSimpleName() + " has been closed already");
}
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, future));
doExecute(new TaskTrackingRunnable(taskToUse));
}
catch (Throwable ex) {
// Release concurrency permit if thread creation fails
@@ -329,7 +324,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
}
}
else if (this.activeThreads != null) {
doExecute(new TaskTrackingRunnable(taskToUse, future));
doExecute(new TaskTrackingRunnable(taskToUse));
}
else {
doExecute(taskToUse);
@@ -389,13 +384,11 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
*/
@Override
public void close() {
if (this.closed.compareAndSet(false, true)) {
if (this.active) {
this.active = false;
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);
}
@@ -409,7 +402,6 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
this.cancelled = true;
}
if (!this.cancelRemainingTasksOnClose) {
// Late interrupt for remaining tasks after timeout
@@ -420,15 +412,6 @@ 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,
@@ -470,12 +453,9 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
private final Runnable task;
private final @Nullable Future<?> future;
public TaskTrackingRunnable(Runnable task, @Nullable Future<?> future) {
public TaskTrackingRunnable(Runnable task) {
Assert.notNull(task, "Task must not be null");
this.task = task;
this.future = future;
}
@Override
@@ -484,10 +464,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
Thread thread = null;
if (threads != null) {
thread = Thread.currentThread();
synchronized (threads) {
checkCancelled(this.future);
threads.add(thread);
}
threads.add(thread);
}
try {
this.task.run();
@@ -495,7 +472,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
finally {
if (threads != null) {
threads.remove(thread);
if (closed.get()) {
if (!isActive()) {
synchronized (threads) {
if (threads.isEmpty()) {
threads.notify();
@@ -117,7 +117,6 @@ 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,
@@ -32,6 +32,7 @@ import org.springframework.core.io.ResourceLoader;
*/
public class SimpleMetadataReaderFactory extends AbstractMetadataReaderFactory {
/**
* Create a new SimpleMetadataReaderFactory for the default class loader.
*/
@@ -56,7 +57,6 @@ 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
* "defaultCharset" and "fileEncodings" properties).
* "defaultEncoding" and "fileEncodings" properties).
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
@@ -24,6 +24,7 @@ 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;
@@ -694,4 +695,10 @@ 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,13 +221,9 @@ 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,
* or the given InputStream if it is non-closing already
* @return a version of the InputStream that ignores calls to close
*/
public static InputStream nonClosing(InputStream in) {
if (in instanceof NonClosingInputStream) {
return in;
}
Assert.notNull(in, "No InputStream specified");
return new NonClosingInputStream(in);
}
@@ -236,38 +232,15 @@ 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,
* or the given OutputStream if it is non-closing already
* @see #nonFlushing(OutputStream)
* @return a version of the OutputStream that ignores calls to close
*/
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 class NonClosingInputStream extends FilterInputStream {
private static final class NonClosingInputStream extends FilterInputStream {
public NonClosingInputStream(InputStream in) {
super(in);
@@ -299,7 +272,7 @@ public abstract class StreamUtils {
}
private static class NonClosingOutputStream extends FilterOutputStream {
private static final class NonClosingOutputStream extends FilterOutputStream {
public NonClosingOutputStream(OutputStream out) {
super(out);
@@ -316,16 +289,4 @@ public abstract class StreamUtils {
}
}
private static class NonFlushingOutputStream extends NonClosingOutputStream {
public NonFlushingOutputStream(OutputStream out) {
super(out);
}
@Override
public void flush() throws IOException {
}
}
}
@@ -1,177 +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.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) {
}
}
@@ -16,305 +16,165 @@
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.Annotation;
import java.lang.classfile.AnnotationElement;
import java.lang.classfile.AnnotationValue;
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
import java.lang.classfile.constantpool.ClassEntry;
import java.lang.reflect.AccessFlag;
import java.lang.constant.ClassDesc;
import java.lang.reflect.Array;
import java.util.Collections;
import java.util.LinkedHashSet;
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.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.
* Parse {@link RuntimeVisibleAnnotationsAttribute} into {@link MergedAnnotations}
* instances.
*
* @author Brian Clozel
* @author Juergen Hoeller
* @since 7.0
*/
final class ClassFileAnnotationMetadata implements AnnotationMetadata {
abstract class ClassFileAnnotationMetadata {
private final String className;
static MergedAnnotations createMergedAnnotations(
String className, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) {
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;
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) {
@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;
String typeName = fromTypeDescriptor(annotation.className().stringValue());
if (AnnotationFilter.PLAIN.matches(typeName)) {
return null;
}
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> 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 builder.build();
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;
}
}
}
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);
}
record Source(Annotation entryName) {
}
}
@@ -0,0 +1,308 @@
/*
* 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);
}
}
}
@@ -42,12 +42,13 @@ final class ClassFileMetadataReader implements MetadataReader {
ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
this.resource = resource;
this.annotationMetadata = ClassFileAnnotationMetadata.of(parseClassModel(resource), classLoader);
this.annotationMetadata = ClassFileClassMetadata.of(parseClassModel(resource), classLoader);
}
private static ClassModel parseClassModel(Resource resource) throws IOException {
try (InputStream inputStream = resource.getInputStream()) {
return ClassFile.of().parse(inputStream.readAllBytes());
try (InputStream is = resource.getInputStream()) {
byte[] bytes = is.readAllBytes();
return ClassFile.of().parse(bytes);
}
}
@@ -30,7 +30,7 @@ import org.springframework.core.io.ResourceLoader;
* @author Brian Clozel
* @since 7.0
*/
final class ClassFileMetadataReaderFactory extends AbstractMetadataReaderFactory {
class ClassFileMetadataReaderFactory extends AbstractMetadataReaderFactory {
/**
* Create a new ClassFileMetadataReaderFactory for the default class loader.
@@ -41,7 +41,7 @@ import org.springframework.util.ClassUtils;
* @author Brian Clozel
* @since 7.0
*/
final class ClassFileMethodMetadata implements MethodMetadata {
class ClassFileMethodMetadata implements MethodMetadata {
private final String methodName;
@@ -148,7 +148,7 @@ final class ClassFileMethodMetadata implements MethodMetadata {
MergedAnnotations annotations = methodModel.elementStream()
.filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute)
.findFirst()
.map(element -> ClassFileAnnotationDelegate.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader))
.map(element -> ClassFileAnnotationMetadata.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader))
.orElse(MergedAnnotations.of(Collections.emptyList()));
return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations);
}
@@ -25,7 +25,5 @@ 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