mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77140da643 | |||
| d879112885 | |||
| 0a71df7aee | |||
| 82b7fcb2cc | |||
| 6717fca4ec | |||
| 86ea7003f7 | |||
| 0a157faec1 | |||
| 2548d44f3b | |||
| 6cc6ea1b2b | |||
| b73ca60811 | |||
| d689629036 | |||
| 077721bdb4 | |||
| b7654dd984 | |||
| 4645ce60c8 | |||
| 564d3f6f5e | |||
| 7a1dfe79a0 | |||
| 19257f4fdb | |||
| f06167ea01 | |||
| 618e003b91 | |||
| 8c494e5165 | |||
| 62d7396769 | |||
| 252058a65a | |||
| 9ea049ad6f | |||
| 1e31cd685e | |||
| 0d60f266ad | |||
| a018ae6e02 | |||
| ea419d2dcd | |||
| c02d07fdef | |||
| 7460be617b | |||
| ccf4b028d2 | |||
| b5c89c91a9 | |||
| 01ae0a7c26 | |||
| b48d72cbd0 | |||
| 48628194dc | |||
| 3cf1fbf222 | |||
| ecd943c7ec | |||
| 9d6d67188a | |||
| 24fd0940bd | |||
| 5b928f47a8 | |||
| 5e52baee86 | |||
| c10596a1fd | |||
| a68b7289f0 | |||
| a5b9e667e3 | |||
| 78fe55f4d1 | |||
| 65913c3655 | |||
| b7a996a64b | |||
| 5b1a7c7f21 | |||
| ebd80bdd6c | |||
| a4fc68b8e8 | |||
| c333946b0b | |||
| b0a8a3ec5f | |||
| bb5be2119c | |||
| cf662368a5 | |||
| e1b06ccfaa | |||
| 9eefdb8041 | |||
| c50cb10964 | |||
| 245341231f | |||
| 875cc828cf | |||
| 898d3ec86a | |||
| 8ccaabe778 | |||
| 9c934b5019 | |||
| 4b45338ae6 | |||
| 95003e3512 | |||
| 68368621f3 | |||
| 890a8f4311 | |||
| 0976d1a252 | |||
| 2c0ce790d8 | |||
| d512aafc36 | |||
| faa000f330 | |||
| daf1b3d7a7 | |||
| a48897a241 | |||
| 7ffa12f90f | |||
| 0e74ffa144 | |||
| 357195289d | |||
| 4fbca99f20 | |||
| c48ca5151f | |||
| aaf2e8fbe6 | |||
| 3635ff0c17 | |||
| f20e76e227 | |||
| dd32f95192 | |||
| 75a920bc9f | |||
| 92874adae9 | |||
| 55167b7f50 | |||
| 3129afba19 | |||
| 1366d2926b |
@@ -11,7 +11,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
|
||||
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
|
||||
with:
|
||||
config-file: .github/actions/create-github-release/changelog-generator.yml
|
||||
milestone: ${{ inputs.milestone }}
|
||||
|
||||
@@ -15,7 +15,7 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Dispatch docs deployment
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.12-librca
|
||||
java=17.0.13-librca
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -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("10.20.1");
|
||||
checkstyle.setToolVersion("10.22.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -36,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.17/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
|
||||
-7
@@ -120,8 +120,6 @@ dynamically generate a subclass that overrides the method.
|
||||
subclasses cannot be `final`, and the method to be overridden cannot be `final`, either.
|
||||
* Unit-testing a class that has an `abstract` method requires you to subclass the class
|
||||
yourself and to supply a stub implementation of the `abstract` method.
|
||||
* Concrete methods are also necessary for component scanning, which requires concrete
|
||||
classes to pick up.
|
||||
* A further key limitation is that lookup methods do not work with factory methods and
|
||||
in particular not with `@Bean` methods in configuration classes, since, in that case,
|
||||
the container is not in charge of creating the instance and therefore cannot create
|
||||
@@ -293,11 +291,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Note that you should typically declare such annotated lookup methods with a concrete
|
||||
stub implementation, in order for them to be compatible with Spring's component
|
||||
scanning rules where abstract classes get ignored by default. This limitation does not
|
||||
apply to explicitly registered or explicitly imported bean classes.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Another way of accessing differently scoped target beans is an `ObjectFactory`/
|
||||
|
||||
@@ -12,27 +12,27 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Inventor einstein = p.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
Inventor einstein = parser.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
.getValue(Inventor.class);
|
||||
|
||||
// create new Inventor instance within the add() method of List
|
||||
p.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor(
|
||||
'Albert Einstein', 'German'))").getValue(societyContext);
|
||||
parser.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
|
||||
.getValue(societyContext);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val einstein = p.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
val einstein = parser.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
.getValue(Inventor::class.java)
|
||||
|
||||
// create new Inventor instance within the add() method of List
|
||||
p.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
|
||||
parser.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
|
||||
.getValue(societyContext)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -110,8 +110,8 @@ potentially more efficient use cases if the `MethodHandle` target and parameters
|
||||
been fully bound prior to registration; however, partially bound handles are also
|
||||
supported.
|
||||
|
||||
Consider the `String#formatted(String, Object...)` instance method, which produces a
|
||||
message according to a template and a variable number of arguments.
|
||||
Consider the `String#formatted(Object...)` instance method, which produces a message
|
||||
according to a template and a variable number of arguments.
|
||||
|
||||
You can register and use the `formatted` method as a `MethodHandle`, as the following
|
||||
example shows:
|
||||
@@ -151,10 +151,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
As hinted above, binding a `MethodHandle` and registering the bound `MethodHandle` is also
|
||||
supported. This is likely to be more performant if both the target and all the arguments
|
||||
are bound. In that case no arguments are necessary in the SpEL expression, as the
|
||||
following example shows:
|
||||
As mentioned above, binding a `MethodHandle` and registering the bound `MethodHandle` is
|
||||
also supported. This is likely to be more performant if both the target and all the
|
||||
arguments are bound. In that case no arguments are necessary in the SpEL expression, as
|
||||
the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -168,9 +168,10 @@ Java::
|
||||
String template = "This is a %s message with %s words: <%s>";
|
||||
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
|
||||
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
|
||||
MethodType.methodType(String.class, Object[].class))
|
||||
MethodType.methodType(String.class, Object[].class))
|
||||
.bindTo(template)
|
||||
.bindTo(varargs); //here we have to provide arguments in a single array binding
|
||||
// Here we have to provide the arguments in a single array binding:
|
||||
.bindTo(varargs);
|
||||
context.setVariable("message", mh);
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
@@ -189,9 +190,10 @@ Kotlin::
|
||||
val varargs = arrayOf("prerecorded", 3, "Oh Hello World!", "ignored")
|
||||
|
||||
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
.bindTo(template)
|
||||
.bindTo(varargs) //here we have to provide arguments in a single array binding
|
||||
// Here we have to provide the arguments in a single array binding:
|
||||
.bindTo(varargs)
|
||||
context.setVariable("message", mh)
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
|
||||
@@ -193,7 +193,7 @@ Kotlin::
|
||||
[[webtestclient-fn-config]]
|
||||
=== Bind to Router Function
|
||||
|
||||
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
|
||||
This setup allows you to test xref:web/webflux-functional.adoc[functional endpoints] via
|
||||
mock request and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-cors]]
|
||||
= CORS
|
||||
|
||||
[.small]#xref:web/webmvc-cors.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
|
||||
@@ -364,7 +365,7 @@ Kotlin::
|
||||
|
||||
You can apply CORS support through the built-in
|
||||
{spring-framework-api}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
|
||||
good fit with <<webflux-fn, functional endpoints>>.
|
||||
good fit with xref:web/webflux-functional.adoc[functional endpoints].
|
||||
|
||||
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
|
||||
Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-fn]]
|
||||
= Functional Endpoints
|
||||
|
||||
[.small]#xref:web/webmvc-functional.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-websocket]]
|
||||
= WebSockets
|
||||
|
||||
[.small]#xref:web/websocket.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
This part of the reference documentation covers support for reactive-stack WebSocket
|
||||
|
||||
@@ -101,7 +101,7 @@ On that foundation, Spring WebFlux provides a choice of two programming models:
|
||||
from the `spring-web` module. Both Spring MVC and WebFlux controllers support reactive
|
||||
(Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable
|
||||
difference is that WebFlux also supports reactive `@RequestBody` arguments.
|
||||
* <<webflux-fn>>: Lambda-based, lightweight, and functional programming model. You can think of
|
||||
* xref:web/webflux-functional.adoc[Functional Endpoints]: Lambda-based, lightweight, and functional programming model. You can think of
|
||||
this as a small library or a set of utilities that an application can use to route and
|
||||
handle requests. The big difference with annotated controllers is that the application
|
||||
is in charge of request handling from start to finish versus declaring intent through
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[mvc-cors]]
|
||||
= CORS
|
||||
|
||||
[.small]#xref:web/webflux-cors.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[[webmvc-fn]]
|
||||
= Functional Endpoints
|
||||
[.small]#<<web-reactive.adoc#webflux-fn, See equivalent in the Reactive stack>>#
|
||||
|
||||
[.small]#xref:web/webflux-functional.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions
|
||||
are used to route and handle requests and contracts are designed for immutability.
|
||||
|
||||
@@ -26,7 +26,7 @@ available through the `ServletRequest.getParameter{asterisk}()` family of method
|
||||
|
||||
|
||||
|
||||
[[forwarded-headers]]
|
||||
[[filters-forwarded-headers]]
|
||||
== Forwarded Headers
|
||||
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-forwarded-headers[See equivalent in the Reactive stack]#
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[[websocket]]
|
||||
= WebSockets
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webflux-websocket.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
This part of the reference documentation covers support for Servlet stack, WebSocket
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"@antora/collector-extension": "1.0.0-alpha.3",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.11.1",
|
||||
"fast-xml-parser": "4.5.2",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ javaPlatform {
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.12"))
|
||||
api(platform("io.netty:netty-bom:4.1.115.Final"))
|
||||
api(platform("io.netty:netty-bom:4.1.119.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.12"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.4"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.16"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.26.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.15"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.15"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.18"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.18"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.10.5"))
|
||||
@@ -44,7 +44,7 @@ dependencies {
|
||||
api("com.sun.xml.bind:jaxb-impl:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.1.0")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.20")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.21")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.micrometer:context-propagation:1.1.1")
|
||||
@@ -54,11 +54,11 @@ dependencies {
|
||||
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.9")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.10")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.17.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.17.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.17.Final")
|
||||
api("io.undertow:undertow-core:2.3.18.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.18.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.18.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -101,8 +101,8 @@ dependencies {
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
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.4.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.3")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
|
||||
@@ -116,7 +116,7 @@ dependencies {
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.1.16-SNAPSHOT
|
||||
version=6.1.19
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+13
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -276,14 +276,18 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
|
||||
}
|
||||
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
|
||||
// May need to add implicit join point arg name...
|
||||
Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
|
||||
if (firstArgType == JoinPoint.class ||
|
||||
firstArgType == ProceedingJoinPoint.class ||
|
||||
firstArgType == JoinPoint.StaticPart.class) {
|
||||
String[] oldNames = this.argumentNames;
|
||||
this.argumentNames = new String[oldNames.length + 1];
|
||||
this.argumentNames[0] = "THIS_JOIN_POINT";
|
||||
System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
|
||||
for (int i = 0; i < this.aspectJAdviceMethod.getParameterCount(); i++) {
|
||||
Class<?> argType = this.aspectJAdviceMethod.getParameterTypes()[i];
|
||||
if (argType == JoinPoint.class ||
|
||||
argType == ProceedingJoinPoint.class ||
|
||||
argType == JoinPoint.StaticPart.class) {
|
||||
String[] oldNames = this.argumentNames;
|
||||
this.argumentNames = new String[oldNames.length + 1];
|
||||
System.arraycopy(oldNames, 0, this.argumentNames, 0, i);
|
||||
this.argumentNames[i] = "THIS_JOIN_POINT";
|
||||
System.arraycopy(oldNames, i, this.argumentNames, i + 1, oldNames.length - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -241,7 +241,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
try {
|
||||
int algorithmicStep = STEP_JOIN_POINT_BINDING;
|
||||
while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
|
||||
while (this.numberOfRemainingUnboundArguments > 0 && algorithmicStep < STEP_FINISHED) {
|
||||
switch (algorithmicStep++) {
|
||||
case STEP_JOIN_POINT_BINDING -> {
|
||||
if (!maybeBindThisJoinPoint()) {
|
||||
@@ -373,7 +373,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
if (this.returningName != null) {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates. " +
|
||||
"Consider compiling with -parameters in order to make declared parameter names available.");
|
||||
}
|
||||
|
||||
// We're all set... find the unbound parameter, and bind it.
|
||||
@@ -485,8 +486,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
*/
|
||||
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
|
||||
" unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -535,8 +536,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
private void maybeBindReferencePointcutParameter() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at reference pointcut binding stage, with no way to determine between them");
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
|
||||
" unbound args at reference pointcut binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -741,7 +742,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
* Simple record to hold the extracted text from a pointcut body, together
|
||||
* with the number of tokens consumed in extracting it.
|
||||
*/
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown in response to an ambiguous binding being detected when
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,6 +35,7 @@ import org.springframework.aop.AopInvocationException;
|
||||
import org.springframework.aop.RawTargetAccess;
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
|
||||
import org.springframework.cglib.core.CodeGenerationException;
|
||||
import org.springframework.cglib.core.SpringNamingPolicy;
|
||||
@@ -201,7 +202,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
enhancer.setSuperclass(proxySuperClass);
|
||||
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
|
||||
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
|
||||
enhancer.setAttemptLoad(true);
|
||||
enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
|
||||
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
|
||||
|
||||
Callback[] callbacks = getCallbacks(rootClass);
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.aop.aspectj;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractAspectJAdvice}.
|
||||
*
|
||||
* @author Joshua Chen
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class AbstractAspectJAdviceTests {
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withoutJoinPointParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithNoJoinPoint");
|
||||
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withJoinPointAsFirstParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsFirstParameter");
|
||||
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withJoinPointAsLastParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsLastParameter");
|
||||
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2", "THIS_JOIN_POINT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withJoinPointAsMiddleParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsMiddleParameter");
|
||||
assertThat(advice).satisfies(hasArgumentNames("arg1", "THIS_JOIN_POINT", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withProceedingJoinPoint() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithProceedingJoinPoint");
|
||||
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withStaticPart() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithStaticPart");
|
||||
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
|
||||
}
|
||||
|
||||
private Consumer<AbstractAspectJAdvice> hasArgumentNames(String... argumentNames) {
|
||||
return advice -> assertThat(advice).extracting("argumentNames")
|
||||
.asInstanceOf(InstanceOfAssertFactories.array(String[].class))
|
||||
.containsExactly(argumentNames);
|
||||
}
|
||||
|
||||
private AbstractAspectJAdvice getAspectJAdvice(final String methodName) {
|
||||
AbstractAspectJAdvice advice = new TestAspectJAdvice(getMethod(methodName),
|
||||
mock(AspectJExpressionPointcut.class), mock(AspectInstanceFactory.class));
|
||||
advice.setArgumentNamesFromStringArray("arg1", "arg2");
|
||||
return advice;
|
||||
}
|
||||
|
||||
private Method getMethod(final String methodName) {
|
||||
return Arrays.stream(Sample.class.getDeclaredMethods())
|
||||
.filter(method -> method.getName().equals(methodName)).findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class TestAspectJAdvice extends AbstractAspectJAdvice {
|
||||
|
||||
public TestAspectJAdvice(Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut,
|
||||
AspectInstanceFactory aspectInstanceFactory) {
|
||||
super(aspectJAdviceMethod, pointcut, aspectInstanceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeforeAdvice() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAfterAdvice() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Sample {
|
||||
|
||||
void methodWithNoJoinPoint(String arg1, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithJoinPointAsFirstParameter(JoinPoint joinPoint, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithJoinPointAsLastParameter(String arg1, String arg2, JoinPoint joinPoint) {
|
||||
}
|
||||
|
||||
void methodWithJoinPointAsMiddleParameter(String arg1, JoinPoint joinPoint, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithProceedingJoinPoint(ProceedingJoinPoint joinPoint, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithStaticPart(JoinPoint.StaticPart staticPart, String arg1, String arg2) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+31
-34
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -203,7 +203,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
itb.getSpouse();
|
||||
|
||||
assertThat(maaif.isMaterialized()).isTrue();
|
||||
|
||||
assertThat(imapa.getDeclaredPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getAge"), null)).isTrue();
|
||||
|
||||
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(0);
|
||||
@@ -301,7 +300,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void bindingWithMultipleArgsDifferentlyOrdered() {
|
||||
ManyValuedArgs target = new ManyValuedArgs();
|
||||
ManyValuedArgs mva = createProxy(target, ManyValuedArgs.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
|
||||
|
||||
String a = "a";
|
||||
int b = 12;
|
||||
@@ -320,7 +319,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
NotLockable notLockableTarget = new NotLockable();
|
||||
assertThat(notLockableTarget).isNotInstanceOf(Lockable.class);
|
||||
NotLockable notLockable1 = createProxy(notLockableTarget, NotLockable.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
assertThat(notLockable1).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = (Lockable) notLockable1;
|
||||
assertThat(lockable.locked()).isFalse();
|
||||
@@ -329,7 +328,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
NotLockable notLockable2Target = new NotLockable();
|
||||
NotLockable notLockable2 = createProxy(notLockable2Target, NotLockable.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
assertThat(notLockable2).isInstanceOf(Lockable.class);
|
||||
Lockable lockable2 = (Lockable) notLockable2;
|
||||
assertThat(lockable2.locked()).isFalse();
|
||||
@@ -343,20 +342,19 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void introductionAdvisorExcludedFromTargetImplementingInterface() {
|
||||
assertThat(AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class)).isEmpty();
|
||||
assertThat(AopUtils.findAdvisorsThatCanApply(getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
|
||||
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void introductionOnTargetImplementingInterface() {
|
||||
CannotBeUnlocked target = new CannotBeUnlocked();
|
||||
Lockable proxy = createProxy(target, CannotBeUnlocked.class,
|
||||
// Ensure that we exclude
|
||||
AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class));
|
||||
assertThat(proxy).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = proxy;
|
||||
assertThat(lockable.locked()).as("Already locked").isTrue();
|
||||
@@ -370,8 +368,8 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
ArrayList<Object> target = new ArrayList<>();
|
||||
List<?> proxy = createProxy(target, List.class,
|
||||
AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
List.class));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
List.class));
|
||||
assertThat(proxy).as("Type pattern must have excluded mixin").isNotInstanceOf(Lockable.class);
|
||||
}
|
||||
|
||||
@@ -379,7 +377,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void introductionBasedOnAnnotationMatch() { // gh-9980
|
||||
AnnotatedTarget target = new AnnotatedTargetImpl();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
|
||||
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
|
||||
Object proxy = createProxy(target, AnnotatedTarget.class, advisors);
|
||||
assertThat(proxy).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = (Lockable) proxy;
|
||||
@@ -393,9 +391,9 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
|
||||
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
|
||||
advisors.addAll(getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
|
||||
Modifiable modifiable = (Modifiable) createProxy(target, ITestBean.class, advisors);
|
||||
assertThat(modifiable).isInstanceOf(Modifiable.class);
|
||||
@@ -426,7 +424,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
UnsupportedOperationException expectedException = new UnsupportedOperationException();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
assertThat(advisors).as("One advice method was found").hasSize(1);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(itb::getAge);
|
||||
@@ -439,12 +437,12 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
RemoteException expectedException = new RemoteException();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
assertThat(advisors).as("One advice method was found").hasSize(1);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
assertThatExceptionOfType(UndeclaredThrowableException.class)
|
||||
.isThrownBy(itb::getAge)
|
||||
.withCause(expectedException);
|
||||
.isThrownBy(itb::getAge)
|
||||
.withCause(expectedException);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -452,7 +450,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
TwoAdviceAspect twoAdviceAspect = new TwoAdviceAspect();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(twoAdviceAspect, "someBean"));
|
||||
aspectInstanceFactory(twoAdviceAspect, "someBean"));
|
||||
assertThat(advisors).as("Two advice methods found").hasSize(2);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
itb.setName("");
|
||||
@@ -466,7 +464,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void afterAdviceTypes() throws Exception {
|
||||
InvocationTrackingAspect aspect = new InvocationTrackingAspect();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
|
||||
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
|
||||
Echo echo = createProxy(new Echo(), Echo.class, advisors);
|
||||
|
||||
assertThat(aspect.invocations).isEmpty();
|
||||
@@ -475,7 +473,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
aspect.invocations.clear();
|
||||
assertThatExceptionOfType(FileNotFoundException.class)
|
||||
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
|
||||
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
|
||||
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
|
||||
}
|
||||
|
||||
@@ -487,7 +485,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
assertThat(Modifier.isAbstract(aspect.getClass().getSuperclass().getModifiers())).isFalse();
|
||||
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspect, "incrementingAspect"));
|
||||
|
||||
ITestBean proxy = createProxy(new TestBean("Jane", 42), ITestBean.class, advisors);
|
||||
assertThat(proxy.getAge()).isEqualTo(86); // (42 + 1) * 2
|
||||
}
|
||||
@@ -812,20 +809,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
invocations.add("before");
|
||||
}
|
||||
|
||||
@AfterReturning("echo()")
|
||||
void afterReturning() {
|
||||
invocations.add("after returning");
|
||||
}
|
||||
|
||||
@AfterThrowing("echo()")
|
||||
void afterThrowing() {
|
||||
invocations.add("after throwing");
|
||||
}
|
||||
|
||||
@After("echo()")
|
||||
void after() {
|
||||
invocations.add("after");
|
||||
}
|
||||
|
||||
@AfterReturning(pointcut = "this(target) && execution(* echo(*))", returning = "returnValue")
|
||||
void afterReturning(JoinPoint joinPoint, Echo target, Object returnValue) {
|
||||
invocations.add("after returning");
|
||||
}
|
||||
|
||||
@AfterThrowing(pointcut = "this(target) && execution(* echo(*))", throwing = "exception")
|
||||
void afterThrowing(JoinPoint joinPoint, Echo target, Throwable exception) {
|
||||
invocations.add("after throwing");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -967,7 +964,7 @@ abstract class AbstractMakeModifiable {
|
||||
class MakeITestBeanModifiable extends AbstractMakeModifiable {
|
||||
|
||||
@DeclareParents(value = "org.springframework.beans.testfixture.beans.ITestBean+",
|
||||
defaultImpl=ModifiableImpl.class)
|
||||
defaultImpl = ModifiableImpl.class)
|
||||
static MutableModifiable mixin;
|
||||
|
||||
}
|
||||
|
||||
+11
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -291,7 +291,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
String lastKey = tokens.keys[tokens.keys.length - 1];
|
||||
|
||||
if (propValue.getClass().isArray()) {
|
||||
Class<?> requiredType = propValue.getClass().componentType();
|
||||
Class<?> componentType = propValue.getClass().componentType();
|
||||
int arrayIndex = Integer.parseInt(lastKey);
|
||||
Object oldValue = null;
|
||||
try {
|
||||
@@ -299,10 +299,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
oldValue = Array.get(propValue, arrayIndex);
|
||||
}
|
||||
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
|
||||
requiredType, ph.nested(tokens.keys.length));
|
||||
componentType, ph.nested(tokens.keys.length));
|
||||
int length = Array.getLength(propValue);
|
||||
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
|
||||
Class<?> componentType = propValue.getClass().componentType();
|
||||
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
|
||||
System.arraycopy(propValue, 0, newArray, 0, length);
|
||||
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
|
||||
@@ -658,6 +657,14 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
|
||||
value = list.get(index);
|
||||
}
|
||||
else if (value instanceof Map map) {
|
||||
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
|
||||
// IMPORTANT: Do not pass full property name in here - property editors
|
||||
// must not kick in for map keys but rather only for map values.
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
|
||||
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
|
||||
value = map.get(convertedMapKey);
|
||||
}
|
||||
else if (value instanceof Iterable iterable) {
|
||||
// Apply index to Iterator in case of a Set/Collection/Iterable.
|
||||
int index = Integer.parseInt(key);
|
||||
@@ -685,14 +692,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
currIndex + ", accessed using property path '" + propertyName + "'");
|
||||
}
|
||||
}
|
||||
else if (value instanceof Map map) {
|
||||
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
|
||||
// IMPORTANT: Do not pass full property name in here - property editors
|
||||
// must not kick in for map keys but rather only for map values.
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
|
||||
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
|
||||
value = map.get(convertedMapKey);
|
||||
}
|
||||
else {
|
||||
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||
"Property referenced in indexed property path '" + propertyName +
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -25,6 +25,7 @@ import org.springframework.lang.Nullable;
|
||||
* analogous to an InvocationTargetException.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MethodInvocationException extends PropertyAccessException {
|
||||
@@ -41,7 +42,9 @@ public class MethodInvocationException extends PropertyAccessException {
|
||||
* @param cause the Throwable raised by the invoked method
|
||||
*/
|
||||
public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, @Nullable Throwable cause) {
|
||||
super(propertyChangeEvent, "Property '" + propertyChangeEvent.getPropertyName() + "' threw exception", cause);
|
||||
super(propertyChangeEvent,
|
||||
"Property '" + propertyChangeEvent.getPropertyName() + "' threw exception: " + cause,
|
||||
cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -145,8 +145,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
|
||||
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
|
||||
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
|
||||
@@ -176,8 +174,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>Bean names returned by this method should always return bean names <i>in the
|
||||
* order of definition</i> in the backend configuration, as far as possible.
|
||||
* @param type the generically typed class or interface to match
|
||||
@@ -210,8 +206,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
|
||||
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
|
||||
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
|
||||
@@ -239,8 +233,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>Bean names returned by this method should always return bean names <i>in the
|
||||
* order of definition</i> in the backend configuration, as far as possible.
|
||||
* @param type the class or interface to match, or {@code null} for all bean names
|
||||
@@ -265,21 +257,24 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* subclasses), judging from either bean definitions or the value of
|
||||
* {@code getObjectType} in the case of FactoryBeans.
|
||||
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
|
||||
* check nested beans which might match the specified type as well.
|
||||
* check nested beans which might match the specified type as well. Also, it
|
||||
* <b>suppresses exceptions for beans that are currently in creation in a circular
|
||||
* reference scenario:</b> typically, references back to the caller of this method.
|
||||
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
|
||||
* will get initialized. If the object created by the FactoryBean doesn't match,
|
||||
* the raw FactoryBean itself will be matched against the type.
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>This version of getBeansOfType matches all kinds of beans, be it
|
||||
* singletons, prototypes, or FactoryBeans. In most implementations, the
|
||||
* result will be the same as for {@code getBeansOfType(type, true, true)}.
|
||||
* <p>The Map returned by this method should always return bean names and
|
||||
* corresponding bean instances <i>in the order of definition</i> in the
|
||||
* backend configuration, as far as possible.
|
||||
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
|
||||
* calls for specific bean names in preference to this Map-based retrieval method.</b>
|
||||
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
|
||||
* @param type the class or interface to match, or {@code null} for all concrete beans
|
||||
* @return a Map with the matching beans, containing the bean names as
|
||||
* keys and the corresponding bean instances as values
|
||||
@@ -295,7 +290,9 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* subclasses), judging from either bean definitions or the value of
|
||||
* {@code getObjectType} in the case of FactoryBeans.
|
||||
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
|
||||
* check nested beans which might match the specified type as well.
|
||||
* check nested beans which might match the specified type as well. Also, it
|
||||
* <b>suppresses exceptions for beans that are currently in creation in a circular
|
||||
* reference scenario:</b> typically, references back to the caller of this method.
|
||||
* <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
|
||||
* which means that FactoryBeans will get initialized. If the object created by the
|
||||
* FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
|
||||
@@ -304,11 +301,12 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>The Map returned by this method should always return bean names and
|
||||
* corresponding bean instances <i>in the order of definition</i> in the
|
||||
* backend configuration, as far as possible.
|
||||
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
|
||||
* calls for specific bean names in preference to this Map-based retrieval method.</b>
|
||||
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
|
||||
* @param type the class or interface to match, or {@code null} for all concrete beans
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -156,6 +156,8 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
|
||||
.builder(SuppressWarnings.class)
|
||||
.addMember("value", "{\"rawtypes\", \"unchecked\"}")
|
||||
.build());
|
||||
method.addModifiers(javax.lang.model.element.Modifier.PRIVATE,
|
||||
javax.lang.model.element.Modifier.STATIC);
|
||||
method.returns(Map.class);
|
||||
method.addStatement("$T map = new $T($L)", Map.class,
|
||||
LinkedHashMap.class, map.size());
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -153,7 +154,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(beanDefinition.getBeanClass());
|
||||
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
|
||||
enhancer.setAttemptLoad(true);
|
||||
enhancer.setAttemptLoad(AotDetector.useGeneratedArtifacts());
|
||||
if (this.owner instanceof ConfigurableBeanFactory cbf) {
|
||||
ClassLoader cl = cbf.getBeanClassLoader();
|
||||
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
|
||||
@@ -265,7 +266,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
|
||||
|
||||
/**
|
||||
* CGLIB MethodInterceptor to override methods, replacing them with a call
|
||||
* to a generic MethodReplacer.
|
||||
* to a generic {@link MethodReplacer}.
|
||||
*/
|
||||
private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
|
||||
|
||||
@@ -277,10 +278,10 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
|
||||
ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
|
||||
Assert.state(ro != null, "ReplaceOverride not found");
|
||||
// TODO could cache if a singleton for minor performance optimization
|
||||
MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
|
||||
return mr.reimplement(obj, method, args);
|
||||
}
|
||||
|
||||
+5
@@ -1055,6 +1055,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Removing alias '" + beanName + "' for bean '" + aliasedName +
|
||||
"' due to registration of bean definition for bean '" + beanName + "': [" +
|
||||
beanDefinition + "]");
|
||||
}
|
||||
removeAlias(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -178,13 +178,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
*/
|
||||
@Nullable
|
||||
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
|
||||
// Quick check for existing instance without full singleton lock
|
||||
// Quick check for existing instance without full singleton lock.
|
||||
Object singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
|
||||
singletonObject = this.earlySingletonObjects.get(beanName);
|
||||
if (singletonObject == null && allowEarlyReference) {
|
||||
synchronized (this.singletonObjects) {
|
||||
// Consistent creation of early reference within full singleton lock
|
||||
// Consistent creation of early reference within full singleton lock.
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null) {
|
||||
singletonObject = this.earlySingletonObjects.get(beanName);
|
||||
|
||||
+28
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -139,6 +139,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isReadableProperty("list")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("set")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("xxx")).isFalse();
|
||||
|
||||
@@ -146,6 +147,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isWritableProperty("list")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("set")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("myTestBeans")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("xxx")).isFalse();
|
||||
|
||||
@@ -161,6 +163,14 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isReadableProperty("map[key4][0].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map[key4][1]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map[key4][1].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map[key999]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key1]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key1].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][0]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][0].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][1]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][1].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key999]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("array[key1]")).isFalse();
|
||||
@@ -177,6 +187,14 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isWritableProperty("map[key4][0].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map[key4][1]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map[key4][1].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map[key999]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key1]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key1].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][0]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][0].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][1]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][1].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key999]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
|
||||
assertThat(accessor.isWritableProperty("array[key1]")).isFalse();
|
||||
@@ -1394,6 +1412,9 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
|
||||
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
|
||||
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("nameC");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("nameA");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("nameB");
|
||||
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZ");
|
||||
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -1408,6 +1429,9 @@ abstract class AbstractPropertyAccessorTests {
|
||||
pvs.add("map[key4][0].name", "nameA");
|
||||
pvs.add("map[key4][1].name", "nameB");
|
||||
pvs.add("map[key5[foo]].name", "name10");
|
||||
pvs.add("iterableMap[key1].name", "newName1");
|
||||
pvs.add("iterableMap[key2][0].name", "newName2A");
|
||||
pvs.add("iterableMap[key2][1].name", "newName2B");
|
||||
pvs.add("myTestBeans[0].name", "nameZZ");
|
||||
accessor.setPropertyValues(pvs);
|
||||
assertThat(tb0.getName()).isEqualTo("name5");
|
||||
@@ -1427,6 +1451,9 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.getPropertyValue("map[key4][0].name")).isEqualTo("nameA");
|
||||
assertThat(accessor.getPropertyValue("map[key4][1].name")).isEqualTo("nameB");
|
||||
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name10");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("newName1");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("newName2A");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("newName2B");
|
||||
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZZ");
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -885,10 +885,15 @@ class DefaultListableBeanFactoryTests {
|
||||
@Test
|
||||
void beanDefinitionOverriding() {
|
||||
lbf.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class));
|
||||
// Override "test" bean definition.
|
||||
lbf.registerBeanDefinition("test", new RootBeanDefinition(NestedTestBean.class));
|
||||
// Temporary "test2" alias for nonexistent bean.
|
||||
lbf.registerAlias("otherTest", "test2");
|
||||
// Reassign "test2" alias to "test".
|
||||
lbf.registerAlias("test", "test2");
|
||||
// Assign "testX" alias to "test" as well.
|
||||
lbf.registerAlias("test", "testX");
|
||||
// Register new "testX" bean definition which also removes the "testX" alias for "test".
|
||||
lbf.registerBeanDefinition("testX", new RootBeanDefinition(TestBean.class));
|
||||
|
||||
assertThat(lbf.getBean("test")).isInstanceOf(NestedTestBean.class);
|
||||
|
||||
+16
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.beans.factory.aot;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
@@ -28,7 +29,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
@@ -54,7 +54,7 @@ import org.springframework.core.testfixture.aot.generate.value.ExampleClass;
|
||||
import org.springframework.core.testfixture.aot.generate.value.ExampleClass$$GeneratedBy;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.ParameterizedTypeName;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -83,14 +83,23 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
|
||||
CodeBlock generatedCode = createValueCodeGenerator(generatedClass).generateCode(value);
|
||||
typeBuilder.set(type -> {
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addSuperinterface(
|
||||
ParameterizedTypeName.get(Supplier.class, Object.class));
|
||||
type.addMethod(MethodSpec.methodBuilder("get").addModifiers(Modifier.PUBLIC)
|
||||
type.addMethod(MethodSpec.methodBuilder("get").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(Object.class).addStatement("return $L", generatedCode).build());
|
||||
});
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(generationContext).compile(compiled ->
|
||||
result.accept(compiled.getInstance(Supplier.class).get(), compiled));
|
||||
result.accept(getGeneratedCodeReturnValue(compiled, generatedClass), compiled));
|
||||
}
|
||||
|
||||
private static Object getGeneratedCodeReturnValue(Compiled compiled, GeneratedClass generatedClass) {
|
||||
try {
|
||||
Object instance = compiled.getInstance(Object.class, generatedClass.getName().reflectionName());
|
||||
Method get = ReflectionUtils.findMethod(instance.getClass(), "get");
|
||||
return get.invoke(null);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException("Failed to invoke generated code '%s':".formatted(generatedClass.getName()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
+67
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -21,12 +21,13 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -43,14 +44,17 @@ class QualifierAnnotationAutowireBeanFactoryTests {
|
||||
|
||||
private static final String MARK = "mark";
|
||||
|
||||
private final DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
void testAutowireCandidateDefaultWithIrrelevantDescriptor() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(Person.class, cavs, null);
|
||||
lbf.registerBeanDefinition(JUERGEN, rbd);
|
||||
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN,
|
||||
new DependencyDescriptor(Person.class.getDeclaredField("name"), false))).isTrue();
|
||||
@@ -60,12 +64,12 @@ class QualifierAnnotationAutowireBeanFactoryTests {
|
||||
|
||||
@Test
|
||||
void testAutowireCandidateExplicitlyFalseWithIrrelevantDescriptor() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(Person.class, cavs, null);
|
||||
rbd.setAutowireCandidate(false);
|
||||
lbf.registerBeanDefinition(JUERGEN, rbd);
|
||||
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isFalse();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN,
|
||||
new DependencyDescriptor(Person.class.getDeclaredField("name"), false))).isFalse();
|
||||
@@ -73,44 +77,46 @@ class QualifierAnnotationAutowireBeanFactoryTests {
|
||||
new DependencyDescriptor(Person.class.getDeclaredField("name"), true))).isFalse();
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@Test
|
||||
void testAutowireCandidateWithFieldDescriptor() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
|
||||
|
||||
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
|
||||
cavs1.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
|
||||
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
|
||||
lbf.registerBeanDefinition(JUERGEN, person1);
|
||||
|
||||
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
|
||||
cavs2.addGenericArgumentValue(MARK);
|
||||
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
|
||||
lbf.registerBeanDefinition(MARK, person2);
|
||||
|
||||
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
|
||||
QualifiedTestBean.class.getDeclaredField("qualified"), false);
|
||||
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(
|
||||
QualifiedTestBean.class.getDeclaredField("nonqualified"), false);
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
|
||||
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, null)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, nonqualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutowireCandidateExplicitlyFalseWithFieldDescriptor() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null);
|
||||
person.setAutowireCandidate(false);
|
||||
person.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
|
||||
lbf.registerBeanDefinition(JUERGEN, person);
|
||||
|
||||
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
|
||||
QualifiedTestBean.class.getDeclaredField("qualified"), false);
|
||||
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(
|
||||
QualifiedTestBean.class.getDeclaredField("nonqualified"), false);
|
||||
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isFalse();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isFalse();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isFalse();
|
||||
@@ -118,56 +124,61 @@ class QualifierAnnotationAutowireBeanFactoryTests {
|
||||
|
||||
@Test
|
||||
void testAutowireCandidateWithShortClassName() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null);
|
||||
person.addQualifier(new AutowireCandidateQualifier(ClassUtils.getShortName(TestQualifier.class)));
|
||||
lbf.registerBeanDefinition(JUERGEN, person);
|
||||
|
||||
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
|
||||
QualifiedTestBean.class.getDeclaredField("qualified"), false);
|
||||
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(
|
||||
QualifiedTestBean.class.getDeclaredField("nonqualified"), false);
|
||||
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@Test
|
||||
void testAutowireCandidateWithConstructorDescriptor() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
|
||||
|
||||
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
|
||||
cavs1.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
|
||||
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
|
||||
lbf.registerBeanDefinition(JUERGEN, person1);
|
||||
|
||||
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
|
||||
cavs2.addGenericArgumentValue(MARK);
|
||||
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
|
||||
lbf.registerBeanDefinition(MARK, person2);
|
||||
|
||||
MethodParameter param = new MethodParameter(QualifiedTestBean.class.getDeclaredConstructor(Person.class), 0);
|
||||
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(param, false);
|
||||
param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
|
||||
assertThat(param.getParameterName()).isEqualTo("tpb");
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isFalse();
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@Test
|
||||
void testAutowireCandidateWithMethodDescriptor() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
|
||||
|
||||
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
|
||||
cavs1.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
|
||||
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
|
||||
lbf.registerBeanDefinition(JUERGEN, person1);
|
||||
|
||||
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
|
||||
cavs2.addGenericArgumentValue(MARK);
|
||||
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
|
||||
lbf.registerBeanDefinition(MARK, person2);
|
||||
|
||||
MethodParameter qualifiedParam =
|
||||
new MethodParameter(QualifiedTestBean.class.getDeclaredMethod("autowireQualified", Person.class), 0);
|
||||
MethodParameter nonqualifiedParam =
|
||||
@@ -175,37 +186,70 @@ class QualifierAnnotationAutowireBeanFactoryTests {
|
||||
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(qualifiedParam, false);
|
||||
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(nonqualifiedParam, false);
|
||||
qualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
assertThat(qualifiedParam.getParameterName()).isEqualTo("tpb");
|
||||
nonqualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
|
||||
assertThat(qualifiedParam.getParameterName()).isEqualTo("tpb");
|
||||
assertThat(nonqualifiedParam.getParameterName()).isEqualTo("tpb");
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, null)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, nonqualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutowireCandidateWithMultipleCandidatesDescriptor() throws Exception {
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
|
||||
cavs1.addGenericArgumentValue(JUERGEN);
|
||||
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
|
||||
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
|
||||
lbf.registerBeanDefinition(JUERGEN, person1);
|
||||
|
||||
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
|
||||
cavs2.addGenericArgumentValue(MARK);
|
||||
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
|
||||
person2.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
|
||||
lbf.registerBeanDefinition(MARK, person2);
|
||||
|
||||
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
|
||||
new MethodParameter(QualifiedTestBean.class.getDeclaredConstructor(Person.class), 0),
|
||||
false);
|
||||
|
||||
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
|
||||
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void autowireBeanByTypeWithQualifierPrecedence() throws Exception {
|
||||
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
|
||||
|
||||
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
|
||||
lbf.registerBeanDefinition("testBean", bd);
|
||||
lbf.registerBeanDefinition("spouse", bd2);
|
||||
lbf.registerAlias("test", "testBean");
|
||||
|
||||
assertThat(lbf.resolveDependency(new DependencyDescriptor(getClass().getDeclaredField("testBean"), true), null))
|
||||
.isSameAs(lbf.getBean("spouse"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autowireBeanByTypeWithQualifierPrecedenceInAncestor() throws Exception {
|
||||
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
|
||||
parent.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
|
||||
|
||||
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
|
||||
parent.registerBeanDefinition("test", bd);
|
||||
parent.registerBeanDefinition("spouse", bd2);
|
||||
parent.registerAlias("test", "testBean");
|
||||
|
||||
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(parent);
|
||||
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
|
||||
|
||||
assertThat(lbf.resolveDependency(new DependencyDescriptor(getClass().getDeclaredField("testBean"), true), null))
|
||||
.isSameAs(lbf.getBean("spouse"));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class QualifiedTestBean {
|
||||
@@ -247,4 +291,8 @@ class QualifierAnnotationAutowireBeanFactoryTests {
|
||||
private @interface TestQualifier {
|
||||
}
|
||||
|
||||
|
||||
@Qualifier("spouse")
|
||||
private TestBean testBean;
|
||||
|
||||
}
|
||||
|
||||
+30
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -49,6 +50,8 @@ public class IndexedTestBean {
|
||||
|
||||
private SortedMap sortedMap;
|
||||
|
||||
private IterableMap iterableMap;
|
||||
|
||||
private MyTestBeans myTestBeans;
|
||||
|
||||
|
||||
@@ -73,6 +76,9 @@ public class IndexedTestBean {
|
||||
TestBean tb6 = new TestBean("name6", 0);
|
||||
TestBean tb7 = new TestBean("name7", 0);
|
||||
TestBean tb8 = new TestBean("name8", 0);
|
||||
TestBean tbA = new TestBean("nameA", 0);
|
||||
TestBean tbB = new TestBean("nameB", 0);
|
||||
TestBean tbC = new TestBean("nameC", 0);
|
||||
TestBean tbX = new TestBean("nameX", 0);
|
||||
TestBean tbY = new TestBean("nameY", 0);
|
||||
TestBean tbZ = new TestBean("nameZ", 0);
|
||||
@@ -88,6 +94,12 @@ public class IndexedTestBean {
|
||||
this.map.put("key2", tb5);
|
||||
this.map.put("key.3", tb5);
|
||||
List list = new ArrayList();
|
||||
list.add(tbA);
|
||||
list.add(tbB);
|
||||
this.iterableMap = new IterableMap<>();
|
||||
this.iterableMap.put("key1", tbC);
|
||||
this.iterableMap.put("key2", list);
|
||||
list = new ArrayList();
|
||||
list.add(tbX);
|
||||
list.add(tbY);
|
||||
this.map.put("key4", list);
|
||||
@@ -152,6 +164,14 @@ public class IndexedTestBean {
|
||||
this.sortedMap = sortedMap;
|
||||
}
|
||||
|
||||
public IterableMap getIterableMap() {
|
||||
return this.iterableMap;
|
||||
}
|
||||
|
||||
public void setIterableMap(IterableMap iterableMap) {
|
||||
this.iterableMap = iterableMap;
|
||||
}
|
||||
|
||||
public MyTestBeans getMyTestBeans() {
|
||||
return myTestBeans;
|
||||
}
|
||||
@@ -161,6 +181,15 @@ public class IndexedTestBean {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class IterableMap<K,V> extends LinkedHashMap<K,V> implements Iterable<V> {
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return values().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyTestBeans implements Iterable<TestBean> {
|
||||
|
||||
private final Collection<TestBean> testBeans;
|
||||
|
||||
+32
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
@@ -41,6 +43,7 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -147,16 +150,26 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
|
||||
Set<String> metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType,
|
||||
key -> getMetaAnnotationTypes(mergedAnnotation));
|
||||
if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) {
|
||||
Object value = attributes.get("value");
|
||||
Object value = attributes.get(MergedAnnotation.VALUE);
|
||||
if (value instanceof String currentName && !currentName.isBlank()) {
|
||||
if (conventionBasedStereotypeCheckCache.add(annotationType) &&
|
||||
metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) && logger.isWarnEnabled()) {
|
||||
logger.warn("""
|
||||
Support for convention-based stereotype names is deprecated and will \
|
||||
be removed in a future version of the framework. Please annotate the \
|
||||
'value' attribute in @%s with @AliasFor(annotation=Component.class) \
|
||||
to declare an explicit alias for @Component's 'value' attribute."""
|
||||
.formatted(annotationType));
|
||||
if (hasExplicitlyAliasedValueAttribute(mergedAnnotation.getType())) {
|
||||
logger.warn("""
|
||||
Although the 'value' attribute in @%s declares @AliasFor for an attribute \
|
||||
other than @Component's 'value' attribute, the value is still used as the \
|
||||
@Component name based on convention. As of Spring Framework 7.0, such a \
|
||||
'value' attribute will no longer be used as the @Component name."""
|
||||
.formatted(annotationType));
|
||||
}
|
||||
else {
|
||||
logger.warn("""
|
||||
Support for convention-based @Component names is deprecated and will \
|
||||
be removed in a future version of the framework. Please annotate the \
|
||||
'value' attribute in @%s with @AliasFor(annotation=Component.class) \
|
||||
to declare an explicit alias for @Component's 'value' attribute."""
|
||||
.formatted(annotationType));
|
||||
}
|
||||
}
|
||||
if (beanName != null && !currentName.equals(beanName)) {
|
||||
throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
|
||||
@@ -224,7 +237,7 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
|
||||
annotationType.equals("jakarta.inject.Named") ||
|
||||
annotationType.equals("javax.inject.Named");
|
||||
|
||||
return (isStereotype && attributes.containsKey("value"));
|
||||
return (isStereotype && attributes.containsKey(MergedAnnotation.VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,4 +268,14 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
|
||||
return StringUtils.uncapitalizeAsProperty(shortClassName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied annotation type declares a {@code value()} attribute
|
||||
* with an explicit alias configured via {@link AliasFor @AliasFor}.
|
||||
* @since 6.2.3
|
||||
*/
|
||||
private static boolean hasExplicitlyAliasedValueAttribute(Class<? extends Annotation> annotationType) {
|
||||
Method valueAttribute = ReflectionUtils.findMethod(annotationType, MergedAnnotation.VALUE);
|
||||
return (valueAttribute != null && valueAttribute.isAnnotationPresent(AliasFor.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -565,9 +565,10 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given bean definition qualifies as candidate.
|
||||
* <p>The default implementation checks whether the class is not an interface
|
||||
* and not dependent on an enclosing class.
|
||||
* Determine whether the given bean definition qualifies as a candidate component.
|
||||
* <p>The default implementation checks whether the class is not dependent on an
|
||||
* enclosing class as well as whether the class is either concrete (and therefore
|
||||
* not an interface) or has {@link Lookup @Lookup} methods.
|
||||
* <p>Can be overridden in subclasses.
|
||||
* @param beanDefinition the bean definition to check
|
||||
* @return whether the bean definition qualifies as a candidate component
|
||||
|
||||
+11
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -191,6 +191,15 @@ final class ConfigurationClass {
|
||||
return this.beanMethods;
|
||||
}
|
||||
|
||||
boolean hasNonStaticBeanMethods() {
|
||||
for (BeanMethod beanMethod : this.beanMethods) {
|
||||
if (!beanMethod.getMetadata().isStatic()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
|
||||
this.importedResources.put(importedResource, readerClass);
|
||||
}
|
||||
@@ -212,7 +221,7 @@ final class ConfigurationClass {
|
||||
|
||||
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
|
||||
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods")) {
|
||||
if (this.metadata.isFinal()) {
|
||||
if (hasNonStaticBeanMethods() && this.metadata.isFinal()) {
|
||||
problemReporter.error(new FinalConfigurationProblem());
|
||||
}
|
||||
for (BeanMethod beanMethod : this.beanMethods) {
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -39,7 +39,6 @@ import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReader;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
|
||||
@@ -318,7 +317,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
|
||||
// At this point, it's a top-level override (probably XML), just having been parsed
|
||||
// before configuration class processing kicks in...
|
||||
if (this.registry instanceof DefaultListableBeanFactory dlbf && !dlbf.isBeanDefinitionOverridable(beanName)) {
|
||||
if (!this.registry.isBeanDefinitionOverridable(beanName)) {
|
||||
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
|
||||
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
|
||||
}
|
||||
|
||||
+61
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.scope.ScopedProxyFactoryBean;
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
@@ -109,8 +110,21 @@ class ConfigurationClassEnhancer {
|
||||
}
|
||||
return configClass;
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
|
||||
// Use original ClassLoader if config class not locally loaded in overriding class loader
|
||||
boolean classLoaderMismatch = (classLoader != null && classLoader != configClass.getClassLoader());
|
||||
if (classLoaderMismatch && classLoader instanceof SmartClassLoader smartClassLoader) {
|
||||
classLoader = smartClassLoader.getOriginalClassLoader();
|
||||
classLoaderMismatch = (classLoader != configClass.getClassLoader());
|
||||
}
|
||||
// Use original ClassLoader if config class relies on package visibility
|
||||
if (classLoaderMismatch && reliesOnPackageVisibility(configClass)) {
|
||||
classLoader = configClass.getClassLoader();
|
||||
classLoaderMismatch = false;
|
||||
}
|
||||
Enhancer enhancer = newEnhancer(configClass, classLoader);
|
||||
Class<?> enhancedClass = createClass(enhancer, classLoaderMismatch);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
|
||||
configClass.getName(), enhancedClass.getName()));
|
||||
@@ -124,36 +138,68 @@ class ConfigurationClassEnhancer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given config class relies on package visibility,
|
||||
* either for the class itself or for any of its {@code @Bean} methods.
|
||||
*/
|
||||
private boolean reliesOnPackageVisibility(Class<?> configSuperClass) {
|
||||
int mod = configSuperClass.getModifiers();
|
||||
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
|
||||
return true;
|
||||
}
|
||||
for (Method method : ReflectionUtils.getDeclaredMethods(configSuperClass)) {
|
||||
if (BeanAnnotationHelper.isBeanAnnotated(method)) {
|
||||
mod = method.getModifiers();
|
||||
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CGLIB {@link Enhancer} instance.
|
||||
*/
|
||||
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
if (classLoader != null) {
|
||||
enhancer.setClassLoader(classLoader);
|
||||
if (classLoader instanceof SmartClassLoader smartClassLoader &&
|
||||
smartClassLoader.isClassReloadable(configSuperClass)) {
|
||||
enhancer.setUseCache(false);
|
||||
}
|
||||
}
|
||||
enhancer.setSuperclass(configSuperClass);
|
||||
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
|
||||
enhancer.setUseFactory(false);
|
||||
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
|
||||
enhancer.setAttemptLoad(!isClassReloadable(configSuperClass, classLoader));
|
||||
enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
|
||||
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
|
||||
enhancer.setCallbackFilter(CALLBACK_FILTER);
|
||||
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
|
||||
return enhancer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given configuration class is reloadable.
|
||||
*/
|
||||
private boolean isClassReloadable(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
|
||||
return (classLoader instanceof SmartClassLoader smartClassLoader &&
|
||||
smartClassLoader.isClassReloadable(configSuperClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses enhancer to generate a subclass of superclass,
|
||||
* ensuring that callbacks are registered for the new subclass.
|
||||
*/
|
||||
private Class<?> createClass(Enhancer enhancer) {
|
||||
Class<?> subclass = enhancer.createClass();
|
||||
private Class<?> createClass(Enhancer enhancer, boolean fallback) {
|
||||
Class<?> subclass;
|
||||
try {
|
||||
subclass = enhancer.createClass();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (!fallback) {
|
||||
throw (ex instanceof CodeGenerationException cgex ? cgex : new CodeGenerationException(ex));
|
||||
}
|
||||
// Possibly a package-visible @Bean method declaration not accessible
|
||||
// in the given ClassLoader -> retry with original ClassLoader
|
||||
enhancer.setClassLoader(null);
|
||||
subclass = enhancer.createClass();
|
||||
}
|
||||
|
||||
// Registering callbacks statically (as opposed to thread-local)
|
||||
// is critical for usage in an OSGi environment (SPR-5932)...
|
||||
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
|
||||
@@ -164,8 +210,7 @@ class ConfigurationClassEnhancer {
|
||||
/**
|
||||
* Marker interface to be implemented by all @Configuration CGLIB subclasses.
|
||||
* Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance}
|
||||
* through checking to see if candidate classes are already assignable to it, e.g.
|
||||
* have already been enhanced.
|
||||
* through checking to see if candidate classes are already assignable to it.
|
||||
* <p>Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration}
|
||||
* classes require access to the {@link BeanFactory} that created them.
|
||||
* <p>Note that this interface is intended for framework-internal use only, however
|
||||
@@ -526,7 +571,7 @@ class ConfigurationClassEnhancer {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(factoryBean.getClass());
|
||||
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
|
||||
enhancer.setAttemptLoad(true);
|
||||
enhancer.setAttemptLoad(AotDetector.useGeneratedArtifacts());
|
||||
enhancer.setCallbackType(MethodInterceptor.class);
|
||||
|
||||
// Ideally create enhanced FactoryBean proxy without constructor side effects,
|
||||
|
||||
+47
-33
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -81,10 +81,9 @@ import org.springframework.util.MultiValueMap;
|
||||
* any number of ConfigurationClass objects because one Configuration class may import
|
||||
* another using the {@link Import} annotation).
|
||||
*
|
||||
* <p>This class helps separate the concern of parsing the structure of a Configuration
|
||||
* class from the concern of registering BeanDefinition objects based on the content of
|
||||
* that model (with the exception of {@code @ComponentScan} annotations which need to be
|
||||
* registered immediately).
|
||||
* <p>This class helps separate the concern of parsing the structure of a Configuration class
|
||||
* from the concern of registering BeanDefinition objects based on the content of that model
|
||||
* (except {@code @ComponentScan} annotations which need to be registered immediately).
|
||||
*
|
||||
* <p>This ASM-based implementation avoids reflection and eager class loading in order to
|
||||
* interoperate effectively with lazy class loading in a Spring ApplicationContext.
|
||||
@@ -161,14 +160,23 @@ class ConfigurationClassParser {
|
||||
for (BeanDefinitionHolder holder : configCandidates) {
|
||||
BeanDefinition bd = holder.getBeanDefinition();
|
||||
try {
|
||||
ConfigurationClass configClass;
|
||||
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
|
||||
parse(annotatedBeanDef.getMetadata(), holder.getBeanName());
|
||||
configClass = parse(annotatedBeanDef.getMetadata(), holder.getBeanName());
|
||||
}
|
||||
else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
|
||||
parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
|
||||
configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
|
||||
}
|
||||
else {
|
||||
parse(bd.getBeanClassName(), holder.getBeanName());
|
||||
configClass = parse(bd.getBeanClassName(), holder.getBeanName());
|
||||
}
|
||||
|
||||
// Downgrade to lite (no enhancement) in case of no instance-level @Bean methods.
|
||||
if (!configClass.getMetadata().isAbstract() && !configClass.hasNonStaticBeanMethods() &&
|
||||
ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(
|
||||
bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) {
|
||||
bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE,
|
||||
ConfigurationClassUtils.CONFIGURATION_CLASS_LITE);
|
||||
}
|
||||
}
|
||||
catch (BeanDefinitionStoreException ex) {
|
||||
@@ -183,31 +191,37 @@ class ConfigurationClassParser {
|
||||
this.deferredImportSelectorHandler.process();
|
||||
}
|
||||
|
||||
protected final void parse(@Nullable String className, String beanName) throws IOException {
|
||||
final ConfigurationClass parse(AnnotationMetadata metadata, String beanName) {
|
||||
ConfigurationClass configClass = new ConfigurationClass(metadata, beanName);
|
||||
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
|
||||
return configClass;
|
||||
}
|
||||
|
||||
final ConfigurationClass parse(Class<?> clazz, String beanName) {
|
||||
ConfigurationClass configClass = new ConfigurationClass(clazz, beanName);
|
||||
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
|
||||
return configClass;
|
||||
}
|
||||
|
||||
final ConfigurationClass parse(@Nullable String className, String beanName) throws IOException {
|
||||
Assert.notNull(className, "No bean class name for configuration class bean definition");
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
|
||||
}
|
||||
|
||||
protected final void parse(Class<?> clazz, String beanName) throws IOException {
|
||||
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
|
||||
}
|
||||
|
||||
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
|
||||
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
|
||||
ConfigurationClass configClass = new ConfigurationClass(reader, beanName);
|
||||
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
|
||||
return configClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate each {@link ConfigurationClass} object.
|
||||
* @see ConfigurationClass#validate
|
||||
*/
|
||||
public void validate() {
|
||||
void validate() {
|
||||
for (ConfigurationClass configClass : this.configurationClasses.keySet()) {
|
||||
configClass.validate(this.problemReporter);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<ConfigurationClass> getConfigurationClasses() {
|
||||
Set<ConfigurationClass> getConfigurationClasses() {
|
||||
return this.configurationClasses.keySet();
|
||||
}
|
||||
|
||||
@@ -216,7 +230,7 @@ class ConfigurationClassParser {
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
|
||||
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) {
|
||||
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
|
||||
return;
|
||||
}
|
||||
@@ -448,7 +462,7 @@ class ConfigurationClassParser {
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@code @Import} class, considering all meta-annotations.
|
||||
* Returns {@code @Import} classes, considering all meta-annotations.
|
||||
*/
|
||||
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
|
||||
Set<SourceClass> imports = new LinkedHashSet<>();
|
||||
@@ -636,7 +650,7 @@ class ConfigurationClassParser {
|
||||
|
||||
private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<>();
|
||||
|
||||
public void registerImport(AnnotationMetadata importingClass, String importedClass) {
|
||||
void registerImport(AnnotationMetadata importingClass, String importedClass) {
|
||||
this.imports.add(importedClass, importingClass);
|
||||
}
|
||||
|
||||
@@ -691,7 +705,7 @@ class ConfigurationClassParser {
|
||||
* @param configClass the source configuration class
|
||||
* @param importSelector the selector to handle
|
||||
*/
|
||||
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
|
||||
void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
|
||||
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
|
||||
if (this.deferredImportSelectors == null) {
|
||||
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
|
||||
@@ -703,7 +717,7 @@ class ConfigurationClassParser {
|
||||
}
|
||||
}
|
||||
|
||||
public void process() {
|
||||
void process() {
|
||||
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
|
||||
this.deferredImportSelectors = null;
|
||||
try {
|
||||
@@ -727,7 +741,7 @@ class ConfigurationClassParser {
|
||||
|
||||
private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
|
||||
|
||||
public void register(DeferredImportSelectorHolder deferredImport) {
|
||||
void register(DeferredImportSelectorHolder deferredImport) {
|
||||
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
|
||||
DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
|
||||
(group != null ? group : deferredImport),
|
||||
@@ -737,7 +751,7 @@ class ConfigurationClassParser {
|
||||
deferredImport.getConfigurationClass());
|
||||
}
|
||||
|
||||
public void processGroupImports() {
|
||||
void processGroupImports() {
|
||||
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
|
||||
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
|
||||
grouping.getImports().forEach(entry -> {
|
||||
@@ -775,16 +789,16 @@ class ConfigurationClassParser {
|
||||
|
||||
private final DeferredImportSelector importSelector;
|
||||
|
||||
public DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) {
|
||||
DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) {
|
||||
this.configurationClass = configClass;
|
||||
this.importSelector = selector;
|
||||
}
|
||||
|
||||
public ConfigurationClass getConfigurationClass() {
|
||||
ConfigurationClass getConfigurationClass() {
|
||||
return this.configurationClass;
|
||||
}
|
||||
|
||||
public DeferredImportSelector getImportSelector() {
|
||||
DeferredImportSelector getImportSelector() {
|
||||
return this.importSelector;
|
||||
}
|
||||
}
|
||||
@@ -800,7 +814,7 @@ class ConfigurationClassParser {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public void add(DeferredImportSelectorHolder deferredImport) {
|
||||
void add(DeferredImportSelectorHolder deferredImport) {
|
||||
this.deferredImports.add(deferredImport);
|
||||
}
|
||||
|
||||
@@ -808,7 +822,7 @@ class ConfigurationClassParser {
|
||||
* Return the imports defined by the group.
|
||||
* @return each import with its associated configuration class
|
||||
*/
|
||||
public Iterable<Group.Entry> getImports() {
|
||||
Iterable<Group.Entry> getImports() {
|
||||
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
|
||||
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
|
||||
deferredImport.getImportSelector());
|
||||
@@ -816,7 +830,7 @@ class ConfigurationClassParser {
|
||||
return this.group.selectImports();
|
||||
}
|
||||
|
||||
public Predicate<String> getCandidateFilter() {
|
||||
Predicate<String> getCandidateFilter() {
|
||||
Predicate<String> mergedFilter = DEFAULT_EXCLUSION_FILTER;
|
||||
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
|
||||
Predicate<String> selectorFilter = deferredImport.getImportSelector().getExclusionFilter();
|
||||
|
||||
+11
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -61,7 +61,8 @@ import org.springframework.util.ClassUtils;
|
||||
* interactions on a {@link org.springframework.context.ConfigurableApplicationContext}.
|
||||
*
|
||||
* <p>As of 6.1, this also includes support for JVM checkpoint/restore (Project CRaC)
|
||||
* when the {@code org.crac:crac} dependency on the classpath.
|
||||
* when the {@code org.crac:crac} dependency is on the classpath. All running beans
|
||||
* will get stopped and restarted according to the CRaC checkpoint/restore callbacks.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
@@ -379,7 +380,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
}
|
||||
|
||||
|
||||
// overridable hooks
|
||||
// Overridable hooks
|
||||
|
||||
/**
|
||||
* Retrieve all applicable Lifecycle beans: all singletons that have already been created,
|
||||
@@ -493,11 +494,13 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
}
|
||||
}
|
||||
try {
|
||||
latch.await(this.timeout, TimeUnit.MILLISECONDS);
|
||||
if (latch.getCount() > 0 && !countDownBeanNames.isEmpty() && logger.isInfoEnabled()) {
|
||||
logger.info("Shutdown phase " + this.phase + " ends with " + countDownBeanNames.size() +
|
||||
" bean" + (countDownBeanNames.size() > 1 ? "s" : "") +
|
||||
" still running after timeout of " + this.timeout + "ms: " + countDownBeanNames);
|
||||
if (!latch.await(this.timeout, TimeUnit.MILLISECONDS)) {
|
||||
// Count is still >0 after timeout
|
||||
if (!countDownBeanNames.isEmpty() && logger.isInfoEnabled()) {
|
||||
logger.info("Shutdown phase " + this.phase + " ends with " + countDownBeanNames.size() +
|
||||
" bean" + (countDownBeanNames.size() > 1 ? "s" : "") +
|
||||
" still running after timeout of " + this.timeout + "ms: " + countDownBeanNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.scheduling.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||
@@ -53,6 +54,7 @@ public class ExecutorBeanDefinitionParser extends AbstractSingleBeanDefinitionPa
|
||||
if (StringUtils.hasText(poolSize)) {
|
||||
builder.addPropertyValue("poolSize", poolSize);
|
||||
}
|
||||
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
}
|
||||
|
||||
private void configureRejectionPolicy(Element element, BeanDefinitionBuilder builder) {
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.scheduling.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -41,6 +42,7 @@ public class SchedulerBeanDefinitionParser extends AbstractSingleBeanDefinitionP
|
||||
if (StringUtils.hasText(poolSize)) {
|
||||
builder.addPropertyValue("poolSize", poolSize);
|
||||
}
|
||||
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -317,8 +317,16 @@ final class QuartzCronField extends CronField {
|
||||
private static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) {
|
||||
TemporalAdjuster adjuster = TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek);
|
||||
return temporal -> {
|
||||
Temporal result = adjuster.adjustInto(temporal);
|
||||
return rollbackToMidnight(temporal, result);
|
||||
// TemporalAdjusters can overflow to a different month
|
||||
// in this case, attempt the same adjustment with the next/previous month
|
||||
for (int i = 0; i < 12; i++) {
|
||||
Temporal result = adjuster.adjustInto(temporal);
|
||||
if (result.get(ChronoField.MONTH_OF_YEAR) == temporal.get(ChronoField.MONTH_OF_YEAR)) {
|
||||
return rollbackToMidnight(temporal, result);
|
||||
}
|
||||
temporal = result;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+9
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.validation.beanvalidation;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,13 @@ public class OptionalValidatorFactoryBean extends LocalValidatorFactoryBean {
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex);
|
||||
Log logger = LogFactory.getLog(getClass());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to set up a Bean Validation provider", ex);
|
||||
}
|
||||
else if (logger.isInfoEnabled()) {
|
||||
logger.info("Failed to set up a Bean Validation provider: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+75
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -168,6 +168,25 @@ class AnnotationBeanNameGeneratorTests {
|
||||
assertGeneratedName(RestControllerAdviceClass.class, "myRestControllerAdvice");
|
||||
}
|
||||
|
||||
@Test // gh-34317
|
||||
void generateBeanNameFromStereotypeAnnotationWithStringValueAsExplicitAliasForMetaAnnotationOtherThanComponent() {
|
||||
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
|
||||
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithoutExplicitName".
|
||||
assertGeneratedName(StereotypeWithoutExplicitName.class, "enigma");
|
||||
}
|
||||
|
||||
@Test // gh-34317
|
||||
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentNameWithBlankName() {
|
||||
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
|
||||
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithGeneratedName".
|
||||
assertGeneratedName(StereotypeWithGeneratedName.class, "enigma");
|
||||
}
|
||||
|
||||
@Test // gh-34317
|
||||
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentName() {
|
||||
assertGeneratedName(StereotypeWithExplicitName.class, "explicitName");
|
||||
}
|
||||
|
||||
|
||||
private void assertGeneratedName(Class<?> clazz, String expectedName) {
|
||||
BeanDefinition bd = annotatedBeanDef(clazz);
|
||||
@@ -210,7 +229,7 @@ class AnnotationBeanNameGeneratorTests {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Component
|
||||
@interface ConventionBasedComponent1 {
|
||||
// This intentionally convention-based. Please do not add @AliasFor.
|
||||
// This is intentionally convention-based. Please do not add @AliasFor.
|
||||
// See gh-31093.
|
||||
String value() default "";
|
||||
}
|
||||
@@ -218,7 +237,7 @@ class AnnotationBeanNameGeneratorTests {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Component
|
||||
@interface ConventionBasedComponent2 {
|
||||
// This intentionally convention-based. Please do not add @AliasFor.
|
||||
// This is intentionally convention-based. Please do not add @AliasFor.
|
||||
// See gh-31093.
|
||||
String value() default "";
|
||||
}
|
||||
@@ -260,7 +279,7 @@ class AnnotationBeanNameGeneratorTests {
|
||||
@Target(ElementType.TYPE)
|
||||
@Controller
|
||||
@interface TestRestController {
|
||||
// This intentionally convention-based. Please do not add @AliasFor.
|
||||
// This is intentionally convention-based. Please do not add @AliasFor.
|
||||
// See gh-31093.
|
||||
String value() default "";
|
||||
}
|
||||
@@ -319,7 +338,6 @@ class AnnotationBeanNameGeneratorTests {
|
||||
String[] basePackages() default {};
|
||||
}
|
||||
|
||||
|
||||
@TestControllerAdvice(basePackages = "com.example", name = "myControllerAdvice")
|
||||
static class ControllerAdviceClass {
|
||||
}
|
||||
@@ -328,4 +346,56 @@ class AnnotationBeanNameGeneratorTests {
|
||||
static class RestControllerAdviceClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@interface MetaAnnotationWithStringAttribute {
|
||||
|
||||
String attribute() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom stereotype annotation which has a {@code String value} attribute that
|
||||
* is explicitly declared as an alias for an attribute in a meta-annotation
|
||||
* other than {@link Component @Component}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Component
|
||||
@MetaAnnotationWithStringAttribute
|
||||
@interface MyStereotype {
|
||||
|
||||
@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@MyStereotype("enigma")
|
||||
static class StereotypeWithoutExplicitName {
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom stereotype annotation which is identical to {@link MyStereotype @MyStereotype}
|
||||
* except that it has a {@link #name} attribute that is an explicit alias for
|
||||
* {@link Component#value}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Component
|
||||
@MetaAnnotationWithStringAttribute
|
||||
@interface MyNamedStereotype {
|
||||
|
||||
@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
|
||||
String value() default "";
|
||||
|
||||
@AliasFor(annotation = Component.class, attribute = "value")
|
||||
String name() default "";
|
||||
}
|
||||
|
||||
@MyNamedStereotype(value = "enigma", name ="explicitName")
|
||||
static class StereotypeWithExplicitName {
|
||||
}
|
||||
|
||||
@MyNamedStereotype(value = "enigma")
|
||||
static class StereotypeWithGeneratedName {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+145
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,11 +18,16 @@ package org.springframework.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.security.SecureClassLoader;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.OverridingClassLoader;
|
||||
import org.springframework.core.SmartClassLoader;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -36,19 +41,108 @@ class ConfigurationClassEnhancerTests {
|
||||
@Test
|
||||
void enhanceReloadedClass() throws Exception {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
|
||||
ClassLoader parentClassLoader = getClass().getClassLoader();
|
||||
CustomClassLoader classLoader = new CustomClassLoader(parentClassLoader);
|
||||
ClassLoader classLoader = new CustomSmartClassLoader(parentClassLoader);
|
||||
Class<?> myClass = parentClassLoader.loadClass(MyConfig.class.getName());
|
||||
configurationClassEnhancer.enhance(myClass, parentClassLoader);
|
||||
Class<?> myReloadedClass = classLoader.loadClass(MyConfig.class.getName());
|
||||
Class<?> enhancedReloadedClass = configurationClassEnhancer.enhance(myReloadedClass, classLoader);
|
||||
assertThat(enhancedReloadedClass.getClassLoader()).isEqualTo(classLoader);
|
||||
Class<?> enhancedClass = configurationClassEnhancer.enhance(myClass, parentClassLoader);
|
||||
assertThat(myClass).isAssignableFrom(enhancedClass);
|
||||
|
||||
myClass = classLoader.loadClass(MyConfig.class.getName());
|
||||
enhancedClass = configurationClassEnhancer.enhance(myClass, classLoader);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader);
|
||||
assertThat(myClass).isAssignableFrom(enhancedClass);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPublicClass() {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
|
||||
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader);
|
||||
|
||||
classLoader = new OverridingClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNonPublicClass() {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
|
||||
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new OverridingClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNonPublicMethod() {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
|
||||
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new OverridingClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class MyConfig {
|
||||
|
||||
@Bean
|
||||
String myBean() {
|
||||
return "bean";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
public static class MyConfigWithPublicClass {
|
||||
|
||||
@Bean
|
||||
public String myBean() {
|
||||
return "bean";
|
||||
@@ -56,9 +150,29 @@ class ConfigurationClassEnhancerTests {
|
||||
}
|
||||
|
||||
|
||||
static class CustomClassLoader extends SecureClassLoader implements SmartClassLoader {
|
||||
@Configuration
|
||||
static class MyConfigWithNonPublicClass {
|
||||
|
||||
CustomClassLoader(ClassLoader parent) {
|
||||
@Bean
|
||||
public String myBean() {
|
||||
return "bean";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
public static class MyConfigWithNonPublicMethod {
|
||||
|
||||
@Bean
|
||||
String myBean() {
|
||||
return "bean";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class CustomSmartClassLoader extends SecureClassLoader implements SmartClassLoader {
|
||||
|
||||
CustomSmartClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@@ -82,6 +196,29 @@ class ConfigurationClassEnhancerTests {
|
||||
public boolean isClassReloadable(Class<?> clazz) {
|
||||
return clazz.getName().contains("MyConfig");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getOriginalClassLoader() {
|
||||
return getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
|
||||
return defineClass(name, b, 0, b.length, protectionDomain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class BasicSmartClassLoader extends SecureClassLoader implements SmartClassLoader {
|
||||
|
||||
BasicSmartClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
|
||||
return defineClass(name, b, 0, b.length, protectionDomain);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+44
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -67,6 +67,7 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -104,6 +105,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertThat(bar.foo).isSameAs(foo);
|
||||
@@ -118,6 +120,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertThat(bar.foo).isSameAs(foo);
|
||||
@@ -126,12 +129,29 @@ class ConfigurationClassPostProcessorTests {
|
||||
assertThat(beanFactory.getDependentBeans("config")).contains("bar");
|
||||
}
|
||||
|
||||
@Test // gh-34663
|
||||
void enhancementIsPresentForAbstractConfigClassWithoutBeanMethods() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(AbstractConfigWithoutBeanMethods.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
RootBeanDefinition beanDefinition = (RootBeanDefinition) beanFactory.getBeanDefinition("config");
|
||||
assertThat(beanDefinition.hasBeanClass()).isTrue();
|
||||
assertThat(beanDefinition.getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertThat(bar.foo).isSameAs(foo);
|
||||
assertThat(beanFactory.getDependentBeans("foo")).contains("bar");
|
||||
String[] dependentsOfSingletonBeanConfig = beanFactory.getDependentBeans(SingletonBeanConfig.class.getName());
|
||||
assertThat(dependentsOfSingletonBeanConfig).containsOnly("foo", "bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertThat(bar.foo).isNotSameAs(foo);
|
||||
@@ -143,6 +163,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertThat(bar.foo).isNotSameAs(foo);
|
||||
@@ -154,6 +175,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
@@ -167,6 +189,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
@@ -174,6 +197,15 @@ class ConfigurationClassPostProcessorTests {
|
||||
assertThat(bar.foo).isNotSameAs(foo);
|
||||
}
|
||||
|
||||
@Test // gh-34486
|
||||
void enhancementIsNotPresentWithEmptyConfig() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(EmptyConfig.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
|
||||
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig"));
|
||||
@@ -1158,7 +1190,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class StaticSingletonBeanConfig {
|
||||
static final class StaticSingletonBeanConfig {
|
||||
|
||||
@Bean
|
||||
public static Foo foo() {
|
||||
@@ -1171,6 +1203,16 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(SingletonBeanConfig.class)
|
||||
abstract static class AbstractConfigWithoutBeanMethods {
|
||||
// This class intentionally does NOT declare @Bean methods.
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class EmptyConfig {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
static class OverridingSingletonBeanConfig {
|
||||
|
||||
+7
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -37,16 +37,18 @@ class InvalidConfigurationClassDefinitionTests {
|
||||
@Test
|
||||
void configurationClassesMayNotBeFinal() {
|
||||
@Configuration
|
||||
final class Config { }
|
||||
final class Config {
|
||||
@Bean String dummy() { return "dummy"; }
|
||||
}
|
||||
|
||||
BeanDefinition configBeanDef = rootBeanDefinition(Config.class).getBeanDefinition();
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition("config", configBeanDef);
|
||||
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() ->
|
||||
pp.postProcessBeanFactory(beanFactory))
|
||||
.withMessageContaining("Remove the final modifier");
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() -> pp.postProcessBeanFactory(beanFactory))
|
||||
.withMessageContaining("Remove the final modifier");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+41
-21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -54,10 +54,11 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void customLifecycleProcessorInstance() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
BeanDefinition beanDefinition = new RootBeanDefinition(DefaultLifecycleProcessor.class);
|
||||
beanDefinition.getPropertyValues().addPropertyValue("timeoutPerShutdownPhase", 1000);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.registerBeanDefinition("lifecycleProcessor", beanDefinition);
|
||||
context.registerBeanDefinition(StaticApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME, beanDefinition);
|
||||
|
||||
context.refresh();
|
||||
LifecycleProcessor bean = context.getBean("lifecycleProcessor", LifecycleProcessor.class);
|
||||
Object contextLifecycleProcessor = new DirectFieldAccessor(context).getPropertyValue("lifecycleProcessor");
|
||||
@@ -70,11 +71,12 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void singleSmartLifecycleAutoStartup() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean bean = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
|
||||
bean.setAutoStartup(true);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean", bean);
|
||||
|
||||
assertThat(bean.isRunning()).isFalse();
|
||||
context.refresh();
|
||||
assertThat(bean.isRunning()).isTrue();
|
||||
@@ -114,12 +116,13 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void singleSmartLifecycleAutoStartupWithFailingLifecycleBean() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean bean = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
|
||||
bean.setAutoStartup(true);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean", bean);
|
||||
context.registerSingleton("failingBean", FailingLifecycleBean.class);
|
||||
|
||||
assertThat(bean.isRunning()).isFalse();
|
||||
assertThatExceptionOfType(ApplicationContextException.class)
|
||||
.isThrownBy(context::refresh).withCauseInstanceOf(IllegalStateException.class);
|
||||
@@ -130,11 +133,12 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void singleSmartLifecycleWithoutAutoStartup() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean bean = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
|
||||
bean.setAutoStartup(false);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean", bean);
|
||||
|
||||
assertThat(bean.isRunning()).isFalse();
|
||||
context.refresh();
|
||||
assertThat(bean.isRunning()).isFalse();
|
||||
@@ -148,15 +152,16 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void singleSmartLifecycleAutoStartupWithNonAutoStartupDependency() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean bean = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
|
||||
bean.setAutoStartup(true);
|
||||
TestSmartLifecycleBean dependency = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
|
||||
dependency.setAutoStartup(false);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean", bean);
|
||||
context.getBeanFactory().registerSingleton("dependency", dependency);
|
||||
context.getBeanFactory().registerDependentBean("dependency", "bean");
|
||||
|
||||
assertThat(bean.isRunning()).isFalse();
|
||||
assertThat(dependency.isRunning()).isFalse();
|
||||
context.refresh();
|
||||
@@ -171,18 +176,19 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void smartLifecycleGroupStartup() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forStartupTests(Integer.MIN_VALUE, startedBeans);
|
||||
TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
|
||||
TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forStartupTests(2, startedBeans);
|
||||
TestSmartLifecycleBean bean3 = TestSmartLifecycleBean.forStartupTests(3, startedBeans);
|
||||
TestSmartLifecycleBean beanMax = TestSmartLifecycleBean.forStartupTests(Integer.MAX_VALUE, startedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean3", bean3);
|
||||
context.getBeanFactory().registerSingleton("beanMin", beanMin);
|
||||
context.getBeanFactory().registerSingleton("bean2", bean2);
|
||||
context.getBeanFactory().registerSingleton("beanMax", beanMax);
|
||||
context.getBeanFactory().registerSingleton("bean1", bean1);
|
||||
|
||||
assertThat(beanMin.isRunning()).isFalse();
|
||||
assertThat(bean1.isRunning()).isFalse();
|
||||
assertThat(bean2.isRunning()).isFalse();
|
||||
@@ -202,16 +208,17 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void contextRefreshThenStartWithMixedBeans() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestLifecycleBean simpleBean1 = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
TestLifecycleBean simpleBean2 = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
TestSmartLifecycleBean smartBean1 = TestSmartLifecycleBean.forStartupTests(5, startedBeans);
|
||||
TestSmartLifecycleBean smartBean2 = TestSmartLifecycleBean.forStartupTests(-3, startedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("simpleBean1", simpleBean1);
|
||||
context.getBeanFactory().registerSingleton("smartBean1", smartBean1);
|
||||
context.getBeanFactory().registerSingleton("simpleBean2", simpleBean2);
|
||||
context.getBeanFactory().registerSingleton("smartBean2", smartBean2);
|
||||
|
||||
assertThat(simpleBean1.isRunning()).isFalse();
|
||||
assertThat(simpleBean2.isRunning()).isFalse();
|
||||
assertThat(smartBean1.isRunning()).isFalse();
|
||||
@@ -233,16 +240,17 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void contextRefreshThenStopAndRestartWithMixedBeans() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestLifecycleBean simpleBean1 = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
TestLifecycleBean simpleBean2 = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
TestSmartLifecycleBean smartBean1 = TestSmartLifecycleBean.forStartupTests(5, startedBeans);
|
||||
TestSmartLifecycleBean smartBean2 = TestSmartLifecycleBean.forStartupTests(-3, startedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("simpleBean1", simpleBean1);
|
||||
context.getBeanFactory().registerSingleton("smartBean1", smartBean1);
|
||||
context.getBeanFactory().registerSingleton("simpleBean2", simpleBean2);
|
||||
context.getBeanFactory().registerSingleton("smartBean2", smartBean2);
|
||||
|
||||
assertThat(simpleBean1.isRunning()).isFalse();
|
||||
assertThat(simpleBean2.isRunning()).isFalse();
|
||||
assertThat(smartBean1.isRunning()).isFalse();
|
||||
@@ -270,16 +278,17 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void contextRefreshThenStopForRestartWithMixedBeans() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestLifecycleBean simpleBean1 = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
TestLifecycleBean simpleBean2 = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
TestSmartLifecycleBean smartBean1 = TestSmartLifecycleBean.forStartupTests(5, startedBeans);
|
||||
TestSmartLifecycleBean smartBean2 = TestSmartLifecycleBean.forStartupTests(-3, startedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("simpleBean1", simpleBean1);
|
||||
context.getBeanFactory().registerSingleton("smartBean1", smartBean1);
|
||||
context.getBeanFactory().registerSingleton("simpleBean2", simpleBean2);
|
||||
context.getBeanFactory().registerSingleton("smartBean2", smartBean2);
|
||||
|
||||
assertThat(simpleBean1.isRunning()).isFalse();
|
||||
assertThat(simpleBean2.isRunning()).isFalse();
|
||||
assertThat(smartBean1.isRunning()).isFalse();
|
||||
@@ -319,6 +328,7 @@ class DefaultLifecycleProcessorTests {
|
||||
@Test
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
void smartLifecycleGroupShutdown() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 300, stoppedBeans);
|
||||
TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(3, 100, stoppedBeans);
|
||||
@@ -327,7 +337,6 @@ class DefaultLifecycleProcessorTests {
|
||||
TestSmartLifecycleBean bean5 = TestSmartLifecycleBean.forShutdownTests(2, 700, stoppedBeans);
|
||||
TestSmartLifecycleBean bean6 = TestSmartLifecycleBean.forShutdownTests(Integer.MAX_VALUE, 200, stoppedBeans);
|
||||
TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(3, 200, stoppedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean1", bean1);
|
||||
context.getBeanFactory().registerSingleton("bean2", bean2);
|
||||
context.getBeanFactory().registerSingleton("bean3", bean3);
|
||||
@@ -335,6 +344,7 @@ class DefaultLifecycleProcessorTests {
|
||||
context.getBeanFactory().registerSingleton("bean5", bean5);
|
||||
context.getBeanFactory().registerSingleton("bean6", bean6);
|
||||
context.getBeanFactory().registerSingleton("bean7", bean7);
|
||||
|
||||
context.refresh();
|
||||
context.stop();
|
||||
assertThat(stoppedBeans).satisfiesExactly(hasPhase(Integer.MAX_VALUE), hasPhase(3),
|
||||
@@ -345,11 +355,12 @@ class DefaultLifecycleProcessorTests {
|
||||
@Test
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
void singleSmartLifecycleShutdown() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean bean = TestSmartLifecycleBean.forShutdownTests(99, 300, stoppedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean", bean);
|
||||
context.refresh();
|
||||
|
||||
assertThat(bean.isRunning()).isTrue();
|
||||
context.stop();
|
||||
assertThat(bean.isRunning()).isFalse();
|
||||
@@ -359,10 +370,11 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void singleLifecycleShutdown() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
|
||||
Lifecycle bean = new TestLifecycleBean(null, stoppedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean", bean);
|
||||
|
||||
context.refresh();
|
||||
assertThat(bean.isRunning()).isFalse();
|
||||
bean.start();
|
||||
@@ -375,6 +387,7 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void mixedShutdown() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
|
||||
Lifecycle bean1 = TestLifecycleBean.forShutdownTests(stoppedBeans);
|
||||
Lifecycle bean2 = TestSmartLifecycleBean.forShutdownTests(500, 200, stoppedBeans);
|
||||
@@ -383,7 +396,6 @@ class DefaultLifecycleProcessorTests {
|
||||
Lifecycle bean5 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans);
|
||||
Lifecycle bean6 = TestSmartLifecycleBean.forShutdownTests(-1, 100, stoppedBeans);
|
||||
Lifecycle bean7 = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 300, stoppedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("bean1", bean1);
|
||||
context.getBeanFactory().registerSingleton("bean2", bean2);
|
||||
context.getBeanFactory().registerSingleton("bean3", bean3);
|
||||
@@ -391,6 +403,7 @@ class DefaultLifecycleProcessorTests {
|
||||
context.getBeanFactory().registerSingleton("bean5", bean5);
|
||||
context.getBeanFactory().registerSingleton("bean6", bean6);
|
||||
context.getBeanFactory().registerSingleton("bean7", bean7);
|
||||
|
||||
context.refresh();
|
||||
assertThat(bean2.isRunning()).isTrue();
|
||||
assertThat(bean3.isRunning()).isTrue();
|
||||
@@ -418,17 +431,18 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void dependencyStartedFirstEvenIfItsPhaseIsHigher() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forStartupTests(Integer.MIN_VALUE, startedBeans);
|
||||
TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forStartupTests(2, startedBeans);
|
||||
TestSmartLifecycleBean bean99 = TestSmartLifecycleBean.forStartupTests(99, startedBeans);
|
||||
TestSmartLifecycleBean beanMax = TestSmartLifecycleBean.forStartupTests(Integer.MAX_VALUE, startedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("beanMin", beanMin);
|
||||
context.getBeanFactory().registerSingleton("bean2", bean2);
|
||||
context.getBeanFactory().registerSingleton("bean99", bean99);
|
||||
context.getBeanFactory().registerSingleton("beanMax", beanMax);
|
||||
context.getBeanFactory().registerDependentBean("bean99", "bean2");
|
||||
|
||||
context.refresh();
|
||||
assertThat(beanMin.isRunning()).isTrue();
|
||||
assertThat(bean2.isRunning()).isTrue();
|
||||
@@ -446,6 +460,7 @@ class DefaultLifecycleProcessorTests {
|
||||
@Test
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
void dependentShutdownFirstEvenIfItsPhaseIsLower() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 100, stoppedBeans);
|
||||
TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans);
|
||||
@@ -453,7 +468,6 @@ class DefaultLifecycleProcessorTests {
|
||||
TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(2, 300, stoppedBeans);
|
||||
TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(7, 400, stoppedBeans);
|
||||
TestSmartLifecycleBean beanMax = TestSmartLifecycleBean.forShutdownTests(Integer.MAX_VALUE, 400, stoppedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("beanMin", beanMin);
|
||||
context.getBeanFactory().registerSingleton("bean1", bean1);
|
||||
context.getBeanFactory().registerSingleton("bean2", bean2);
|
||||
@@ -461,6 +475,7 @@ class DefaultLifecycleProcessorTests {
|
||||
context.getBeanFactory().registerSingleton("bean99", bean99);
|
||||
context.getBeanFactory().registerSingleton("beanMax", beanMax);
|
||||
context.getBeanFactory().registerDependentBean("bean99", "bean2");
|
||||
|
||||
context.refresh();
|
||||
assertThat(beanMin.isRunning()).isTrue();
|
||||
assertThat(bean1.isRunning()).isTrue();
|
||||
@@ -486,17 +501,18 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void dependencyStartedFirstAndIsSmartLifecycle() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean beanNegative = TestSmartLifecycleBean.forStartupTests(-99, startedBeans);
|
||||
TestSmartLifecycleBean bean99 = TestSmartLifecycleBean.forStartupTests(99, startedBeans);
|
||||
TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forStartupTests(7, startedBeans);
|
||||
TestLifecycleBean simpleBean = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("beanNegative", beanNegative);
|
||||
context.getBeanFactory().registerSingleton("bean7", bean7);
|
||||
context.getBeanFactory().registerSingleton("bean99", bean99);
|
||||
context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
|
||||
context.getBeanFactory().registerDependentBean("bean7", "simpleBean");
|
||||
|
||||
context.refresh();
|
||||
context.stop();
|
||||
startedBeans.clear();
|
||||
@@ -514,6 +530,7 @@ class DefaultLifecycleProcessorTests {
|
||||
@Test
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
void dependentShutdownFirstAndIsSmartLifecycle() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 400, stoppedBeans);
|
||||
TestSmartLifecycleBean beanNegative = TestSmartLifecycleBean.forShutdownTests(-99, 100, stoppedBeans);
|
||||
@@ -521,7 +538,6 @@ class DefaultLifecycleProcessorTests {
|
||||
TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(2, 300, stoppedBeans);
|
||||
TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(7, 400, stoppedBeans);
|
||||
TestLifecycleBean simpleBean = TestLifecycleBean.forShutdownTests(stoppedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("beanMin", beanMin);
|
||||
context.getBeanFactory().registerSingleton("beanNegative", beanNegative);
|
||||
context.getBeanFactory().registerSingleton("bean1", bean1);
|
||||
@@ -529,6 +545,7 @@ class DefaultLifecycleProcessorTests {
|
||||
context.getBeanFactory().registerSingleton("bean7", bean7);
|
||||
context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
|
||||
context.getBeanFactory().registerDependentBean("simpleBean", "beanNegative");
|
||||
|
||||
context.refresh();
|
||||
assertThat(beanMin.isRunning()).isTrue();
|
||||
assertThat(beanNegative.isRunning()).isTrue();
|
||||
@@ -551,15 +568,16 @@ class DefaultLifecycleProcessorTests {
|
||||
|
||||
@Test
|
||||
void dependencyStartedFirstButNotSmartLifecycle() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forStartupTests(Integer.MIN_VALUE, startedBeans);
|
||||
TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forStartupTests(7, startedBeans);
|
||||
TestLifecycleBean simpleBean = TestLifecycleBean.forStartupTests(startedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("beanMin", beanMin);
|
||||
context.getBeanFactory().registerSingleton("bean7", bean7);
|
||||
context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
|
||||
context.getBeanFactory().registerDependentBean("simpleBean", "beanMin");
|
||||
|
||||
context.refresh();
|
||||
assertThat(beanMin.isRunning()).isTrue();
|
||||
assertThat(bean7.isRunning()).isTrue();
|
||||
@@ -572,19 +590,20 @@ class DefaultLifecycleProcessorTests {
|
||||
@Test
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
void dependentShutdownFirstButNotSmartLifecycle() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
|
||||
TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans);
|
||||
TestLifecycleBean simpleBean = TestLifecycleBean.forShutdownTests(stoppedBeans);
|
||||
TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(2, 300, stoppedBeans);
|
||||
TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(7, 400, stoppedBeans);
|
||||
TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 400, stoppedBeans);
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.getBeanFactory().registerSingleton("beanMin", beanMin);
|
||||
context.getBeanFactory().registerSingleton("bean1", bean1);
|
||||
context.getBeanFactory().registerSingleton("bean2", bean2);
|
||||
context.getBeanFactory().registerSingleton("bean7", bean7);
|
||||
context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
|
||||
context.getBeanFactory().registerDependentBean("bean2", "simpleBean");
|
||||
|
||||
context.refresh();
|
||||
assertThat(beanMin.isRunning()).isTrue();
|
||||
assertThat(bean1.isRunning()).isTrue();
|
||||
@@ -611,6 +630,7 @@ class DefaultLifecycleProcessorTests {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static class TestLifecycleBean implements Lifecycle {
|
||||
|
||||
private final CopyOnWriteArrayList<Lifecycle> startedBeans;
|
||||
|
||||
+3
-11
@@ -209,18 +209,10 @@ abstract class AbstractSchedulingTaskExecutorTests {
|
||||
CompletableFuture<?> future1 = executor.submitCompletable(new TestTask(this.testName, -1));
|
||||
CompletableFuture<?> future2 = executor.submitCompletable(new TestTask(this.testName, -1));
|
||||
shutdownExecutor();
|
||||
|
||||
try {
|
||||
assertThatExceptionOfType(TimeoutException.class).isThrownBy(() -> {
|
||||
future1.get(1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// ignore
|
||||
}
|
||||
Awaitility.await()
|
||||
.atMost(5, TimeUnit.SECONDS)
|
||||
.pollInterval(10, TimeUnit.MILLISECONDS)
|
||||
.untilAsserted(() -> assertThatExceptionOfType(TimeoutException.class)
|
||||
.isThrownBy(() -> future2.get(1000, TimeUnit.MILLISECONDS)));
|
||||
future2.get(1000, TimeUnit.MILLISECONDS);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+16
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -39,6 +39,7 @@ import static java.time.temporal.TemporalAdjusters.next;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CronExpression}.
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class CronExpressionTests {
|
||||
@@ -1092,6 +1093,20 @@ class CronExpressionTests {
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(FRIDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartz5thMondayOfTheMonthDayName() {
|
||||
CronExpression expression = CronExpression.parse("0 0 0 ? * MON#5");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2025, 1, 1, 0, 0, 0);
|
||||
|
||||
// first occurrence of 5 mondays in a month from last
|
||||
LocalDateTime expected = LocalDateTime.of(2025, 3, 31, 0, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(MONDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzFifthWednesdayOfTheMonth() {
|
||||
CronExpression expression = CronExpression.parse("0 0 0 ? * 3#5");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -27,6 +27,7 @@ import java.util.stream.Stream;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Gather the need for resources available at runtime.
|
||||
@@ -50,14 +51,14 @@ public class ResourceHints {
|
||||
this.resourceBundleHints = new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the resources that should be made available at runtime.
|
||||
* @return a stream of {@link ResourcePatternHints}
|
||||
*/
|
||||
public Stream<ResourcePatternHints> resourcePatternHints() {
|
||||
Stream<ResourcePatternHints> patterns = this.resourcePatternHints.stream();
|
||||
return (this.types.isEmpty() ? patterns
|
||||
: Stream.concat(Stream.of(typesPatternResourceHint()), patterns));
|
||||
return (this.types.isEmpty() ? patterns : Stream.concat(Stream.of(typesPatternResourceHint()), patterns));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,18 +71,18 @@ public class ResourceHints {
|
||||
|
||||
/**
|
||||
* Register a pattern if the given {@code location} is available on the
|
||||
* classpath. This delegates to {@link ClassLoader#getResource(String)}
|
||||
* which validates directories as well. The location is not included in
|
||||
* the hint.
|
||||
* @param classLoader the classloader to use
|
||||
* classpath. This delegates to {@link ClassLoader#getResource(String)} which
|
||||
* validates directories as well. The location is not included in the hint.
|
||||
* @param classLoader the ClassLoader to use, or {@code null} for the default
|
||||
* @param location a '/'-separated path name that should exist
|
||||
* @param resourceHint a builder to customize the resource pattern
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ResourceHints registerPatternIfPresent(@Nullable ClassLoader classLoader, String location,
|
||||
Consumer<ResourcePatternHints.Builder> resourceHint) {
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader : getClass().getClassLoader());
|
||||
if (classLoaderToUse.getResource(location) != null) {
|
||||
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
|
||||
if (classLoaderToUse != null && classLoaderToUse.getResource(location) != null) {
|
||||
registerPattern(resourceHint);
|
||||
}
|
||||
return this;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -25,8 +25,9 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* <p>Implementations of this interface can be registered dynamically by using
|
||||
* {@link org.springframework.context.annotation.ImportRuntimeHints @ImportRuntimeHints}
|
||||
* or statically in {@code META-INF/spring/aot.factories} by using the FQN of this
|
||||
* interface as the key. A standard no-arg constructor is required for implementations.
|
||||
* or statically in {@code META-INF/spring/aot.factories} by using the fully-qualified
|
||||
* class name of this interface as the key. A standard no-arg constructor is required
|
||||
* for implementations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
@@ -38,7 +39,7 @@ public interface RuntimeHintsRegistrar {
|
||||
/**
|
||||
* Contribute hints to the given {@link RuntimeHints} instance.
|
||||
* @param hints the hints contributed so far for the deployment unit
|
||||
* @param classLoader the classloader, or {@code null} if even the system ClassLoader isn't accessible
|
||||
* @param classLoader the ClassLoader to use, or {@code null} for the default
|
||||
*/
|
||||
void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader);
|
||||
|
||||
|
||||
+15
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import org.springframework.aot.hint.ResourceHints;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
@@ -66,19 +67,21 @@ public class FilePatternResourceHintsRegistrar {
|
||||
|
||||
@Deprecated(since = "6.0.12", forRemoval = true)
|
||||
public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) {
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader : getClass().getClassLoader());
|
||||
List<String> includes = new ArrayList<>();
|
||||
for (String location : this.classpathLocations) {
|
||||
if (classLoaderToUse.getResource(location) != null) {
|
||||
for (String filePrefix : this.filePrefixes) {
|
||||
for (String fileExtension : this.fileExtensions) {
|
||||
includes.add(location + filePrefix + "*" + fileExtension);
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
|
||||
if (classLoaderToUse != null) {
|
||||
List<String> includes = new ArrayList<>();
|
||||
for (String location : this.classpathLocations) {
|
||||
if (classLoaderToUse.getResource(location) != null) {
|
||||
for (String filePrefix : this.filePrefixes) {
|
||||
for (String fileExtension : this.fileExtensions) {
|
||||
includes.add(location + filePrefix + "*" + fileExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!includes.isEmpty()) {
|
||||
hints.registerPattern(hint -> hint.includes(includes.toArray(String[]::new)));
|
||||
if (!includes.isEmpty()) {
|
||||
hints.registerPattern(hint -> hint.includes(includes.toArray(String[]::new)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,8 +249,7 @@ public class FilePatternResourceHintsRegistrar {
|
||||
* classpath location that resolves against the {@code ClassLoader}, files
|
||||
* with the configured file prefixes and extensions are registered.
|
||||
* @param hints the hints contributed so far for the deployment unit
|
||||
* @param classLoader the classloader, or {@code null} if even the system
|
||||
* ClassLoader isn't accessible
|
||||
* @param classLoader the ClassLoader to use, or {@code null} for the default
|
||||
*/
|
||||
public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) {
|
||||
build().registerHints(hints, classLoader);
|
||||
|
||||
+6
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -48,10 +48,11 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader :
|
||||
SpringFactoriesLoaderRuntimeHints.class.getClassLoader());
|
||||
for (String resourceLocation : RESOURCE_LOCATIONS) {
|
||||
registerHints(hints, classLoaderToUse, resourceLocation);
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
|
||||
if (classLoaderToUse != null) {
|
||||
for (String resourceLocation : RESOURCE_LOCATIONS) {
|
||||
registerHints(hints, classLoaderToUse, resourceLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -123,13 +123,17 @@ abstract public class AbstractClassGenerator<T> implements ClassGenerator {
|
||||
}
|
||||
|
||||
public Object get(AbstractClassGenerator gen, boolean useCache) {
|
||||
if (!useCache) {
|
||||
return gen.generate(ClassLoaderData.this);
|
||||
}
|
||||
else {
|
||||
// SPRING PATCH BEGIN
|
||||
Object value = null;
|
||||
if (useCache) {
|
||||
Object cachedValue = generatedClasses.get(gen);
|
||||
return gen.unwrapCachedValue(cachedValue);
|
||||
value = gen.unwrapCachedValue(cachedValue);
|
||||
}
|
||||
if (value == null) { // fallback when cached WeakReference returns null
|
||||
value = gen.generate(ClassLoaderData.this);
|
||||
}
|
||||
return value;
|
||||
// SPRING PATCH END
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -527,15 +527,26 @@ public class ReflectUtils {
|
||||
c = lookup.defineClass(b);
|
||||
}
|
||||
catch (LinkageError | IllegalAccessException ex) {
|
||||
throw new CodeGenerationException(ex) {
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "ClassLoader mismatch for [" + contextClass.getName() +
|
||||
"]: JVM should be started with --add-opens=java.base/java.lang=ALL-UNNAMED " +
|
||||
"for ClassLoader.defineClass to be accessible on " + loader.getClass().getName() +
|
||||
"; consider co-locating the affected class in that target ClassLoader instead.";
|
||||
if (ex instanceof LinkageError) {
|
||||
// Could be a ClassLoader mismatch with the class pre-existing in a
|
||||
// parent ClassLoader -> try loadClass before giving up completely.
|
||||
try {
|
||||
c = contextClass.getClassLoader().loadClass(className);
|
||||
}
|
||||
};
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
}
|
||||
}
|
||||
if (c == null) {
|
||||
throw new CodeGenerationException(ex) {
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "ClassLoader mismatch for [" + contextClass.getName() +
|
||||
"]: JVM should be started with --add-opens=java.base/java.lang=ALL-UNNAMED " +
|
||||
"for ClassLoader.defineClass to be accessible on " + loader.getClass().getName() +
|
||||
"; consider co-locating the affected class in that target ClassLoader instead.";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new CodeGenerationException(ex);
|
||||
|
||||
@@ -309,12 +309,12 @@ public class ResolvableType implements Serializable {
|
||||
|
||||
// Deal with wildcard bounds
|
||||
WildcardBounds ourBounds = WildcardBounds.get(this);
|
||||
WildcardBounds typeBounds = WildcardBounds.get(other);
|
||||
WildcardBounds otherBounds = WildcardBounds.get(other);
|
||||
|
||||
// In the form X is assignable to <? extends Number>
|
||||
if (typeBounds != null) {
|
||||
return (ourBounds != null && ourBounds.isSameKind(typeBounds) &&
|
||||
ourBounds.isAssignableFrom(typeBounds.getBounds()));
|
||||
if (otherBounds != null) {
|
||||
return (ourBounds != null && ourBounds.isSameKind(otherBounds) &&
|
||||
ourBounds.isAssignableFrom(otherBounds.getBounds()));
|
||||
}
|
||||
|
||||
// In the form <? extends Number> is assignable to X...
|
||||
@@ -365,8 +365,8 @@ public class ResolvableType implements Serializable {
|
||||
if (checkGenerics) {
|
||||
// Recursively check each generic
|
||||
ResolvableType[] ourGenerics = getGenerics();
|
||||
ResolvableType[] typeGenerics = other.as(ourResolved).getGenerics();
|
||||
if (ourGenerics.length != typeGenerics.length) {
|
||||
ResolvableType[] otherGenerics = other.as(ourResolved).getGenerics();
|
||||
if (ourGenerics.length != otherGenerics.length) {
|
||||
return false;
|
||||
}
|
||||
if (ourGenerics.length > 0) {
|
||||
@@ -375,7 +375,7 @@ public class ResolvableType implements Serializable {
|
||||
}
|
||||
matchedBefore.put(this.type, other.type);
|
||||
for (int i = 0; i < ourGenerics.length; i++) {
|
||||
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore)) {
|
||||
if (!ourGenerics[i].isAssignableFrom(otherGenerics[i], true, matchedBefore)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -117,7 +117,7 @@ public final class SpringProperties {
|
||||
* @param key the property key
|
||||
*/
|
||||
public static void setFlag(String key) {
|
||||
localProperties.put(key, Boolean.TRUE.toString());
|
||||
localProperties.setProperty(key, Boolean.TRUE.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+26
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -206,6 +206,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
|
||||
|
||||
private static final Resource[] EMPTY_RESOURCE_ARRAY = {};
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);
|
||||
|
||||
/**
|
||||
@@ -248,6 +250,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private boolean useCaches = true;
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@code PathMatchingResourcePatternResolver} with a
|
||||
@@ -315,6 +319,21 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
return this.pathMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether this resolver should use jar caches. Default is {@code true}.
|
||||
* <p>Switch this flag to {@code false} in order to avoid jar caching at the
|
||||
* {@link JarURLConnection} level.
|
||||
* <p>Note that {@link JarURLConnection#setDefaultUseCaches} can be turned off
|
||||
* independently. This resolver-level setting is designed to only enforce
|
||||
* {@code JarURLConnection#setUseCaches(false)} if necessary but otherwise
|
||||
* leaves the JVM-level default in place.
|
||||
* @since 6.1.19
|
||||
* @see JarURLConnection#setUseCaches
|
||||
*/
|
||||
public void setUseCaches(boolean useCaches) {
|
||||
this.useCaches = useCaches;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Resource getResource(String location) {
|
||||
@@ -338,7 +357,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
// all class path resources with the given name
|
||||
Collections.addAll(resources, findAllClassPathResources(locationPatternWithoutPrefix));
|
||||
}
|
||||
return resources.toArray(new Resource[0]);
|
||||
return resources.toArray(EMPTY_RESOURCE_ARRAY);
|
||||
}
|
||||
else {
|
||||
// Generally only look for a pattern after a prefix here,
|
||||
@@ -371,7 +390,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved class path location [" + path + "] to resources " + result);
|
||||
}
|
||||
return result.toArray(new Resource[0]);
|
||||
return result.toArray(EMPTY_RESOURCE_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -607,7 +626,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
|
||||
}
|
||||
return result.toArray(new Resource[0]);
|
||||
return result.toArray(EMPTY_RESOURCE_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -695,6 +714,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
|
||||
if (con instanceof JarURLConnection jarCon) {
|
||||
// Should usually be the case for traditional JAR files.
|
||||
if (!this.useCaches) {
|
||||
jarCon.setUseCaches(false);
|
||||
}
|
||||
jarFile = jarCon.getJarFile();
|
||||
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
|
||||
JarEntry jarEntry = jarCon.getJarEntry();
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -51,10 +51,11 @@ public class FlightRecorderApplicationStartup implements ApplicationStartup {
|
||||
|
||||
@Override
|
||||
public StartupStep start(String name) {
|
||||
Long parentId = this.currentSteps.getFirst();
|
||||
long sequenceId = this.currentSequenceId.incrementAndGet();
|
||||
this.currentSteps.offerFirst(sequenceId);
|
||||
return new FlightRecorderStartupStep(sequenceId, name,
|
||||
this.currentSteps.getFirst(), committedStep -> this.currentSteps.removeFirstOccurrence(sequenceId));
|
||||
parentId, committedStep -> this.currentSteps.removeFirstOccurrence(sequenceId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -69,7 +69,13 @@ public class TaskRejectedException extends RejectedExecutionException {
|
||||
|
||||
private static String executorDescription(Executor executor) {
|
||||
if (executor instanceof ExecutorService executorService) {
|
||||
return "ExecutorService in " + (executorService.isShutdown() ? "shutdown" : "active") + " state";
|
||||
try {
|
||||
return "ExecutorService in " + (executorService.isShutdown() ? "shutdown" : "active") + " state";
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// UnsupportedOperationException/IllegalStateException from ManagedExecutorService.isShutdown()
|
||||
// Falling back to toString() below.
|
||||
}
|
||||
}
|
||||
return executor.toString();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -69,7 +69,7 @@ public class SpringObjenesis implements Objenesis {
|
||||
this.strategy = (strategy != null ? strategy : new StdInstantiatorStrategy());
|
||||
|
||||
// Evaluate the "spring.objenesis.ignore" property upfront...
|
||||
if (SpringProperties.getFlag(SpringObjenesis.IGNORE_OBJENESIS_PROPERTY_NAME)) {
|
||||
if (SpringProperties.getFlag(IGNORE_OBJENESIS_PROPERTY_NAME)) {
|
||||
this.worthTrying = Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -169,7 +169,7 @@ class ResolvableTypeTests {
|
||||
|
||||
@Test
|
||||
void forInstanceProviderNull() {
|
||||
ResolvableType type = ResolvableType.forInstance(new MyGenericInterfaceType<String>(null));
|
||||
ResolvableType type = ResolvableType.forInstance(new MyGenericInterfaceType<>(null));
|
||||
assertThat(type.getType()).isEqualTo(MyGenericInterfaceType.class);
|
||||
assertThat(type.resolve()).isEqualTo(MyGenericInterfaceType.class);
|
||||
}
|
||||
@@ -1177,6 +1177,20 @@ class ResolvableTypeTests {
|
||||
assertThatResolvableType(complex4).isNotAssignableFrom(complex3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAssignableFromForUnresolvedWildcards() {
|
||||
ResolvableType wildcard = ResolvableType.forInstance(new Wildcard<>());
|
||||
ResolvableType wildcardFixed = ResolvableType.forInstance(new WildcardFixed());
|
||||
ResolvableType wildcardConcrete = ResolvableType.forClassWithGenerics(Wildcard.class, Number.class);
|
||||
|
||||
assertThat(wildcard.isAssignableFrom(wildcardFixed)).isTrue();
|
||||
assertThat(wildcard.isAssignableFrom(wildcardConcrete)).isTrue();
|
||||
assertThat(wildcardFixed.isAssignableFrom(wildcard)).isFalse();
|
||||
assertThat(wildcardFixed.isAssignableFrom(wildcardConcrete)).isFalse();
|
||||
assertThat(wildcardConcrete.isAssignableFrom(wildcard)).isTrue();
|
||||
assertThat(wildcardConcrete.isAssignableFrom(wildcardFixed)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identifyTypeVariable() throws Exception {
|
||||
Method method = ClassArguments.class.getMethod("typedArgumentFirst", Class.class, Class.class, Class.class);
|
||||
@@ -1574,7 +1588,6 @@ class ResolvableTypeTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class MySimpleInterfaceType implements MyInterfaceType<String> {
|
||||
}
|
||||
|
||||
@@ -1584,7 +1597,6 @@ class ResolvableTypeTests {
|
||||
public abstract class ExtendsMySimpleInterfaceTypeWithImplementsRaw extends MySimpleInterfaceTypeWithImplementsRaw {
|
||||
}
|
||||
|
||||
|
||||
public class MyCollectionInterfaceType implements MyInterfaceType<Collection<String>> {
|
||||
}
|
||||
|
||||
@@ -1592,20 +1604,17 @@ class ResolvableTypeTests {
|
||||
public abstract class MySuperclassType<T> {
|
||||
}
|
||||
|
||||
|
||||
public class MySimpleSuperclassType extends MySuperclassType<String> {
|
||||
}
|
||||
|
||||
|
||||
public class MyCollectionSuperclassType extends MySuperclassType<Collection<String>> {
|
||||
}
|
||||
|
||||
|
||||
interface Wildcard<T extends Number> extends List<T> {
|
||||
public class Wildcard<T extends Number> {
|
||||
}
|
||||
|
||||
|
||||
interface RawExtendsWildcard extends Wildcard {
|
||||
public class WildcardFixed extends Wildcard<Integer> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -108,9 +108,11 @@ class PathMatchingResourcePatternResolverTests {
|
||||
Path rootDir = Paths.get("src/test/resources/custom%23root").toAbsolutePath();
|
||||
URL root = new URL("file:" + rootDir + "/");
|
||||
resolver = new PathMatchingResourcePatternResolver(new DefaultResourceLoader(new URLClassLoader(new URL[] {root})));
|
||||
resolver.setUseCaches(false);
|
||||
assertExactFilenames("classpath*:scanned/*.txt", "resource#test1.txt", "resource#test2.txt");
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class WithHashtagsInTheirFilenames {
|
||||
|
||||
@@ -332,7 +334,7 @@ class PathMatchingResourcePatternResolverTests {
|
||||
// Tests fail if we use resource.getURL().getPath(). They would also fail on macOS when
|
||||
// using resource.getURI().getPath() if the resource paths are not Unicode normalized.
|
||||
//
|
||||
// On the JVM, all tests should pass when using resouce.getFile().getPath(); however,
|
||||
// On the JVM, all tests should pass when using resource.getFile().getPath(); however,
|
||||
// we use FileSystemResource#getPath since this test class is sometimes run within a
|
||||
// GraalVM native image which cannot support Path#toFile.
|
||||
//
|
||||
|
||||
+18
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -156,6 +156,23 @@ class PropertyPlaceholderHelperTests {
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("exactMatchPlaceholders")
|
||||
void placeholdersWithExactMatchAreConsidered(String text, String expected) {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("prefix://my-service", "example-service");
|
||||
properties.setProperty("px", "prefix");
|
||||
properties.setProperty("p1", "${prefix://my-service}");
|
||||
assertThat(this.helper.replacePlaceholders(text, properties)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> exactMatchPlaceholders() {
|
||||
return Stream.of(
|
||||
Arguments.of("${prefix://my-service}", "example-service"),
|
||||
Arguments.of("${p1}", "example-service")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PlaceholderResolver mockPlaceholderResolver(String... pairs) {
|
||||
|
||||
+3
-2
@@ -226,8 +226,9 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);
|
||||
|
||||
if (isSuspectedVarargs) {
|
||||
if (declaredParamCount == 1) {
|
||||
// We only repackage the varargs if it is the ONLY argument -- for example,
|
||||
if (declaredParamCount == 1 && !methodHandle.isVarargsCollector()) {
|
||||
// We only repackage the arguments if the MethodHandle accepts a single
|
||||
// argument AND the MethodHandle is not a "varargs collector" -- for example,
|
||||
// when we are dealing with a bound MethodHandle.
|
||||
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
|
||||
methodHandle.type().parameterArray(), functionArgs);
|
||||
|
||||
+2
@@ -36,6 +36,8 @@ import static org.assertj.core.api.Assertions.assertThatException;
|
||||
* Tests invocation of constructors.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @see MethodInvocationTests
|
||||
* @see VariableAndFunctionTests
|
||||
*/
|
||||
class ConstructorInvocationTests extends AbstractExpressionTests {
|
||||
|
||||
|
||||
+2
@@ -46,6 +46,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Andy Clement
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @see ConstructorInvocationTests
|
||||
* @see VariableAndFunctionTests
|
||||
*/
|
||||
class MethodInvocationTests extends AbstractExpressionTests {
|
||||
|
||||
|
||||
+2
-1
@@ -597,7 +597,8 @@ class SpelDocumentationTests extends AbstractExpressionTests {
|
||||
MethodHandle methodHandle = MethodHandles.lookup().findVirtual(String.class, "formatted",
|
||||
MethodType.methodType(String.class, Object[].class))
|
||||
.bindTo(template)
|
||||
.bindTo(varargs); // here we have to provide arguments in a single array binding
|
||||
// Here we have to provide the arguments in a single array binding:
|
||||
.bindTo(varargs);
|
||||
context.registerFunction("message", methodHandle);
|
||||
|
||||
String message = parser.parseExpression("#message()").getValue(context, String.class);
|
||||
|
||||
+6
-1
@@ -108,11 +108,16 @@ class TestScenarioCreator {
|
||||
"formatObjectVarargs", MethodType.methodType(String.class, String.class, Object[].class));
|
||||
testContext.registerFunction("formatObjectVarargs", formatObjectVarargs);
|
||||
|
||||
// #formatObjectVarargs(format, args...)
|
||||
// #formatPrimitiveVarargs(format, args...)
|
||||
MethodHandle formatPrimitiveVarargs = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
|
||||
"formatPrimitiveVarargs", MethodType.methodType(String.class, String.class, int[].class));
|
||||
testContext.registerFunction("formatPrimitiveVarargs", formatPrimitiveVarargs);
|
||||
|
||||
// #varargsFunctionHandle(args...)
|
||||
MethodHandle varargsFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
|
||||
"varargsFunction", MethodType.methodType(String.class, String[].class));
|
||||
testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle);
|
||||
|
||||
// #add(int, int)
|
||||
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
|
||||
"add", MethodType.methodType(int.class, int.class, int.class));
|
||||
|
||||
+25
@@ -32,6 +32,8 @@ import static org.springframework.expression.spel.SpelMessage.INCORRECT_NUMBER_O
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @see ConstructorInvocationTests
|
||||
* @see MethodInvocationTests
|
||||
*/
|
||||
class VariableAndFunctionTests extends AbstractExpressionTests {
|
||||
|
||||
@@ -77,6 +79,8 @@ class VariableAndFunctionTests extends AbstractExpressionTests {
|
||||
|
||||
@Test
|
||||
void functionWithVarargs() {
|
||||
// static String varargsFunction(String... strings) -> Arrays.toString(strings)
|
||||
|
||||
evaluate("#varargsFunction()", "[]", String.class);
|
||||
evaluate("#varargsFunction(new String[0])", "[]", String.class);
|
||||
evaluate("#varargsFunction('a')", "[a]", String.class);
|
||||
@@ -239,6 +243,27 @@ class VariableAndFunctionTests extends AbstractExpressionTests {
|
||||
evaluate("#formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
|
||||
}
|
||||
|
||||
@Test // gh-34109
|
||||
void functionViaMethodHandleForStaticMethodThatAcceptsOnlyVarargs() {
|
||||
// #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings)
|
||||
|
||||
evaluate("#varargsFunctionHandle()", "[]", String.class);
|
||||
evaluate("#varargsFunctionHandle(new String[0])", "[]", String.class);
|
||||
evaluate("#varargsFunctionHandle('a')", "[a]", String.class);
|
||||
evaluate("#varargsFunctionHandle('a','b','c')", "[a, b, c]", String.class);
|
||||
evaluate("#varargsFunctionHandle(new String[]{'a','b','c'})", "[a, b, c]", String.class);
|
||||
// Conversion from int to String
|
||||
evaluate("#varargsFunctionHandle(25)", "[25]", String.class);
|
||||
evaluate("#varargsFunctionHandle('b',25)", "[b, 25]", String.class);
|
||||
evaluate("#varargsFunctionHandle(new int[]{1, 2, 3})", "[1, 2, 3]", String.class);
|
||||
// Strings that contain a comma
|
||||
evaluate("#varargsFunctionHandle('a,b')", "[a,b]", String.class);
|
||||
evaluate("#varargsFunctionHandle('a', 'x,y', 'd')", "[a, x,y, d]", String.class);
|
||||
// null values
|
||||
evaluate("#varargsFunctionHandle(null)", "[null]", String.class);
|
||||
evaluate("#varargsFunctionHandle('a',null,'b')", "[a, null, b]", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionMethodMustBeStatic() throws Exception {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -85,12 +85,13 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
|
||||
* Set a {@link ConversionService} for converting a fetched value.
|
||||
* <p>Default is the {@link DefaultConversionService}.
|
||||
* @since 5.0.4
|
||||
* @see DefaultConversionService#getSharedInstance
|
||||
* @see DefaultConversionService#getSharedInstance()
|
||||
*/
|
||||
public void setConversionService(@Nullable ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a value for the single column in the current row.
|
||||
* <p>Validates that there is only one column selected,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -439,7 +439,11 @@ public abstract class DataSourceUtils {
|
||||
public static Connection getTargetConnection(Connection con) {
|
||||
Connection conToUse = con;
|
||||
while (conToUse instanceof ConnectionProxy connectionProxy) {
|
||||
conToUse = connectionProxy.getTargetConnection();
|
||||
Connection targetCon = connectionProxy.getTargetConnection();
|
||||
if (targetCon == conToUse) {
|
||||
break;
|
||||
}
|
||||
conToUse = targetCon;
|
||||
}
|
||||
return conToUse;
|
||||
}
|
||||
|
||||
+13
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -210,13 +210,23 @@ public class TransactionAwareDataSourceProxy extends DelegatingDataSource {
|
||||
sb.append('[').append(this.target).append(']');
|
||||
}
|
||||
else {
|
||||
sb.append(" from DataSource [").append(this.targetDataSource).append(']');
|
||||
sb.append("from DataSource [").append(this.targetDataSource).append(']');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
case "close" -> {
|
||||
// Handle close method: only close if not within a transaction.
|
||||
DataSourceUtils.doReleaseConnection(this.target, this.targetDataSource);
|
||||
if (this.target != null) {
|
||||
ConnectionHolder conHolder = (ConnectionHolder)
|
||||
TransactionSynchronizationManager.getResource(this.targetDataSource);
|
||||
if (conHolder != null && conHolder.hasConnection() && conHolder.getConnection() == this.target) {
|
||||
// It's the transactional Connection: Don't close it.
|
||||
conHolder.released();
|
||||
}
|
||||
else {
|
||||
DataSourceUtils.doCloseConnection(this.target, this.targetDataSource);
|
||||
}
|
||||
}
|
||||
this.closed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
+43
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -72,7 +72,7 @@ public class DataSourceTransactionManagerTests {
|
||||
|
||||
protected DataSource ds = mock();
|
||||
|
||||
protected Connection con = mock();
|
||||
protected ConnectionProxy con = mock();
|
||||
|
||||
protected DataSourceTransactionManager tm;
|
||||
|
||||
@@ -81,6 +81,7 @@ public class DataSourceTransactionManagerTests {
|
||||
void setup() throws Exception {
|
||||
tm = createTransactionManager(ds);
|
||||
given(ds.getConnection()).willReturn(con);
|
||||
given(con.getTargetConnection()).willThrow(new UnsupportedOperationException());
|
||||
}
|
||||
|
||||
protected DataSourceTransactionManager createTransactionManager(DataSource ds) {
|
||||
@@ -1074,9 +1075,9 @@ public class DataSourceTransactionManagerTests {
|
||||
Connection tCon = dsProxy.getConnection();
|
||||
tCon.getWarnings();
|
||||
tCon.clearWarnings();
|
||||
assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con);
|
||||
assertThat(((ConnectionProxy) tCon).getTargetConnection()).isEqualTo(con);
|
||||
// should be ignored
|
||||
dsProxy.getConnection().close();
|
||||
tCon.close();
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new UncategorizedSQLException("", "", ex);
|
||||
@@ -1110,9 +1111,9 @@ public class DataSourceTransactionManagerTests {
|
||||
Connection tCon = dsProxy.getConnection();
|
||||
assertThatExceptionOfType(SQLException.class).isThrownBy(tCon::getWarnings);
|
||||
tCon.clearWarnings();
|
||||
assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con);
|
||||
assertThat(((ConnectionProxy) tCon).getTargetConnection()).isEqualTo(con);
|
||||
// should be ignored
|
||||
dsProxy.getConnection().close();
|
||||
tCon.close();
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new UncategorizedSQLException("", "", ex);
|
||||
@@ -1128,6 +1129,42 @@ public class DataSourceTransactionManagerTests {
|
||||
verify(con).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTransactionAwareDataSourceProxyWithEarlyConnection() throws Exception {
|
||||
given(ds.getConnection()).willReturn(mock(Connection.class), con);
|
||||
given(con.getAutoCommit()).willReturn(true);
|
||||
given(con.getWarnings()).willThrow(new SQLException());
|
||||
|
||||
TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds);
|
||||
dsProxy.setLazyTransactionalConnections(false);
|
||||
Connection tCon = dsProxy.getConnection();
|
||||
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
// something transactional
|
||||
assertThat(DataSourceUtils.getConnection(ds)).isEqualTo(con);
|
||||
try {
|
||||
// should close the early Connection obtained before the transaction
|
||||
tCon.close();
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new UncategorizedSQLException("", "", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
|
||||
InOrder ordered = inOrder(con);
|
||||
ordered.verify(con).setAutoCommit(false);
|
||||
ordered.verify(con).commit();
|
||||
ordered.verify(con).setAutoCommit(true);
|
||||
verify(con).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTransactionAwareDataSourceProxyWithSuspension() throws Exception {
|
||||
given(con.getAutoCommit()).willReturn(true);
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -209,6 +209,7 @@ public abstract class AbstractJmsListenerContainerFactory<C extends AbstractMess
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public C createListenerContainer(JmsListenerEndpoint endpoint) {
|
||||
C instance = createContainerInstance();
|
||||
|
||||
+7
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -102,7 +102,7 @@ import org.springframework.util.ErrorHandler;
|
||||
* (i.e. after your business logic executed but before the JMS part got committed),
|
||||
* so duplicate message detection is just there to cover a corner case.
|
||||
* <li>Or wrap your <i>entire processing with an XA transaction</i>, covering the
|
||||
* reception of the JMS message as well as the execution of the business logic in
|
||||
* receipt of the JMS message as well as the execution of the business logic in
|
||||
* your message listener (including database operations etc). This is only
|
||||
* supported by {@link DefaultMessageListenerContainer}, through specifying
|
||||
* an external "transactionManager" (typically a
|
||||
@@ -152,7 +152,8 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
|
||||
implements MessageListenerContainer {
|
||||
|
||||
private static final boolean micrometerJakartaPresent = ClassUtils.isPresent(
|
||||
"io.micrometer.jakarta9.instrument.jms.JmsInstrumentation", AbstractMessageListenerContainer.class.getClassLoader());
|
||||
"io.micrometer.jakarta9.instrument.jms.JmsInstrumentation",
|
||||
AbstractMessageListenerContainer.class.getClassLoader());
|
||||
|
||||
@Nullable
|
||||
private volatile Object destination;
|
||||
@@ -170,14 +171,14 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
|
||||
@Nullable
|
||||
private String subscriptionName;
|
||||
|
||||
private boolean pubSubNoLocal = false;
|
||||
|
||||
@Nullable
|
||||
private Boolean replyPubSubDomain;
|
||||
|
||||
@Nullable
|
||||
private QosSettings replyQosSettings;
|
||||
|
||||
private boolean pubSubNoLocal = false;
|
||||
|
||||
@Nullable
|
||||
private MessageConverter messageConverter;
|
||||
|
||||
@@ -500,12 +501,7 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
|
||||
*/
|
||||
@Override
|
||||
public boolean isReplyPubSubDomain() {
|
||||
if (this.replyPubSubDomain != null) {
|
||||
return this.replyPubSubDomain;
|
||||
}
|
||||
else {
|
||||
return isPubSubDomain();
|
||||
}
|
||||
return (this.replyPubSubDomain != null ? this.replyPubSubDomain : isPubSubDomain());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,9 +22,7 @@ import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* Convenient base class for {@link AsyncHandlerMethodReturnValueHandler}
|
||||
* implementations that support only asynchronous (Future-like) return values
|
||||
* and merely serve as adapters of such types to Spring's
|
||||
* {@link org.springframework.util.concurrent.ListenableFuture ListenableFuture}.
|
||||
* implementations that support only asynchronous (Future-like) return values.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -572,7 +572,7 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||
if (returnValue != null && this.returnValueHandlers.isAsyncReturnValue(returnValue, returnType)) {
|
||||
CompletableFuture<?> future = this.returnValueHandlers.toCompletableFuture(returnValue, returnType);
|
||||
if (future != null) {
|
||||
future.whenComplete(new ReturnValueListenableFutureCallback(invocable, message));
|
||||
future.whenComplete(new ReturnValueCallback(invocable, message));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -703,13 +703,13 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||
}
|
||||
|
||||
|
||||
private class ReturnValueListenableFutureCallback implements BiConsumer<Object, Throwable> {
|
||||
private class ReturnValueCallback implements BiConsumer<Object, Throwable> {
|
||||
|
||||
private final InvocableHandlerMethod handlerMethod;
|
||||
|
||||
private final Message<?> message;
|
||||
|
||||
public ReturnValueListenableFutureCallback(InvocableHandlerMethod handlerMethod, Message<?> message) {
|
||||
public ReturnValueCallback(InvocableHandlerMethod handlerMethod, Message<?> message) {
|
||||
this.handlerMethod = handlerMethod;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -24,8 +24,6 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* An extension of {@link HandlerMethodReturnValueHandler} for handling async,
|
||||
* Future-like return value types that support success and error callbacks.
|
||||
* Essentially anything that can be adapted to a
|
||||
* {@link org.springframework.util.concurrent.ListenableFuture ListenableFuture}.
|
||||
*
|
||||
* <p>Implementations should consider extending the convenient base class
|
||||
* {@link AbstractAsyncReturnValueHandler}.
|
||||
@@ -71,6 +69,7 @@ public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodRetur
|
||||
@Nullable
|
||||
default org.springframework.util.concurrent.ListenableFuture<?> toListenableFuture(
|
||||
Object returnValue, MethodParameter returnType) {
|
||||
|
||||
CompletableFuture<?> result = toCompletableFuture(returnValue, returnType);
|
||||
return (result != null ?
|
||||
new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>(result) :
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -48,8 +48,8 @@ public class DestinationVariableArgumentResolver implements RSocketServiceArgume
|
||||
collection.forEach(requestValues::addRouteVariable);
|
||||
return true;
|
||||
}
|
||||
else if (argument.getClass().isArray()) {
|
||||
for (Object variable : (Object[]) argument) {
|
||||
else if (argument instanceof Object[] arguments) {
|
||||
for (Object variable : arguments) {
|
||||
requestValues.addRouteVariable(variable);
|
||||
}
|
||||
return true;
|
||||
|
||||
+5
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,6 @@ import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -280,12 +279,10 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
|
||||
return this.messagingTemplate;
|
||||
}
|
||||
|
||||
public void send(UserDestinationResult destinationResult, Message<?> message) throws MessagingException {
|
||||
Set<String> sessionIds = destinationResult.getSessionIds();
|
||||
Iterator<String> itr = (sessionIds != null ? sessionIds.iterator() : null);
|
||||
|
||||
for (String target : destinationResult.getTargetDestinations()) {
|
||||
String sessionId = (itr != null ? itr.next() : null);
|
||||
public void send(UserDestinationResult result, Message<?> message) throws MessagingException {
|
||||
Iterator<String> itr = result.getSessionIds().iterator();
|
||||
for (String target : result.getTargetDestinations()) {
|
||||
String sessionId = (itr.hasNext() ? itr.next() : null);
|
||||
getTemplateToUse(sessionId).send(target, message);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -44,7 +44,11 @@ public class UserDestinationResult {
|
||||
private final Set<String> sessionIds;
|
||||
|
||||
|
||||
public UserDestinationResult(String sourceDestination, Set<String> targetDestinations,
|
||||
/**
|
||||
* Main constructor.
|
||||
*/
|
||||
public UserDestinationResult(
|
||||
String sourceDestination, Set<String> targetDestinations,
|
||||
String subscribeDestination, @Nullable String user) {
|
||||
|
||||
this(sourceDestination, targetDestinations, subscribeDestination, user, null);
|
||||
@@ -114,7 +118,6 @@ public class UserDestinationResult {
|
||||
/**
|
||||
* Return the session id for the targetDestination.
|
||||
*/
|
||||
@Nullable
|
||||
public Set<String> getSessionIds() {
|
||||
return this.sessionIds;
|
||||
}
|
||||
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.messaging.simp.user;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -98,6 +99,26 @@ class UserDestinationMessageHandlerTests {
|
||||
assertThat(accessor.getFirstNativeHeader(ORIGINAL_DESTINATION)).isEqualTo("/user/queue/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void handleMessageWithoutSessionIds() {
|
||||
UserDestinationResolver resolver = mock();
|
||||
Message message = createWith(SimpMessageType.MESSAGE, "joe", null, "/user/joe/queue/foo");
|
||||
UserDestinationResult result = new UserDestinationResult("/queue/foo-user123", Set.of("/queue/foo-user123"), "/user/queue/foo", "joe");
|
||||
given(resolver.resolveDestination(message)).willReturn(result);
|
||||
|
||||
given(this.brokerChannel.send(Mockito.any(Message.class))).willReturn(true);
|
||||
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(new StubMessageChannel(), this.brokerChannel, resolver);
|
||||
handler.handleMessage(message);
|
||||
|
||||
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
|
||||
Mockito.verify(this.brokerChannel).send(captor.capture());
|
||||
|
||||
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(captor.getValue());
|
||||
assertThat(accessor.getDestination()).isEqualTo("/queue/foo-user123");
|
||||
assertThat(accessor.getFirstNativeHeader(ORIGINAL_DESTINATION)).isEqualTo("/user/queue/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void handleMessageWithoutActiveSession() {
|
||||
|
||||
+1
-1
@@ -185,7 +185,7 @@ public abstract class SharedEntityManagerCreator {
|
||||
@SuppressWarnings("serial")
|
||||
private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
private static final Log logger = LogFactory.getLog(SharedEntityManagerInvocationHandler.class);
|
||||
|
||||
private final EntityManagerFactory targetFactory;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.orm.jpa;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityTransaction;
|
||||
import jakarta.persistence.OptimisticLockException;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
@@ -33,33 +34,37 @@ import static org.mockito.Mockito.mock;
|
||||
/**
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class DefaultJpaDialectTests {
|
||||
|
||||
private JpaDialect dialect = new DefaultJpaDialect();
|
||||
private final JpaDialect dialect = new DefaultJpaDialect();
|
||||
|
||||
@Test
|
||||
void testDefaultTransactionDefinition() {
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
|
||||
assertThatExceptionOfType(TransactionException.class).isThrownBy(() ->
|
||||
dialect.beginTransaction(null, definition));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultBeginTransaction() throws Exception {
|
||||
TransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
EntityManager entityManager = mock();
|
||||
EntityTransaction entityTx = mock();
|
||||
|
||||
given(entityManager.getTransaction()).willReturn(entityTx);
|
||||
|
||||
dialect.beginTransaction(entityManager, definition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTranslateException() {
|
||||
OptimisticLockException ex = new OptimisticLockException();
|
||||
assertThat(dialect.translateExceptionIfPossible(ex).getCause()).isEqualTo(EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex).getCause());
|
||||
void testCustomIsolationLevel() {
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
|
||||
|
||||
assertThatExceptionOfType(TransactionException.class).isThrownBy(() ->
|
||||
dialect.beginTransaction(null, definition));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTranslateException() {
|
||||
PersistenceException ex = new OptimisticLockException();
|
||||
assertThat(dialect.translateExceptionIfPossible(ex))
|
||||
.isInstanceOf(JpaOptimisticLockingFailureException.class).hasCause(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -23,6 +23,7 @@ import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.IsolationLevel;
|
||||
import io.r2dbc.spi.R2dbcBadGrammarException;
|
||||
import io.r2dbc.spi.R2dbcTransientResourceException;
|
||||
import io.r2dbc.spi.Statement;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -32,6 +33,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.dao.TransientDataAccessResourceException;
|
||||
import org.springframework.r2dbc.BadSqlGrammarException;
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
import org.springframework.transaction.IllegalTransactionStateException;
|
||||
@@ -315,6 +317,31 @@ class R2dbcTransactionManagerTests {
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCommitAndRollbackFails() {
|
||||
when(connectionMock.isAutoCommit()).thenReturn(false);
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.defer(() ->
|
||||
Mono.error(new R2dbcBadGrammarException("Commit should fail"))));
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.defer(() ->
|
||||
Mono.error(new R2dbcTransientResourceException("Rollback should also fail"))));
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock)
|
||||
.doOnNext(connection -> connection.createStatement("foo")).then()
|
||||
.as(operator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.verifyError(TransientDataAccessResourceException.class);
|
||||
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction(any(io.r2dbc.spi.TransactionDefinition.class));
|
||||
verify(connectionMock).createStatement("foo");
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTransactionSetRollbackOnly() {
|
||||
when(connectionMock.isAutoCommit()).thenReturn(false);
|
||||
|
||||
+18
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -671,7 +671,12 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, @Nullable String value) {
|
||||
setHeaderValue(name, value);
|
||||
if (value == null) {
|
||||
this.headers.remove(name);
|
||||
}
|
||||
else {
|
||||
setHeaderValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -723,14 +728,17 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||
}
|
||||
else if (HttpHeaders.CONTENT_LANGUAGE.equalsIgnoreCase(name)) {
|
||||
String contentLanguages = value.toString();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.CONTENT_LANGUAGE, contentLanguages);
|
||||
Locale language = headers.getContentLanguage();
|
||||
setLocale(language != null ? language : Locale.getDefault());
|
||||
// Since setLocale() sets the Content-Language header to the given
|
||||
// single Locale, we have to explicitly set the Content-Language header
|
||||
// to the user-provided value.
|
||||
doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, contentLanguages, true);
|
||||
// only set the locale if we replace the header or if there was none before
|
||||
if (replaceHeader || !this.headers.containsKey(HttpHeaders.CONTENT_LANGUAGE)) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.CONTENT_LANGUAGE, contentLanguages);
|
||||
Locale language = headers.getContentLanguage();
|
||||
this.locale = language != null ? language : Locale.getDefault();
|
||||
doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, contentLanguages, replaceHeader);
|
||||
}
|
||||
else {
|
||||
doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, contentLanguages, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (HttpHeaders.SET_COOKIE.equalsIgnoreCase(name)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -389,8 +389,8 @@ public class TestContextManager {
|
||||
* have executed, the first caught exception will be rethrown with any
|
||||
* subsequent exceptions {@linkplain Throwable#addSuppressed suppressed} in
|
||||
* the first exception.
|
||||
* <p>Note that registered listeners will be executed in the opposite
|
||||
* order in which they were registered.
|
||||
* <p>Note that listeners will be executed in the opposite order in which they
|
||||
* were registered.
|
||||
* @param testInstance the current test instance
|
||||
* @param testMethod the test method which has just been executed on the
|
||||
* test instance
|
||||
@@ -459,7 +459,8 @@ public class TestContextManager {
|
||||
* have executed, the first caught exception will be rethrown with any
|
||||
* subsequent exceptions {@linkplain Throwable#addSuppressed suppressed} in
|
||||
* the first exception.
|
||||
* <p>Note that registered listeners will be executed in the opposite
|
||||
* <p>Note that listeners will be executed in the opposite order in which they
|
||||
* were registered.
|
||||
* @param testInstance the current test instance
|
||||
* @param testMethod the test method which has just been executed on the
|
||||
* test instance
|
||||
@@ -517,7 +518,8 @@ public class TestContextManager {
|
||||
* have executed, the first caught exception will be rethrown with any
|
||||
* subsequent exceptions {@linkplain Throwable#addSuppressed suppressed} in
|
||||
* the first exception.
|
||||
* <p>Note that registered listeners will be executed in the opposite
|
||||
* <p>Note that listeners will be executed in the opposite order in which they
|
||||
* were registered.
|
||||
* @throws Exception if a registered TestExecutionListener throws an exception
|
||||
* @since 3.0
|
||||
* @see #getTestExecutionListeners()
|
||||
|
||||
+3
-3
@@ -182,10 +182,10 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||
@Override
|
||||
public void processAheadOfTime(RuntimeHints runtimeHints, Class<?> testClass, ClassLoader classLoader) {
|
||||
getSqlAnnotationsFor(testClass).forEach(sql ->
|
||||
registerClasspathResources(getScripts(sql, testClass, null, true), runtimeHints, classLoader));
|
||||
registerClasspathResources(getScripts(sql, testClass, null, true), runtimeHints, classLoader));
|
||||
getSqlMethods(testClass).forEach(testMethod ->
|
||||
getSqlAnnotationsFor(testMethod).forEach(sql ->
|
||||
registerClasspathResources(getScripts(sql, testClass, testMethod, false), runtimeHints, classLoader)));
|
||||
getSqlAnnotationsFor(testMethod).forEach(sql ->
|
||||
registerClasspathResources(getScripts(sql, testClass, testMethod, false), runtimeHints, classLoader)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+21
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -87,6 +87,18 @@ class MockHttpServletResponseTests {
|
||||
assertThat(response.containsHeader(headerName)).isFalse();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
CONTENT_TYPE,
|
||||
CONTENT_LANGUAGE,
|
||||
"X-Test-Header"
|
||||
})
|
||||
void removeHeaderIfNullValue(String headerName) {
|
||||
response.addHeader(headerName, "test");
|
||||
response.setHeader(headerName, null);
|
||||
assertThat(response.containsHeader(headerName)).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-26493
|
||||
void setLocaleWithNullValue() {
|
||||
assertThat(response.getLocale()).isEqualTo(Locale.getDefault());
|
||||
@@ -619,4 +631,12 @@ class MockHttpServletResponseTests {
|
||||
assertThat(contentTypeHeader).isEqualTo("text/plain");
|
||||
}
|
||||
|
||||
@Test // gh-34488
|
||||
void shouldAddMultipleContentLanguage() {
|
||||
response.addHeader("Content-Language", "en");
|
||||
response.addHeader("Content-Language", "fr");
|
||||
assertThat(response.getHeaders("Content-Language")).contains("en", "fr");
|
||||
assertThat(response.getLocale()).isEqualTo(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -494,21 +494,17 @@ public abstract class AbstractReactiveTransactionManager
|
||||
}));
|
||||
}
|
||||
else if (ErrorPredicates.RUNTIME_OR_ERROR.test(ex)) {
|
||||
Mono<Void> mono;
|
||||
Mono<Void> mono = Mono.empty();
|
||||
if (!beforeCompletionInvoked.get()) {
|
||||
mono = triggerBeforeCompletion(synchronizationManager, status);
|
||||
}
|
||||
else {
|
||||
mono = Mono.empty();
|
||||
}
|
||||
result = mono.then(doRollbackOnCommitException(synchronizationManager, status, ex))
|
||||
.then(propagateException);
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.then(Mono.defer(() -> triggerAfterCommit(synchronizationManager, status).onErrorResume(ex ->
|
||||
triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_COMMITTED).then(Mono.error(ex)))
|
||||
triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_COMMITTED).then(Mono.error(ex)))
|
||||
.then(triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_COMMITTED))
|
||||
.then(Mono.defer(() -> {
|
||||
if (status.isNewTransaction()) {
|
||||
@@ -518,8 +514,8 @@ public abstract class AbstractReactiveTransactionManager
|
||||
}))));
|
||||
|
||||
return commit
|
||||
.onErrorResume(ex -> cleanupAfterCompletion(synchronizationManager, status)
|
||||
.then(Mono.error(ex))).then(cleanupAfterCompletion(synchronizationManager, status));
|
||||
.onErrorResume(ex -> cleanupAfterCompletion(synchronizationManager, status).then(Mono.error(ex)))
|
||||
.then(cleanupAfterCompletion(synchronizationManager, status));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -571,8 +567,8 @@ public abstract class AbstractReactiveTransactionManager
|
||||
}
|
||||
return beforeCompletion;
|
||||
}
|
||||
})).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, ex -> triggerAfterCompletion(
|
||||
synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN)
|
||||
})).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, ex ->
|
||||
triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN)
|
||||
.then(Mono.defer(() -> {
|
||||
if (status.isNewTransaction()) {
|
||||
this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, ex));
|
||||
@@ -623,7 +619,7 @@ public abstract class AbstractReactiveTransactionManager
|
||||
return Mono.empty();
|
||||
}))
|
||||
.then(Mono.error(rbex));
|
||||
}).then(triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK))
|
||||
}).then(Mono.defer(() -> triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK)))
|
||||
.then(Mono.defer(() -> {
|
||||
this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));
|
||||
return Mono.empty();
|
||||
|
||||
@@ -853,7 +853,7 @@ public class MediaType extends MimeType implements Serializable {
|
||||
* <blockquote>audio/basic == text/html</blockquote>
|
||||
* <blockquote>audio/basic == audio/wave</blockquote>
|
||||
* @param mediaTypes the list of media types to be sorted
|
||||
* @deprecated As of 6.0, in favor of {@link MimeTypeUtils#sortBySpecificity(List)}
|
||||
* @deprecated as of 6.0, in favor of {@link MimeTypeUtils#sortBySpecificity(List)}
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static void sortBySpecificity(List<MediaType> mediaTypes) {
|
||||
@@ -882,7 +882,7 @@ public class MediaType extends MimeType implements Serializable {
|
||||
* </ol>
|
||||
* @param mediaTypes the list of media types to be sorted
|
||||
* @see #getQualityValue()
|
||||
* @deprecated As of 6.0, with no direct replacement
|
||||
* @deprecated as of 6.0, with no direct replacement
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static void sortByQualityValue(List<MediaType> mediaTypes) {
|
||||
@@ -895,9 +895,9 @@ public class MediaType extends MimeType implements Serializable {
|
||||
/**
|
||||
* Sorts the given list of {@code MediaType} objects by specificity as the
|
||||
* primary criteria and quality value the secondary.
|
||||
* @deprecated As of 6.0, in favor of {@link MimeTypeUtils#sortBySpecificity(List)}
|
||||
* @deprecated as of 6.0, in favor of {@link MimeTypeUtils#sortBySpecificity(List)}
|
||||
*/
|
||||
@Deprecated(since = "6.0")
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static void sortBySpecificityAndQuality(List<MediaType> mediaTypes) {
|
||||
Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
|
||||
if (mediaTypes.size() > 1) {
|
||||
@@ -908,7 +908,7 @@ public class MediaType extends MimeType implements Serializable {
|
||||
|
||||
/**
|
||||
* Comparator used by {@link #sortByQualityValue(List)}.
|
||||
* @deprecated As of 6.0, with no direct replacement
|
||||
* @deprecated as of 6.0, with no direct replacement
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> {
|
||||
@@ -948,7 +948,7 @@ public class MediaType extends MimeType implements Serializable {
|
||||
|
||||
/**
|
||||
* Comparator used by {@link #sortBySpecificity(List)}.
|
||||
* @deprecated As of 6.0, with no direct replacement
|
||||
* @deprecated as of 6.0, with no direct replacement
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
|
||||
+3
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -62,16 +62,9 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
|
||||
/**
|
||||
* Indicate whether this request factory should buffer the
|
||||
* {@linkplain ClientHttpRequest#getBody() request body} internally.
|
||||
* <p>Default is {@code true}. When sending large amounts of data via POST or PUT,
|
||||
* it is recommended to change this property to {@code false}, so as not to run
|
||||
* out of memory. This will result in a {@link ClientHttpRequest} that either
|
||||
* streams directly to the underlying {@link HttpURLConnection} (if the
|
||||
* {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length}
|
||||
* is known in advance), or that will use "Chunked transfer encoding"
|
||||
* (if the {@code Content-Length} is not known in advance).
|
||||
* @see #setChunkSize(int)
|
||||
* @see HttpURLConnection#setFixedLengthStreamingMode(int)
|
||||
* @deprecated since 6.1 requests are never buffered, as if this property is {@code false}
|
||||
* @deprecated since 6.1 requests are never buffered,
|
||||
* as if this property is {@code false}
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public void setBufferRequestBody(boolean bufferRequestBody) {
|
||||
@@ -80,11 +73,6 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
|
||||
/**
|
||||
* Set the number of bytes to write in each chunk when not buffering request
|
||||
* bodies locally.
|
||||
* <p>Note that this parameter is only used when
|
||||
* {@link #setBufferRequestBody(boolean) bufferRequestBody} is set to {@code false},
|
||||
* and the {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length}
|
||||
* is not known in advance.
|
||||
* @see #setBufferRequestBody(boolean)
|
||||
*/
|
||||
public void setChunkSize(int chunkSize) {
|
||||
this.chunkSize = chunkSize;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user