mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e354390837 |
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
|
||||
|
||||
## Continuous Integration Builds
|
||||
|
||||
CI builds are defined with [GitHub Actions workflows](.github/workflows).
|
||||
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.14.1/api/",
|
||||
// "https://junit.org/junit5/docs/5.14.0/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -676,7 +676,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
|
||||
.paramSource(new Actor("Leonor", "Watling"))
|
||||
.paramSource(new Actor("Leonor", "Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
|
||||
@@ -448,7 +448,7 @@ Java::
|
||||
----
|
||||
@GetMapping
|
||||
FragmentsRendering handle() {
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build();
|
||||
return FragmentsRendering.with("posts").fragment("comments").build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -458,7 +458,7 @@ Kotlin::
|
||||
----
|
||||
@GetMapping
|
||||
fun handle(): FragmentsRendering {
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build()
|
||||
return FragmentsRendering.with("posts").fragment("comments").build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -89,7 +89,40 @@ Kotlin::
|
||||
|
||||
You can map requests by using glob patterns and wildcards:
|
||||
|
||||
include::partial$web/uri-patterns.adoc[leveloffset=+1]
|
||||
[cols="2,3,5"]
|
||||
|===
|
||||
|Pattern |Description |Example
|
||||
|
||||
| `+?+`
|
||||
| Matches one character
|
||||
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
|
||||
|
||||
| `+*+`
|
||||
| Matches zero or more characters within a path segment
|
||||
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
|
||||
|
||||
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`
|
||||
|
||||
| `+**+`
|
||||
| Matches zero or more path segments until the end of the path
|
||||
| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
|
||||
|
||||
`+"/resources/**/file.png"+` is invalid as `+**+` is only allowed at the end of the path.
|
||||
|
||||
| `+{name}+`
|
||||
| Matches a path segment and captures it as a variable named "name"
|
||||
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
|
||||
`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment.
|
||||
|
||||
| `{name:[a-z]+}`
|
||||
| Matches the regexp `[a-z]+` as a path variable named "name"
|
||||
| `/projects/{project:[a-z]+}/versions` matches `/projects/spring/versions` but not `/projects/spring1/versions`
|
||||
|
||||
| `+{*path}+`
|
||||
| Matches zero or more path segments until the end of the path and captures it as a variable named "path"
|
||||
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
|
||||
|
||||
|===
|
||||
|
||||
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
|
||||
|
||||
|
||||
@@ -7,23 +7,19 @@ Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] for
|
||||
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
Validation may be applied at one of two levels:
|
||||
|
||||
1. Java Bean Validation is applied individually to an
|
||||
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
|
||||
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
|
||||
it is a command object rather than a container such as `Map` or `Collection`, it does not
|
||||
have `Errors` or `BindingResult` immediately after in the method signature, and does not
|
||||
otherwise require method validation (see next). `WebExchangeBindException` is the
|
||||
exception raised when validating a method parameter individually.
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
|
||||
resolvers validate a method argument individually if the method parameter is annotated
|
||||
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
|
||||
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
|
||||
discussed next). The exception raised in this case is `WebExchangeBindException`.
|
||||
|
||||
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
|
||||
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
|
||||
method for the return value, and it supersedes any validation that would be applied
|
||||
otherwise to a method parameter individually because method validation covers both
|
||||
method parameter constraints and nested constraints via `@Valid`.
|
||||
`HandlerMethodValidationException` is the exception raised validation is applied
|
||||
to the method.
|
||||
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
|
||||
directly on method parameters, or on the method (for the return value), then method
|
||||
validation must be applied, and that supersedes validation at the method argument level
|
||||
because method validation covers both method parameter constraints and nested constraints
|
||||
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
|
||||
|
||||
Applications must handle both `WebExchangeBindException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
|
||||
@@ -48,7 +48,7 @@ Java::
|
||||
----
|
||||
@GetMapping
|
||||
FragmentsRendering handle() {
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build();
|
||||
return FragmentsRendering.with("posts").fragment("comments").build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -58,7 +58,7 @@ Kotlin::
|
||||
----
|
||||
@GetMapping
|
||||
fun handle(): FragmentsRendering {
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build()
|
||||
return FragmentsRendering.with("posts").fragment("comments").build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
|
||||
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-filters[See equivalent in the Reactive stack]#
|
||||
|
||||
In the Servlet API, you can add a `jakarta.servlet.Filter` to apply interception-style logic
|
||||
before and after the rest of the processing chain of filters and the target `Servlet`.
|
||||
|
||||
The `spring-web` module has a number of built-in `Filter` implementations:
|
||||
The `spring-web` module provides some useful filters:
|
||||
|
||||
* xref:web/webmvc/filters.adoc#filters-http-put[Form Data]
|
||||
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
|
||||
@@ -14,19 +11,9 @@ The `spring-web` module has a number of built-in `Filter` implementations:
|
||||
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
|
||||
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
|
||||
|
||||
There are also base class implementations for use in Spring applications:
|
||||
|
||||
* `GenericFilterBean` -- base class for a `Filter` configured as a Spring bean;
|
||||
integrates with the Spring `ApplicationContext` lifecycle.
|
||||
* `OncePerRequestFilter` -- extension of `GenericFilterBean` that supports a single
|
||||
invocation at the start of a request, i.e. during the `REQUEST` dispatch phase, and
|
||||
ignoring further handling via `FORWARD` dispatches. The filter also provides control
|
||||
over whether the `Filter` gets involved in `ASYNC` and `ERROR` dispatches.
|
||||
|
||||
Servlet filters can be configured in `web.xml` or via Servlet annotations.
|
||||
In a Spring Boot application , you can
|
||||
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare Filter's as beans]
|
||||
and Boot will have them configured.
|
||||
Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations.
|
||||
If you are using Spring Boot, you can
|
||||
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare them as beans and configure them as part of your application].
|
||||
|
||||
|
||||
[[filters-http-put]]
|
||||
|
||||
@@ -422,7 +422,7 @@ reactive types from the controller method.
|
||||
Reactive return values are handled as follows:
|
||||
|
||||
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
|
||||
include `CompletionStage` (JDK), `Mono` (Reactor), and `Single` (RxJava).
|
||||
include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
|
||||
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
|
||||
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
|
||||
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
|
||||
|
||||
+15
-2
@@ -103,9 +103,22 @@ Spring WebFlux. It was enabled for use in Spring MVC from version 5.3 and is ena
|
||||
default from version 6.0. See xref:web/webmvc/mvc-config/path-matching.adoc[MVC config] for
|
||||
customizations of path matching options.
|
||||
|
||||
You can map requests by using glob patterns and wildcards:
|
||||
`PathPattern` supports the same pattern syntax as `AntPathMatcher`. In addition, it also
|
||||
supports the capturing pattern, for example, `+{*spring}+`, for matching 0 or more path segments
|
||||
at the end of a path. `PathPattern` also restricts the use of `+**+` for matching multiple
|
||||
path segments such that it's only allowed at the end of a pattern. This eliminates many
|
||||
cases of ambiguity when choosing the best matching pattern for a given request.
|
||||
For full pattern syntax please refer to
|
||||
{spring-framework-api}/web/util/pattern/PathPattern.html[PathPattern] and
|
||||
{spring-framework-api}/util/AntPathMatcher.html[AntPathMatcher].
|
||||
|
||||
include::partial$web/uri-patterns.adoc[leveloffset=+1]
|
||||
Some example patterns:
|
||||
|
||||
* `+"/resources/ima?e.png"+` - match one character in a path segment
|
||||
* `+"/resources/*.png"+` - match zero or more characters in a path segment
|
||||
* `+"/resources/**"+` - match multiple path segments
|
||||
* `+"/projects/{project}/versions"+` - match a path segment and capture it as a variable
|
||||
* `++"/projects/{project:[a-z]+}/versions"++` - match and capture a variable with a regex
|
||||
|
||||
Captured URI variables can be accessed with `@PathVariable`. For example:
|
||||
|
||||
|
||||
@@ -7,26 +7,22 @@ Spring MVC has built-in xref:core/validation/validator.adoc[validation] for
|
||||
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
Validation may be applied at one of two levels:
|
||||
|
||||
1. Java Bean Validation is applied individually to an
|
||||
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
|
||||
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
|
||||
it is a command object rather than a container such as `Map` or `Collection`, it does not
|
||||
have `Errors` or `BindingResult` immediately after in the method signature, and does not
|
||||
otherwise require method validation (see next). `MethodArgumentNotValidException` is the
|
||||
exception raised when validating a method parameter individually.
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
|
||||
resolvers validate a method argument individually if the method parameter is annotated
|
||||
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
|
||||
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
|
||||
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
|
||||
|
||||
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
|
||||
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
|
||||
method for the return value, and it supersedes any validation that would be applied
|
||||
otherwise to a method parameter individually because method validation covers both
|
||||
method parameter constraints and nested constraints via `@Valid`.
|
||||
`HandlerMethodValidationException` is the exception raised validation is applied
|
||||
to the method.
|
||||
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
|
||||
directly on method parameters, or on the method (for the return value), then method
|
||||
validation must be applied, and that supersedes validation at the method argument level
|
||||
because method validation covers both method parameter constraints and nested constraints
|
||||
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
|
||||
|
||||
Applications should handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` since either may be raised depending on the controller
|
||||
Applications must handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
method signature. The two exceptions, however are designed to be very similar, and can be
|
||||
handled with almost identical code. The main difference is that the former is for a single
|
||||
object while the latter is for a list of method parameters.
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
[cols="2,3,5"]
|
||||
|===
|
||||
|Pattern |Description |Example
|
||||
|
||||
| `spring`
|
||||
| Literal pattern
|
||||
| `+"/spring"+` matches `+"/spring"+`
|
||||
|
||||
| `+?+`
|
||||
| Matches one character
|
||||
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
|
||||
|
||||
| `+*+`
|
||||
| Matches zero or more characters within a path segment
|
||||
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
|
||||
|
||||
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`.
|
||||
|
||||
`+"/projects/*"+` matches `+"/projects/spring"+` but does not match `+"/projects"+` as the path segment is not present.
|
||||
|
||||
| `+**+`
|
||||
| Matches zero or more path segments
|
||||
| `+"/resources/**"+` matches `+"/resources"+`, `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
|
||||
|
||||
`+"/**/info"+` matches `+"/info"+`, `+"/spring/info"+` and `+"/spring/framework/info"+`
|
||||
|
||||
`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path.
|
||||
|
||||
`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
|
||||
|
||||
| `+{name}+`
|
||||
| Similar to `+*+`, but also captures the path segment as a variable named "name"
|
||||
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
|
||||
|
||||
`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment.
|
||||
|
||||
| `{name:[a-z]+}`
|
||||
| Matches the regexp `"[a-z]+"` as a path variable named "name"
|
||||
| `"/projects/{project:[a-z]+}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"`
|
||||
|
||||
| `+{*path}+`
|
||||
| Similar to `+**+`, but also captures the path segments as a variable named "path"
|
||||
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
|
||||
|
||||
`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+`
|
||||
|
||||
`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path.
|
||||
|
||||
`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
|
||||
|
||||
|===
|
||||
@@ -7,26 +7,26 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.13"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4.1"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.12"))
|
||||
api(platform("io.netty:netty-bom:4.1.128.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.12"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.11"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.28"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.6"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.30"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.30"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.28"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.28"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.14.1"))
|
||||
api(platform("org.junit:junit-bom:5.14.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.20.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.4")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.2.3")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.2.2")
|
||||
api("com.github.librepdf:openpdf:1.3.43")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
@@ -129,7 +129,7 @@ dependencies {
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.4")
|
||||
api("org.htmlunit:htmlunit:4.18.0")
|
||||
api("org.htmlunit:htmlunit:4.17.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.13.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
@@ -137,8 +137,8 @@ dependencies {
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.4")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.38.0")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.36.1")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.36.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
api("org.testng:testng:7.11.0")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.13
|
||||
version=6.2.12
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
@@ -62,7 +62,7 @@ public interface ProxyMethodInvocation extends MethodInvocation {
|
||||
MethodInvocation invocableClone(Object... arguments);
|
||||
|
||||
/**
|
||||
* Set the arguments to be used on subsequent invocations in any advice
|
||||
* Set the arguments to be used on subsequent invocations in the any advice
|
||||
* in this chain.
|
||||
* @param arguments the argument array
|
||||
*/
|
||||
|
||||
@@ -41,8 +41,8 @@ abstract class CoroutinesUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Nullable
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
static Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
|
||||
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
|
||||
(Continuation<Object>) continuation);
|
||||
|
||||
+6
-5
@@ -566,7 +566,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
|
||||
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
|
||||
Class<?> targetClass = ClassUtils.getUserClass(clazz);
|
||||
Class<?> targetClass = clazz;
|
||||
|
||||
do {
|
||||
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
|
||||
@@ -586,11 +586,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
|
||||
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
|
||||
if (method.isBridge()) {
|
||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
|
||||
return;
|
||||
}
|
||||
MergedAnnotation<?> ann = findAutowiredAnnotation(method);
|
||||
if (ann != null && method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
|
||||
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Autowired annotation is not supported on static methods: " + method);
|
||||
@@ -608,7 +609,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
}
|
||||
boolean required = determineRequiredStatus(ann);
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
|
||||
methodElements.add(new AutowiredMethodElement(method, required, pd));
|
||||
}
|
||||
});
|
||||
|
||||
+1
-8
@@ -180,6 +180,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
* {@code true} if a qualifier has been found and matched,
|
||||
* {@code null} if no qualifier has been found at all
|
||||
*/
|
||||
|
||||
@Nullable
|
||||
protected Boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
|
||||
boolean qualifierFound = false;
|
||||
@@ -374,14 +375,6 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
return true;
|
||||
}
|
||||
}
|
||||
MethodParameter methodParam = descriptor.getMethodParameter();
|
||||
if (methodParam != null) {
|
||||
for (Annotation annotation : methodParam.getMethodAnnotations()) {
|
||||
if (isQualifier(annotation.annotationType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+1
-20
@@ -29,7 +29,6 @@ import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
|
||||
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
|
||||
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.CollectionDelegate;
|
||||
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.MapDelegate;
|
||||
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
@@ -58,7 +57,6 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
|
||||
* <li>{@link LinkedHashMap}</li>
|
||||
* <li>{@link BeanReference}</li>
|
||||
* <li>{@link TypedStringValue}</li>
|
||||
* <li>{@link AutowiredPropertyMarker}</li>
|
||||
* </ul>
|
||||
* When combined with {@linkplain ValueCodeGeneratorDelegates#INSTANCES the
|
||||
* delegates for common value types}, this should be added first as they have
|
||||
@@ -70,8 +68,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
|
||||
new ManagedMapDelegate(),
|
||||
new LinkedHashMapDelegate(),
|
||||
new BeanReferenceDelegate(),
|
||||
new TypedStringValueDelegate(),
|
||||
new AutowiredPropertyMarkerDelegate()
|
||||
new TypedStringValueDelegate()
|
||||
);
|
||||
|
||||
|
||||
@@ -219,20 +216,4 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
|
||||
return valueCodeGenerator.generateCode(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Delegate} for {@link AutowiredPropertyMarker} types.
|
||||
*/
|
||||
private static class AutowiredPropertyMarkerDelegate implements Delegate {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
|
||||
if (value instanceof AutowiredPropertyMarker) {
|
||||
return CodeBlock.of("$T.INSTANCE", AutowiredPropertyMarker.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-7
@@ -1653,8 +1653,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullAway") // Dataflow analysis limitation
|
||||
@Nullable
|
||||
@SuppressWarnings("NullAway")
|
||||
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
|
||||
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
|
||||
|
||||
@@ -1991,8 +1991,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
|
||||
for (String candidate : candidateNames) {
|
||||
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
|
||||
(!multiple || matchesBeanName(candidate, descriptor.getDependencyName()) ||
|
||||
getAutowireCandidateResolver().hasQualifier(descriptor))) {
|
||||
(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
|
||||
addCandidateEntry(result, candidate, descriptor, requiredType);
|
||||
}
|
||||
}
|
||||
@@ -2263,12 +2262,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given dependency name matches the bean name or the aliases
|
||||
* Determine whether the given candidate name matches the bean name or the aliases
|
||||
* stored in this bean definition.
|
||||
*/
|
||||
protected boolean matchesBeanName(String beanName, @Nullable String dependencyName) {
|
||||
return (dependencyName != null &&
|
||||
(dependencyName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), dependencyName)));
|
||||
protected boolean matchesBeanName(String beanName, @Nullable String candidateName) {
|
||||
return (candidateName != null &&
|
||||
(candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -472,7 +472,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
|
||||
assertThat(name).as("name is OK").isEqualTo(target.getName());
|
||||
accessor.setPropertyValues(new MutablePropertyValues());
|
||||
// Check it's unchanged
|
||||
// Check its unchanged
|
||||
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
|
||||
assertThat(name).as("name is OK").isEqualTo(target.getName());
|
||||
}
|
||||
|
||||
+82
-167
@@ -1275,6 +1275,88 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.getTestBean().get("testBean2")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldInjectionWithMap() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb1 = new TestBean("tb1");
|
||||
TestBean tb2 = new TestBean("tb2");
|
||||
bf.registerSingleton("testBean1", tb1);
|
||||
bf.registerSingleton("testBean2", tb2);
|
||||
bf.registerAlias("testBean1", "testBean");
|
||||
|
||||
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(2);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb1);
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb2);
|
||||
|
||||
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(2);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb1);
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMap() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(1);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb);
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
|
||||
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(1);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb);
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMapAndMultipleMatches() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
|
||||
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
|
||||
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
|
||||
.isThrownBy(() -> bf.getBean("annotatedBean"))
|
||||
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
|
||||
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
|
||||
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
|
||||
rbd2.setAutowireCandidate(false);
|
||||
bf.registerBeanDefinition("testBean2", rbd2);
|
||||
|
||||
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
TestBean tb = bf.getBean("testBean1", TestBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(1);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb);
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMapAndNoMatches() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
|
||||
|
||||
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isNull();
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorInjectionWithTypedMapAsBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
|
||||
@@ -1327,19 +1409,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
|
||||
@Test
|
||||
void constructorInjectionWithPlainHashMapAsBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(NamedMapConstructorInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
|
||||
|
||||
NamedMapConstructorInjectionBean bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
|
||||
bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorInjectionWithQualifiedPlainHashMapAsBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapConstructorInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
@@ -1428,114 +1497,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.getTestBeanSet()).contains(tb1, tb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldInjectionWithMap() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb1 = new TestBean("tb1");
|
||||
TestBean tb2 = new TestBean("tb2");
|
||||
bf.registerSingleton("testBean1", tb1);
|
||||
bf.registerSingleton("testBean2", tb2);
|
||||
bf.registerAlias("testBean1", "testBean");
|
||||
|
||||
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(2);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb1);
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb2);
|
||||
|
||||
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(2);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb1);
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMap() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(1);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb);
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
|
||||
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(1);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb);
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMapAndMultipleMatches() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
|
||||
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
|
||||
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
|
||||
.isThrownBy(() -> bf.getBean("annotatedBean"))
|
||||
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
|
||||
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
|
||||
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
|
||||
rbd2.setAutowireCandidate(false);
|
||||
bf.registerBeanDefinition("testBean2", rbd2);
|
||||
|
||||
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
TestBean tb = bf.getBean("testBean1", TestBean.class);
|
||||
assertThat(bean.getTestBeanMap()).hasSize(1);
|
||||
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
|
||||
assertThat(bean.getTestBeanMap()).containsValue(tb);
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithMapAndNoMatches() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
|
||||
|
||||
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isNull();
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithPlainHashMapAsBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(NamedMapMethodInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
|
||||
|
||||
NamedMapMethodInjectionBean bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
|
||||
bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodInjectionWithQualifiedPlainHashMapAsBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapMethodInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
bf.registerBeanDefinition("myTestBeanMap", new RootBeanDefinition(HashMap.class));
|
||||
|
||||
QualifiedMapMethodInjectionBean bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
|
||||
bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
|
||||
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selfReference() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class));
|
||||
@@ -3297,21 +3258,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
}
|
||||
|
||||
|
||||
public static class NamedMapConstructorInjectionBean {
|
||||
|
||||
private Map<String, TestBean> testBeanMap;
|
||||
|
||||
@Autowired
|
||||
public NamedMapConstructorInjectionBean(Map<String, TestBean> testBeanMap) {
|
||||
this.testBeanMap = testBeanMap;
|
||||
}
|
||||
|
||||
public Map<String, TestBean> getTestBeanMap() {
|
||||
return this.testBeanMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class QualifiedMapConstructorInjectionBean {
|
||||
|
||||
private Map<String, TestBean> testBeanMap;
|
||||
@@ -3411,37 +3357,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
}
|
||||
|
||||
|
||||
public static class NamedMapMethodInjectionBean {
|
||||
|
||||
private Map<String, TestBean> testBeanMap;
|
||||
|
||||
@Autowired
|
||||
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
|
||||
this.testBeanMap = testBeanMap;
|
||||
}
|
||||
|
||||
public Map<String, TestBean> getTestBeanMap() {
|
||||
return this.testBeanMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class QualifiedMapMethodInjectionBean {
|
||||
|
||||
private Map<String, TestBean> testBeanMap;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myTestBeanMap")
|
||||
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
|
||||
this.testBeanMap = testBeanMap;
|
||||
}
|
||||
|
||||
public Map<String, TestBean> getTestBeanMap() {
|
||||
return this.testBeanMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class ObjectFactoryFieldInjectionBean implements Serializable {
|
||||
|
||||
|
||||
-18
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.beans.factory.aot;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -35,7 +34,6 @@ import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
@@ -628,22 +626,6 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
-> assertThat(customPropertyValue.value()).isEqualTo("test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateBeanDefinitionMethodWhenHasAutowiredPropertyGeneratesMethod() {
|
||||
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder
|
||||
.rootBeanDefinition(CustomBean.class).addAutowiredProperty("innerBean")
|
||||
.getBeanDefinition();
|
||||
RegisteredBean registeredBean = registerBean(beanDefinition);
|
||||
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
|
||||
this.methodGeneratorFactory, registeredBean, null,
|
||||
Collections.emptyList());
|
||||
MethodReference method = generator.generateBeanDefinitionMethod(
|
||||
this.generationContext, this.beanRegistrationsCode);
|
||||
compile(method, (actual, compiled) ->
|
||||
assertThat(actual.getPropertyValues().get("innerBean"))
|
||||
.isSameAs(AutowiredPropertyMarker.INSTANCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateBeanDefinitionMethodWhenHasAotContributionsAppliesContributions() {
|
||||
RegisteredBean registeredBean = registerBean(
|
||||
|
||||
-13
@@ -39,7 +39,6 @@ import org.springframework.aot.generate.GeneratedClass;
|
||||
import org.springframework.aot.generate.ValueCodeGenerator;
|
||||
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
@@ -487,16 +486,4 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AutowiredPropertyMarkerTests {
|
||||
|
||||
@Test
|
||||
void generateWhenAutowiredPropertyMarker() {
|
||||
compile(AutowiredPropertyMarker.INSTANCE, (instance, compiler) ->
|
||||
assertThat(instance).isInstanceOf(AutowiredPropertyMarker.class)
|
||||
.isSameAs(AutowiredPropertyMarker.INSTANCE));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-33
@@ -28,7 +28,6 @@ import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.FieldRetrievingFactoryBean;
|
||||
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
||||
import org.springframework.beans.factory.parsing.ComponentDefinition;
|
||||
@@ -47,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
class UtilNamespaceHandlerTests {
|
||||
|
||||
private DefaultListableBeanFactory beanFactory;
|
||||
@@ -55,7 +55,7 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
void setUp() {
|
||||
this.beanFactory = new DefaultListableBeanFactory();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory);
|
||||
reader.setEventListener(this.listener);
|
||||
@@ -109,17 +109,17 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
@Test
|
||||
void testSimpleMap() {
|
||||
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
|
||||
Map<?, ?> map = (Map) this.beanFactory.getBean("simpleMap");
|
||||
assertThat(map.get("foo")).isEqualTo("bar");
|
||||
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
|
||||
Map<?, ?> map2 = (Map) this.beanFactory.getBean("simpleMap");
|
||||
assertThat(map).isSameAs(map2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScopedMap() {
|
||||
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
|
||||
Map<?, ?> map = (Map) this.beanFactory.getBean("scopedMap");
|
||||
assertThat(map.get("foo")).isEqualTo("bar");
|
||||
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
|
||||
Map<?, ?> map2 = (Map) this.beanFactory.getBean("scopedMap");
|
||||
assertThat(map2.get("foo")).isEqualTo("bar");
|
||||
assertThat(map).isNotSameAs(map2);
|
||||
}
|
||||
@@ -164,23 +164,17 @@ class UtilNamespaceHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMapWithRef() throws Exception {
|
||||
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithRef");
|
||||
void testMapWithRef() {
|
||||
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithRef");
|
||||
assertThat(map).isInstanceOf(TreeMap.class);
|
||||
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
|
||||
assertThat(this.beanFactory.resolveDependency(
|
||||
new DependencyDescriptor(getClass().getDeclaredField("mapWithRef"), true), null))
|
||||
.isSameAs(map);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMapWithTypes() throws Exception {
|
||||
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithTypes");
|
||||
void testMapWithTypes() {
|
||||
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithTypes");
|
||||
assertThat(map).isInstanceOf(LinkedCaseInsensitiveMap.class);
|
||||
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
|
||||
assertThat(this.beanFactory.resolveDependency(
|
||||
new DependencyDescriptor(getClass().getDeclaredField("mapWithTypes"), true), null))
|
||||
.isSameAs(map);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -246,11 +240,11 @@ class UtilNamespaceHandlerTests {
|
||||
void testCircularCollections() {
|
||||
TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionsBean");
|
||||
|
||||
assertThat(bean.getSomeList()).singleElement().isSameAs(bean);
|
||||
assertThat(bean.getSomeSet()).singleElement().isSameAs(bean);
|
||||
assertThat(bean.getSomeList()).singleElement().isEqualTo(bean);
|
||||
assertThat(bean.getSomeSet()).singleElement().isEqualTo(bean);
|
||||
assertThat(bean.getSomeMap()).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isSameAs(bean);
|
||||
assertThat(value).isEqualTo(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -261,17 +255,17 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
List<?> list = bean.getSomeList();
|
||||
assertThat(Proxy.isProxyClass(list.getClass())).isTrue();
|
||||
assertThat(list).singleElement().isSameAs(bean);
|
||||
assertThat(list).singleElement().isEqualTo(bean);
|
||||
|
||||
Set<?> set = bean.getSomeSet();
|
||||
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
|
||||
assertThat(set).singleElement().isSameAs(bean);
|
||||
assertThat(set).singleElement().isEqualTo(bean);
|
||||
|
||||
Map<?, ?> map = bean.getSomeMap();
|
||||
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
|
||||
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isSameAs(bean);
|
||||
assertThat(value).isEqualTo(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -282,17 +276,17 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
List<?> list = bean.getSomeList();
|
||||
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
|
||||
assertThat(list).singleElement().isSameAs(bean);
|
||||
assertThat(list).singleElement().isEqualTo(bean);
|
||||
|
||||
Set<?> set = bean.getSomeSet();
|
||||
assertThat(Proxy.isProxyClass(set.getClass())).isTrue();
|
||||
assertThat(set).singleElement().isSameAs(bean);
|
||||
assertThat(set).singleElement().isEqualTo(bean);
|
||||
|
||||
Map<?, ?> map = bean.getSomeMap();
|
||||
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
|
||||
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isSameAs(bean);
|
||||
assertThat(value).isEqualTo(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,17 +297,17 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
List<?> list = bean.getSomeList();
|
||||
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
|
||||
assertThat(list).singleElement().isSameAs(bean);
|
||||
assertThat(list).singleElement().isEqualTo(bean);
|
||||
|
||||
Set<?> set = bean.getSomeSet();
|
||||
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
|
||||
assertThat(set).singleElement().isSameAs(bean);
|
||||
assertThat(set).singleElement().isEqualTo(bean);
|
||||
|
||||
Map<?, ?> map = bean.getSomeMap();
|
||||
assertThat(Proxy.isProxyClass(map.getClass())).isTrue();
|
||||
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isSameAs(bean);
|
||||
assertThat(value).isEqualTo(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -378,9 +372,4 @@ class UtilNamespaceHandlerTests {
|
||||
assertThat(props).as("Incorrect property value").containsEntry("foo2", "local2");
|
||||
}
|
||||
|
||||
|
||||
// For DependencyDescriptor resolution
|
||||
private Map<String, TestBean> mapWithRef;
|
||||
private Map<String, TestBean> mapWithTypes;
|
||||
|
||||
}
|
||||
|
||||
-10
@@ -59,8 +59,6 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
|
||||
|
||||
private boolean jedi;
|
||||
|
||||
private String favoriteCafé;
|
||||
|
||||
private ITestBean spouse;
|
||||
|
||||
private String touchy;
|
||||
@@ -211,14 +209,6 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
|
||||
this.jedi = jedi;
|
||||
}
|
||||
|
||||
public String getFavoriteCafé() {
|
||||
return this.favoriteCafé;
|
||||
}
|
||||
|
||||
public void setFavoriteCafé(String favoriteCafé) {
|
||||
this.favoriteCafé = favoriteCafé;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITestBean getSpouse() {
|
||||
return this.spouse;
|
||||
|
||||
-5
@@ -25,7 +25,6 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -98,10 +97,6 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The method may be on an interface, but we need metadata from the target class.
|
||||
// If the target class is null, the method will be unchanged.
|
||||
|
||||
+7
-7
@@ -23,14 +23,14 @@ import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Interface to be implemented for explicitly specifying how caches are resolved
|
||||
* and how keys are generated for annotation-driven cache management.
|
||||
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
|
||||
* Configuration} classes annotated with @{@link EnableCaching} that wish or need to specify
|
||||
* explicitly how caches are resolved and how keys are generated for annotation-driven
|
||||
* cache management.
|
||||
*
|
||||
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
|
||||
* Configuration} classes annotated with @{@link EnableCaching}.
|
||||
* See @{@link EnableCaching} for general examples and context; see
|
||||
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()},
|
||||
* and {@link #errorHandler()} for detailed instructions.
|
||||
* <p>See @{@link EnableCaching} for general examples and context; see
|
||||
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()}, and
|
||||
* {@link #errorHandler()} for detailed instructions.
|
||||
*
|
||||
* <p><b>NOTE: A {@code CachingConfigurer} will get initialized early.</b>
|
||||
* Do not inject common dependencies into autowired fields directly; instead, consider
|
||||
|
||||
-5
@@ -27,7 +27,6 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -140,10 +139,6 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The method may be on an interface, but we need metadata from the target class.
|
||||
// If the target class is null, the method will be unchanged.
|
||||
|
||||
+17
-16
@@ -149,6 +149,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
private static final boolean jndiPresent = ClassUtils.isPresent(
|
||||
"javax.naming.InitialContext", CommonAnnotationBeanPostProcessor.class.getClassLoader());
|
||||
|
||||
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
|
||||
|
||||
@Nullable
|
||||
private static final Class<? extends Annotation> jakartaResourceType;
|
||||
|
||||
@@ -158,8 +160,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
@Nullable
|
||||
private static final Class<? extends Annotation> ejbAnnotationType;
|
||||
|
||||
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
|
||||
|
||||
static {
|
||||
jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
|
||||
if (jakartaResourceType != null) {
|
||||
@@ -424,7 +424,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
}
|
||||
|
||||
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
|
||||
Class<?> targetClass = ClassUtils.getUserClass(clazz);
|
||||
Class<?> targetClass = clazz;
|
||||
|
||||
do {
|
||||
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
|
||||
@@ -455,23 +455,24 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
});
|
||||
|
||||
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
|
||||
if (method.isBridge()) {
|
||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
|
||||
return;
|
||||
}
|
||||
if (ejbAnnotationType != null && method.isAnnotationPresent(ejbAnnotationType)) {
|
||||
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@EJB annotation is not supported on static methods");
|
||||
}
|
||||
if (method.getParameterCount() != 1) {
|
||||
throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
|
||||
}
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
currElements.add(new EjbRefElement(method, method, pd));
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
|
||||
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
|
||||
}
|
||||
}
|
||||
else if (jakartaResourceType != null && method.isAnnotationPresent(jakartaResourceType)) {
|
||||
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@Resource annotation is not supported on static methods");
|
||||
}
|
||||
@@ -480,13 +481,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
|
||||
}
|
||||
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
currElements.add(new ResourceElement(method, method, pd));
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
|
||||
currElements.add(new ResourceElement(method, bridgedMethod, pd));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (javaxResourceType != null && method.isAnnotationPresent(javaxResourceType)) {
|
||||
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@Resource annotation is not supported on static methods");
|
||||
}
|
||||
@@ -495,8 +496,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
|
||||
}
|
||||
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
currElements.add(new LegacyResourceElement(method, method, pd));
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
|
||||
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-18
@@ -25,18 +25,18 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* Declares that a field or method parameter should be formatted as a number.
|
||||
*
|
||||
* <p>Supports formatting by style or custom pattern string. Can be applied to
|
||||
* any JDK {@code Number} types such as {@code Double} and {@code Long}.
|
||||
* <p>Supports formatting by style or custom pattern string. Can be applied
|
||||
* to any JDK {@code Number} type such as {@code Double} and {@code Long}.
|
||||
*
|
||||
* <p>For style-based formatting, set the {@link #style} attribute to the desired
|
||||
* {@link Style}. For custom formatting, set the {@link #pattern} attribute to the
|
||||
* desired number pattern, such as {@code "#,###.##"}.
|
||||
* <p>For style-based formatting, set the {@link #style} attribute to be the
|
||||
* desired {@link Style}. For custom formatting, set the {@link #pattern}
|
||||
* attribute to be the number pattern, such as {@code #, ###.##}.
|
||||
*
|
||||
* <p>Each attribute is mutually exclusive, so only set one attribute per
|
||||
* annotation (the one most convenient for your formatting needs). When the
|
||||
* {@link #pattern} attribute is specified, it takes precedence over the
|
||||
* {@link #style} attribute. When no annotation attributes are specified, the
|
||||
* default format applied is style-based for either number or currency,
|
||||
* annotation instance (the one most convenient one for your formatting needs).
|
||||
* When the {@link #pattern} attribute is specified, it takes precedence over
|
||||
* the {@link #style} attribute. When no annotation attributes are specified,
|
||||
* the default format applied is style-based for either number of currency,
|
||||
* depending on the annotated field or method parameter type.
|
||||
*
|
||||
* @author Keith Donald
|
||||
@@ -50,21 +50,19 @@ import java.lang.annotation.Target;
|
||||
public @interface NumberFormat {
|
||||
|
||||
/**
|
||||
* The style pattern to use to format the field or method parameter.
|
||||
* The style pattern to use to format the field.
|
||||
* <p>Defaults to {@link Style#DEFAULT} for general-purpose number formatting
|
||||
* for most annotated types, except for money types which default to currency
|
||||
* formatting.
|
||||
* <p>Set this attribute when you wish to format your field or method parameter
|
||||
* in accordance with a common style other than the default style.
|
||||
* formatting. Set this attribute when you wish to format your field in
|
||||
* accordance with a common style other than the default style.
|
||||
*/
|
||||
Style style() default Style.DEFAULT;
|
||||
|
||||
/**
|
||||
* The custom pattern to use to format the field or method parameter.
|
||||
* <p>Defaults to an empty String, indicating no custom pattern has been
|
||||
* specified.
|
||||
* <p>Set this attribute when you wish to format your field or method parameter
|
||||
* in accordance with a custom number pattern not represented by a style.
|
||||
* The custom pattern to use to format the field.
|
||||
* <p>Defaults to empty String, indicating no custom pattern String has been specified.
|
||||
* Set this attribute when you wish to format your field in accordance with a
|
||||
* custom number pattern not represented by a style.
|
||||
*/
|
||||
String pattern() default "";
|
||||
|
||||
|
||||
+6
-11
@@ -22,18 +22,13 @@ import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Interface to be implemented for customizing the {@link Executor} instance used when
|
||||
* processing async method invocations or the {@link AsyncUncaughtExceptionHandler}
|
||||
* instance used to process exceptions thrown from async methods with a {@code void}
|
||||
* return type.
|
||||
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
|
||||
* Configuration} classes annotated with @{@link EnableAsync} that wish to customize the
|
||||
* {@link Executor} instance used when processing async method invocations or the
|
||||
* {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from
|
||||
* async method with {@code void} return type.
|
||||
*
|
||||
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
|
||||
* Configuration} classes annotated with @{@link EnableAsync}.
|
||||
* See the @{@link EnableAsync} javadoc for usage examples.
|
||||
*
|
||||
* <p><b>NOTE: An {@code AsyncConfigurer} will get initialized early.</b>
|
||||
* Do not inject common dependencies into autowired fields directly; instead, consider
|
||||
* declaring a lazy {@link org.springframework.beans.factory.ObjectProvider} for those.
|
||||
* <p>See @{@link EnableAsync} for usage examples.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.validation;
|
||||
|
||||
import java.util.HexFormat;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -128,18 +126,8 @@ public class FieldError extends ObjectError {
|
||||
// We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but
|
||||
// keep including the full nullSafeToString representation for backwards compatibility.
|
||||
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
|
||||
"': rejected value [" + formatRejectedValue() + "]; " +
|
||||
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
|
||||
resolvableToString();
|
||||
}
|
||||
|
||||
private String formatRejectedValue() {
|
||||
|
||||
// Special handling of byte[], to be moved into ObjectUtils in 7.0
|
||||
if (this.rejectedValue instanceof byte[] bytes && bytes.length != 0) {
|
||||
return "{" + HexFormat.of().formatHex(bytes) + "}";
|
||||
}
|
||||
|
||||
return ObjectUtils.nullSafeToString(this.rejectedValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -503,6 +503,10 @@ class SpringValidatorAdapterTests {
|
||||
|
||||
private static final String ID = "id";
|
||||
|
||||
@Override
|
||||
public void initialize(AnythingValid constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Object value, ConstraintValidatorContext context) {
|
||||
List<Field> fieldsErrors = new ArrayList<>();
|
||||
|
||||
+12
@@ -425,6 +425,10 @@ class ValidatorFactoryTests {
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void initialize(NameAddressValid constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
|
||||
if (value.expectsAutowiredValidator) {
|
||||
@@ -491,6 +495,10 @@ class ValidatorFactoryTests {
|
||||
|
||||
public static class InnerValidator implements ConstraintValidator<InnerValid, InnerBean> {
|
||||
|
||||
@Override
|
||||
public void initialize(InnerValid constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
@@ -534,6 +542,10 @@ class ValidatorFactoryTests {
|
||||
|
||||
public static class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {
|
||||
|
||||
@Override
|
||||
public void initialize(NotXList constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(List<String> list, ConstraintValidatorContext context) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
|
||||
@@ -193,16 +193,12 @@ public class ClassReader {
|
||||
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
|
||||
this.classFileBuffer = classFileBuffer;
|
||||
this.b = classFileBuffer;
|
||||
|
||||
// Check the class' major_version. This field is after the magic and minor_version fields, which
|
||||
// use 4 and 2 bytes respectively.
|
||||
// SPRING PATCH: leniently try to parse newer class files as well
|
||||
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
|
||||
// throw new IllegalArgumentException(
|
||||
// "Unsupported class file major version " + readShort(classFileOffset + 6));
|
||||
// }
|
||||
// END OF PATCH
|
||||
|
||||
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V25) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported class file major version " + readShort(classFileOffset + 6));
|
||||
}
|
||||
// Create the constant pool arrays. The constant_pool_count field is after the magic,
|
||||
// minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
|
||||
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
|
||||
|
||||
@@ -290,7 +290,6 @@ public interface Opcodes {
|
||||
int V23 = 0 << 16 | 67;
|
||||
int V24 = 0 << 16 | 68;
|
||||
int V25 = 0 << 16 | 69;
|
||||
int V26 = 0 << 16 | 70;
|
||||
|
||||
/**
|
||||
* Version flag indicating that the class is using 'preview' features.
|
||||
|
||||
@@ -1473,7 +1473,7 @@ final class SymbolTable {
|
||||
|
||||
/**
|
||||
* Another entry (and so on recursively) having the same hash code (modulo the size of {@link
|
||||
* SymbolTable#labelEntries}) as this one.
|
||||
* SymbolTable#labelEntries}}) as this one.
|
||||
*/
|
||||
LabelEntry next;
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class BridgeMethodResolver {
|
||||
&& currentMethod != null) {
|
||||
Signature target = new Signature(name, desc);
|
||||
// If the target signature is the same as the current,
|
||||
// we shouldn't change our bridge because invokespecial
|
||||
// we shouldn't change our bridge becaues invokespecial
|
||||
// is the only way to make progress (otherwise we'll
|
||||
// get infinite recursion). This would typically
|
||||
// only happen when a bridge method is created to widen
|
||||
|
||||
@@ -100,15 +100,15 @@ public final class BridgeMethodResolver {
|
||||
}
|
||||
|
||||
private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
|
||||
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
|
||||
Class<?> userClass = targetClass;
|
||||
if (!bridgeMethod.isBridge()) {
|
||||
if (!bridgeMethod.isBridge() && localBridge) {
|
||||
userClass = ClassUtils.getUserClass(targetClass);
|
||||
if (userClass == targetClass) {
|
||||
return bridgeMethod;
|
||||
}
|
||||
}
|
||||
|
||||
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
|
||||
Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass));
|
||||
Method bridgedMethod = cache.get(cacheKey);
|
||||
if (bridgedMethod == null) {
|
||||
@@ -118,7 +118,7 @@ public final class BridgeMethodResolver {
|
||||
ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter);
|
||||
if (!candidateMethods.isEmpty()) {
|
||||
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
|
||||
searchCandidates(candidateMethods, bridgeMethod, targetClass));
|
||||
searchCandidates(candidateMethods, bridgeMethod));
|
||||
}
|
||||
if (bridgedMethod == null) {
|
||||
// A bridge method was passed in but we couldn't find the bridged method.
|
||||
@@ -149,16 +149,14 @@ public final class BridgeMethodResolver {
|
||||
* @return the bridged method, or {@code null} if none found
|
||||
*/
|
||||
@Nullable
|
||||
private static Method searchCandidates(
|
||||
List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
|
||||
|
||||
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
|
||||
if (candidateMethods.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Method previousMethod = null;
|
||||
boolean sameSig = true;
|
||||
for (Method candidateMethod : candidateMethods) {
|
||||
if (isBridgeMethodFor(bridgeMethod, candidateMethod, targetClass)) {
|
||||
if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
|
||||
return candidateMethod;
|
||||
}
|
||||
else if (previousMethod != null) {
|
||||
@@ -174,12 +172,12 @@ public final class BridgeMethodResolver {
|
||||
* Determines whether the bridge {@link Method} is the bridge for the
|
||||
* supplied candidate {@link Method}.
|
||||
*/
|
||||
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> targetClass) {
|
||||
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, targetClass)) {
|
||||
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
|
||||
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
|
||||
return true;
|
||||
}
|
||||
Method method = findGenericDeclaration(bridgeMethod);
|
||||
return (method != null && isResolvedTypeMatch(method, candidateMethod, targetClass));
|
||||
return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,25 +186,14 @@ public final class BridgeMethodResolver {
|
||||
* are equal after resolving all types against the declaringType, otherwise
|
||||
* returns {@code false}.
|
||||
*/
|
||||
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> targetClass) {
|
||||
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
|
||||
Type[] genericParameters = genericMethod.getGenericParameterTypes();
|
||||
if (genericParameters.length != candidateMethod.getParameterCount()) {
|
||||
return false;
|
||||
}
|
||||
Class<?> clazz = targetClass;
|
||||
while (clazz != null && clazz != Object.class && clazz != genericMethod.getDeclaringClass()) {
|
||||
if (checkResolvedTypeMatch(genericMethod, candidateMethod, clazz)) {
|
||||
return true;
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> clazz) {
|
||||
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
|
||||
for (int i = 0; i < candidateParameters.length; i++) {
|
||||
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz);
|
||||
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
|
||||
Class<?> candidateParameter = candidateParameters[i];
|
||||
if (candidateParameter.isArray()) {
|
||||
// An array type: compare the component type.
|
||||
@@ -286,9 +273,7 @@ public final class BridgeMethodResolver {
|
||||
* introduced in Java 6 to fix <a href="https://bugs.openjdk.org/browse/JDK-6342411">
|
||||
* JDK-6342411</a>.
|
||||
* @return whether signatures match as described
|
||||
* @deprecated as of 6.2.13: not necessary anymore due to {@link #getMostSpecificMethod}
|
||||
*/
|
||||
@Deprecated(since = "6.2.13", forRemoval = true)
|
||||
public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
|
||||
if (bridgeMethod == bridgedMethod) {
|
||||
// Same method: for common purposes, return true to proceed as if it was a visibility bridge.
|
||||
|
||||
@@ -50,7 +50,7 @@ public abstract class Hints {
|
||||
|
||||
|
||||
/**
|
||||
* Create a map with a single hint via {@link Collections#singletonMap}.
|
||||
* Create a map wit a single hint via {@link Collections#singletonMap}.
|
||||
* @param hintName the hint name
|
||||
* @param value the hint value
|
||||
* @return the created map
|
||||
|
||||
+2
-11
@@ -617,12 +617,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
private Set<ClassPathManifestEntry> getClassPathManifestEntriesFromJar(File jar) throws IOException {
|
||||
URL base = jar.toURI().toURL();
|
||||
File parent = jar.getAbsoluteFile().getParentFile();
|
||||
|
||||
try (JarFile jarFile = new JarFile(jar)) {
|
||||
Manifest manifest = jarFile.getManifest();
|
||||
Attributes attributes = (manifest != null ? manifest.getMainAttributes() : null);
|
||||
String classPath = (attributes != null ? attributes.getValue(Name.CLASS_PATH) : null);
|
||||
|
||||
Set<ClassPathManifestEntry> manifestEntries = new LinkedHashSet<>();
|
||||
if (StringUtils.hasLength(classPath)) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(classPath);
|
||||
@@ -632,15 +630,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
// See jdk.internal.loader.URLClassPath.JarLoader.tryResolveFile(URL, String)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle absolute paths correctly: do not apply parent to absolute paths.
|
||||
File pathFile = new File(path);
|
||||
File candidate = (pathFile.isAbsolute() ? pathFile : new File(parent, path));
|
||||
|
||||
// For relative paths, enforce security check: must be under parent.
|
||||
// For absolute paths, just verify file exists (matching JVM behavior).
|
||||
if (candidate.isFile() && (pathFile.isAbsolute() ||
|
||||
candidate.getCanonicalPath().contains(parent.getCanonicalPath()))) {
|
||||
File candidate = new File(parent, path);
|
||||
if (candidate.isFile() && candidate.getCanonicalPath().contains(parent.getCanonicalPath())) {
|
||||
manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-9
@@ -313,15 +313,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
|
||||
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
|
||||
this.concurrencyThrottle.beforeAccess();
|
||||
try {
|
||||
doExecute(new TaskTrackingRunnable(taskToUse));
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Release concurrency permit if thread creation fails
|
||||
this.concurrencyThrottle.afterAccess();
|
||||
throw new TaskRejectedException(
|
||||
"Failed to start execution thread for task: " + task, ex);
|
||||
}
|
||||
doExecute(new TaskTrackingRunnable(taskToUse));
|
||||
}
|
||||
else if (this.activeThreads != null) {
|
||||
doExecute(new TaskTrackingRunnable(taskToUse));
|
||||
|
||||
@@ -1483,16 +1483,15 @@ public abstract class ClassUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest publicly accessible method in the supplied method's type hierarchy that
|
||||
* has a method signature equivalent to the supplied method, if possible.
|
||||
* <p>This method recursively searches the class hierarchy and implemented interfaces for
|
||||
* an equivalent method that is {@code public}, declared in a {@code public} type, and
|
||||
* {@linkplain Module#isExported(String, Module) exported} to {@code spring-core}.
|
||||
* <p>If the supplied method is not {@code public} or is {@code static}, or if a publicly
|
||||
* accessible equivalent method cannot be found, the supplied method will be returned,
|
||||
* indicating that no such equivalent method exists. Consequently, callers of this method
|
||||
* must manually validate the accessibility of the returned method if public access is a
|
||||
* requirement.
|
||||
* Get the closest publicly accessible (and exported) method in the supplied method's type
|
||||
* hierarchy that has a method signature equivalent to the supplied method, if possible.
|
||||
* <p>Otherwise, this method recursively searches the class hierarchy and implemented
|
||||
* interfaces for an equivalent method that is {@code public} and declared in a
|
||||
* {@code public} type.
|
||||
* <p>If a publicly accessible equivalent method cannot be found, the supplied method
|
||||
* will be returned, indicating that no such equivalent method exists. Consequently,
|
||||
* callers of this method must manually validate the accessibility of the returned method
|
||||
* if public access is a requirement.
|
||||
* <p>This is particularly useful for arriving at a public exported type on the Java
|
||||
* Module System which allows the method to be invoked via reflection without an illegal
|
||||
* access warning. This is also useful for invoking methods via a public API in bytecode
|
||||
@@ -1509,11 +1508,10 @@ public abstract class ClassUtils {
|
||||
*/
|
||||
public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nullable Class<?> targetClass) {
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
// If the method is not public, or it's static, or its declaring class is public and exported
|
||||
// already, we can abort the search immediately (avoiding reflection as well as cache access).
|
||||
if (!Modifier.isPublic(method.getModifiers()) || Modifier.isStatic(method.getModifiers()) ||
|
||||
(Modifier.isPublic(declaringClass.getModifiers()) &&
|
||||
declaringClass.getModule().isExported(declaringClass.getPackageName(), ClassUtils.class.getModule()))) {
|
||||
// If the method is not public or its declaring class is public and exported already,
|
||||
// we can abort the search immediately (avoiding reflection as well as cache access).
|
||||
if (!Modifier.isPublic(method.getModifiers()) || (Modifier.isPublic(declaringClass.getModifiers()) &&
|
||||
declaringClass.getModule().isExported(declaringClass.getPackageName(), ClassUtils.class.getModule()))) {
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
+7
-120
@@ -33,13 +33,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link ConcurrentHashMap} variant that uses {@link ReferenceType#SOFT soft} or
|
||||
* A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
|
||||
* {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}.
|
||||
*
|
||||
* <p>This class can be used as an alternative to
|
||||
@@ -367,118 +365,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public V computeIfAbsent(@Nullable K key, Function<? super K, ? extends V> mappingFunction) {
|
||||
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
|
||||
@Override
|
||||
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
|
||||
if (entry != null) {
|
||||
return entry.getValue();
|
||||
}
|
||||
V value = mappingFunction.apply(key);
|
||||
// Add entry only if not null
|
||||
if (value != null) {
|
||||
Assert.state(entries != null, "No entries segment");
|
||||
entries.add(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public V computeIfPresent(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
|
||||
@Override
|
||||
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
|
||||
if (entry != null) {
|
||||
V oldValue = entry.getValue();
|
||||
V value = remappingFunction.apply(key, oldValue);
|
||||
if (value != null) {
|
||||
// Replace entry
|
||||
entry.setValue(value);
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
// Remove entry
|
||||
if (ref != null) {
|
||||
ref.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public V compute(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
|
||||
@Override
|
||||
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
|
||||
V oldValue = null;
|
||||
if (entry != null) {
|
||||
oldValue = entry.getValue();
|
||||
}
|
||||
V value = remappingFunction.apply(key, oldValue);
|
||||
if (value != null) {
|
||||
if (entry != null) {
|
||||
// Replace entry
|
||||
entry.setValue(value);
|
||||
}
|
||||
else {
|
||||
// Add entry
|
||||
Assert.state(entries != null, "No entries segment");
|
||||
entries.add(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
// Remove entry
|
||||
if (ref != null) {
|
||||
ref.release();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public V merge(@Nullable K key, @Nullable V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
|
||||
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
|
||||
@Override
|
||||
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
|
||||
if (entry != null) {
|
||||
V oldValue = entry.getValue();
|
||||
V newValue = remappingFunction.apply(oldValue, value);
|
||||
if (newValue != null) {
|
||||
// Replace entry
|
||||
entry.setValue(newValue);
|
||||
return newValue;
|
||||
}
|
||||
else {
|
||||
// Remove entry
|
||||
if (ref != null) {
|
||||
ref.release();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Add entry
|
||||
Assert.state(entries != null, "No entries segment");
|
||||
entries.add(value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (Segment segment : this.segments) {
|
||||
@@ -628,7 +514,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
* @return the result of the operation
|
||||
*/
|
||||
@Nullable
|
||||
private <T> T doTask(final int hash, @Nullable final Object key, final Task<T> task) {
|
||||
public <T> T doTask(final int hash, @Nullable final Object key, final Task<T> task) {
|
||||
boolean resize = task.hasOption(TaskOption.RESIZE);
|
||||
if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
|
||||
restructureIfNecessary(resize);
|
||||
@@ -693,6 +579,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
}
|
||||
|
||||
private void restructure(boolean allowResize, @Nullable Reference<K, V> ref) {
|
||||
boolean needsResize;
|
||||
lock();
|
||||
try {
|
||||
int expectedCount = this.count.get();
|
||||
@@ -708,7 +595,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
|
||||
// Estimate new count, taking into account count inside lock and items that
|
||||
// will be purged.
|
||||
boolean needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold);
|
||||
needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold);
|
||||
boolean resizing = false;
|
||||
int restructureSize = this.references.length;
|
||||
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
|
||||
@@ -749,8 +636,8 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
while (ref != null) {
|
||||
if (!toPurge.contains(ref)) {
|
||||
Entry<K, V> entry = ref.get();
|
||||
// Also filter out null references that are now null:
|
||||
// They should be polled from the queue in a later restructure call.
|
||||
// Also filter out null references that are now null
|
||||
// they should be polled from the queue in a later restructure call.
|
||||
if (entry != null) {
|
||||
purgedRef = this.referenceManager.createReference(
|
||||
entry, ref.getHash(), purgedRef);
|
||||
@@ -762,7 +649,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
this.references[i] = purgedRef;
|
||||
}
|
||||
}
|
||||
this.count.set(newCount);
|
||||
this.count.set(Math.max(newCount, 0));
|
||||
}
|
||||
finally {
|
||||
unlock();
|
||||
|
||||
@@ -30,10 +30,6 @@ final class VirtualThreadDelegate {
|
||||
|
||||
private final Thread.Builder threadBuilder = Thread.ofVirtual();
|
||||
|
||||
public VirtualThreadDelegate() {
|
||||
// Matching constructor in dummy version, avoiding jar verification issues.
|
||||
}
|
||||
|
||||
public ThreadFactory virtualThreadFactory() {
|
||||
return this.threadBuilder.factory();
|
||||
}
|
||||
|
||||
@@ -429,14 +429,14 @@ class BridgeMethodResolverTests {
|
||||
}
|
||||
|
||||
|
||||
public abstract static class AbstractAdder<T extends Serializable> implements Adder<T> {
|
||||
public abstract static class AbstractDateAdder implements Adder<Date> {
|
||||
|
||||
@Override
|
||||
public abstract void add(T item);
|
||||
public abstract void add(Date date);
|
||||
}
|
||||
|
||||
|
||||
public static class DateAdder extends AbstractAdder<Date> {
|
||||
public static class DateAdder extends AbstractDateAdder {
|
||||
|
||||
@Override
|
||||
public void add(Date date) {
|
||||
|
||||
-44
@@ -337,21 +337,6 @@ class PathMatchingResourcePatternResolverTests {
|
||||
assertThat(result.replace("\\", "/")).contains("!!!!").contains("/lib/asset.jar!/assets/file.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaDashJarFindsAbsoluteClassPathManifestEntries() throws Exception {
|
||||
Path assetJar = this.temp.resolve("dependency").resolve("asset.jar");
|
||||
Files.createDirectories(assetJar.getParent());
|
||||
writeAssetJar(assetJar);
|
||||
writeApplicationJarWithAbsolutePath(this.temp.resolve("app.jar"), assetJar);
|
||||
String java = ProcessHandle.current().info().command().get();
|
||||
Process process = new ProcessBuilder(java, "-jar", "app.jar")
|
||||
.directory(this.temp.toFile())
|
||||
.start();
|
||||
assertThat(process.waitFor()).isZero();
|
||||
String result = StreamUtils.copyToString(process.getInputStream(), StandardCharsets.UTF_8);
|
||||
assertThat(result.replace("\\", "/")).contains("!!!!").contains("asset.jar!/assets/file.txt");
|
||||
}
|
||||
|
||||
private void writeAssetJar(Path path) throws Exception {
|
||||
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()))) {
|
||||
jar.putNextEntry(new ZipEntry("assets/"));
|
||||
@@ -407,35 +392,6 @@ class PathMatchingResourcePatternResolverTests {
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
|
||||
}
|
||||
|
||||
private void writeApplicationJarWithAbsolutePath(Path path, Path assetJar) throws Exception {
|
||||
Manifest manifest = new Manifest();
|
||||
Attributes mainAttributes = manifest.getMainAttributes();
|
||||
mainAttributes.put(Name.CLASS_PATH, buildSpringClassPath() + assetJar.toAbsolutePath());
|
||||
mainAttributes.put(Name.MAIN_CLASS, ClassPathManifestEntriesTestApplication.class.getName());
|
||||
mainAttributes.put(Name.MANIFEST_VERSION, "1.0");
|
||||
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()), manifest)) {
|
||||
String appClassResource = ClassUtils.convertClassNameToResourcePath(
|
||||
ClassPathManifestEntriesTestApplication.class.getName()) + ClassUtils.CLASS_FILE_SUFFIX;
|
||||
String folder = "";
|
||||
for (String name : appClassResource.split("/")) {
|
||||
if (!name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) {
|
||||
folder += name + "/";
|
||||
jar.putNextEntry(new ZipEntry(folder));
|
||||
jar.closeEntry();
|
||||
}
|
||||
else {
|
||||
jar.putNextEntry(new ZipEntry(folder + name));
|
||||
try (InputStream in = getClass().getResourceAsStream(name)) {
|
||||
in.transferTo(jar);
|
||||
}
|
||||
jar.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
assertThat(new FileSystemResource(path).exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
|
||||
}
|
||||
|
||||
private String buildSpringClassPath() throws Exception {
|
||||
return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") +
|
||||
copyClasses(LogFactory.class, "commons-logging");
|
||||
|
||||
-62
@@ -16,9 +16,6 @@
|
||||
|
||||
package org.springframework.core.task;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.ConcurrencyThrottleSupport;
|
||||
@@ -27,12 +24,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.willCallRealMethod;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rick Evans
|
||||
@@ -78,59 +69,6 @@ class SimpleAsyncTaskExecutorTests {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that when thread creation fails in doExecute() while concurrency
|
||||
* limiting is active, the concurrency permit is properly released to
|
||||
* prevent permanent deadlock.
|
||||
*
|
||||
* <p>This test reproduces a critical bug where OutOfMemoryError from
|
||||
* Thread.start() causes the executor to permanently deadlock:
|
||||
* <ol>
|
||||
* <li>beforeAccess() increments concurrencyCount
|
||||
* <li>doExecute() throws Error before thread starts
|
||||
* <li>TaskTrackingRunnable.run() never executes
|
||||
* <li>afterAccess() in finally block never called
|
||||
* <li>Subsequent tasks block forever in onLimitReached()
|
||||
* </ol>
|
||||
*
|
||||
* <p>Test approach: The first execute() should fail with some exception
|
||||
* (type doesn't matter - could be Error or TaskRejectedException).
|
||||
* The second execute() is the real test: it should complete without
|
||||
* deadlock if the permit was properly released.
|
||||
*/
|
||||
@Test
|
||||
void executeFailsToStartThreadReleasesConcurrencyPermit() throws InterruptedException {
|
||||
// Arrange
|
||||
SimpleAsyncTaskExecutor executor = spy(new SimpleAsyncTaskExecutor());
|
||||
executor.setConcurrencyLimit(1); // Enable concurrency limiting
|
||||
|
||||
Runnable task = () -> {};
|
||||
Error failure = new OutOfMemoryError("TEST: Cannot start thread");
|
||||
|
||||
// Simulate thread creation failure
|
||||
doThrow(failure).when(executor).doExecute(any(Runnable.class));
|
||||
|
||||
// Act - First execution fails
|
||||
// Both "before fix" (throws Error) and "after fix" (throws TaskRejectedException)
|
||||
// should throw some exception here - that's expected and correct
|
||||
assertThatThrownBy(() -> executor.execute(task))
|
||||
.isInstanceOf(Throwable.class);
|
||||
|
||||
// Arrange - Reset mock to allow second execution to succeed
|
||||
willCallRealMethod().given(executor).doExecute(any(Runnable.class));
|
||||
|
||||
// Assert - Second execution should NOT deadlock
|
||||
// This is the real test: if permit was leaked, this will timeout
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
executor.execute(() -> latch.countDown());
|
||||
|
||||
boolean completed = latch.await(1, TimeUnit.SECONDS);
|
||||
|
||||
assertThat(completed)
|
||||
.withFailMessage("Executor should not deadlock if concurrency permit was properly released after first failure")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadNameGetsSetCorrectly() {
|
||||
String customPrefix = "chankPop#";
|
||||
|
||||
@@ -868,49 +868,6 @@ class ClassUtilsTests {
|
||||
assertPubliclyAccessible(publiclyAccessibleMethod);
|
||||
}
|
||||
|
||||
@Test // gh-35667
|
||||
void staticMethodInPublicClass() throws Exception {
|
||||
Method originalMethod = PublicSuperclass.class.getMethod("getCacheKey");
|
||||
|
||||
// Prerequisite: method must be public static for this use case.
|
||||
assertPublic(originalMethod);
|
||||
assertStatic(originalMethod);
|
||||
|
||||
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
|
||||
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
|
||||
assertPubliclyAccessible(publiclyAccessibleMethod);
|
||||
}
|
||||
|
||||
@Test // gh-35667
|
||||
void publicSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
|
||||
Method originalMethod = PublicSubclass.class.getMethod("getCacheKey");
|
||||
|
||||
// Prerequisite: type must be public for this use case.
|
||||
assertPublic(originalMethod.getDeclaringClass());
|
||||
// Prerequisite: method must be public static for this use case.
|
||||
assertPublic(originalMethod);
|
||||
assertStatic(originalMethod);
|
||||
|
||||
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
|
||||
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
|
||||
assertPubliclyAccessible(publiclyAccessibleMethod);
|
||||
}
|
||||
|
||||
@Test // gh-35667
|
||||
void privateSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
|
||||
Method originalMethod = PrivateSubclass.class.getMethod("getCacheKey");
|
||||
|
||||
// Prerequisite: type must not be public for this use case.
|
||||
assertNotPublic(originalMethod.getDeclaringClass());
|
||||
// Prerequisite: method must be public static for this use case.
|
||||
assertPublic(originalMethod);
|
||||
assertStatic(originalMethod);
|
||||
|
||||
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
|
||||
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
|
||||
assertNotPubliclyAccessible(publiclyAccessibleMethod);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -957,10 +914,6 @@ class ClassUtilsTests {
|
||||
return Modifier.isPublic(member.getModifiers());
|
||||
}
|
||||
|
||||
private static void assertStatic(Member member) {
|
||||
assertThat(Modifier.isStatic(member.getModifiers())).as("%s must be static", member).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -1095,27 +1048,8 @@ class ClassUtilsTests {
|
||||
String greet(String name);
|
||||
}
|
||||
|
||||
public static class PublicSubclass extends PublicSuperclass {
|
||||
|
||||
/**
|
||||
* This method intentionally has the exact same signature as
|
||||
* {@link PublicSuperclass#getCacheKey()}.
|
||||
*/
|
||||
public static String getCacheKey() {
|
||||
return "child";
|
||||
}
|
||||
}
|
||||
|
||||
private static class PrivateSubclass extends PublicSuperclass implements PublicInterface, PrivateInterface {
|
||||
|
||||
/**
|
||||
* This method intentionally has the exact same signature as
|
||||
* {@link PublicSuperclass#getCacheKey()}.
|
||||
*/
|
||||
public static String getCacheKey() {
|
||||
return "child";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumber() {
|
||||
return 2;
|
||||
|
||||
+42
-82
@@ -53,7 +53,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
|
||||
|
||||
@Test
|
||||
void createWithDefaults() {
|
||||
void shouldCreateWithDefaults() {
|
||||
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>();
|
||||
assertThat(map.getSegmentsSize()).isEqualTo(16);
|
||||
assertThat(map.getSegment(0).getSize()).isEqualTo(1);
|
||||
@@ -61,7 +61,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithInitialCapacity() {
|
||||
void shouldCreateWithInitialCapacity() {
|
||||
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32);
|
||||
assertThat(map.getSegmentsSize()).isEqualTo(16);
|
||||
assertThat(map.getSegment(0).getSize()).isEqualTo(2);
|
||||
@@ -69,7 +69,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithInitialCapacityAndLoadFactor() {
|
||||
void shouldCreateWithInitialCapacityAndLoadFactor() {
|
||||
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32, 0.5f);
|
||||
assertThat(map.getSegmentsSize()).isEqualTo(16);
|
||||
assertThat(map.getSegment(0).getSize()).isEqualTo(2);
|
||||
@@ -77,7 +77,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithInitialCapacityAndConcurrentLevel() {
|
||||
void shouldCreateWithInitialCapacityAndConcurrentLevel() {
|
||||
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(16, 2);
|
||||
assertThat(map.getSegmentsSize()).isEqualTo(2);
|
||||
assertThat(map.getSegment(0).getSize()).isEqualTo(8);
|
||||
@@ -85,7 +85,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFullyCustom() {
|
||||
void shouldCreateFullyCustom() {
|
||||
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(5, 0.5f, 3);
|
||||
// concurrencyLevel of 3 ends up as 4 (nearest power of 2)
|
||||
assertThat(map.getSegmentsSize()).isEqualTo(4);
|
||||
@@ -95,28 +95,28 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonNegativeInitialCapacity() {
|
||||
void shouldNeedNonNegativeInitialCapacity() {
|
||||
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 1));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(-1, 1))
|
||||
.withMessageContaining("Initial capacity must not be negative");
|
||||
}
|
||||
|
||||
@Test
|
||||
void positiveLoadFactor() {
|
||||
void shouldNeedPositiveLoadFactor() {
|
||||
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 0.1f, 1));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 0.0f, 1))
|
||||
.withMessageContaining("Load factor must be positive");
|
||||
}
|
||||
|
||||
@Test
|
||||
void positiveConcurrencyLevel() {
|
||||
void shouldNeedPositiveConcurrencyLevel() {
|
||||
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(1, 1));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(1, 0))
|
||||
.withMessageContaining("Concurrency level must be positive");
|
||||
}
|
||||
|
||||
@Test
|
||||
void putAndGet() {
|
||||
void shouldPutAndGet() {
|
||||
// NOTE we are using mock references so we don't need to worry about GC
|
||||
assertThat(this.map).isEmpty();
|
||||
this.map.put(123, "123");
|
||||
@@ -129,14 +129,14 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceOnDoublePut() {
|
||||
void shouldReplaceOnDoublePut() {
|
||||
this.map.put(123, "321");
|
||||
this.map.put(123, "123");
|
||||
assertThat(this.map.get(123)).isEqualTo("123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void putNullKey() {
|
||||
void shouldPutNullKey() {
|
||||
assertThat(this.map.get(null)).isNull();
|
||||
assertThat(this.map.getOrDefault(null, "456")).isEqualTo("456");
|
||||
this.map.put(null, "123");
|
||||
@@ -145,7 +145,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void putNullValue() {
|
||||
void shouldPutNullValue() {
|
||||
assertThat(this.map.get(123)).isNull();
|
||||
assertThat(this.map.getOrDefault(123, "456")).isEqualTo("456");
|
||||
this.map.put(123, "321");
|
||||
@@ -157,12 +157,12 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithNoItems() {
|
||||
void shouldGetWithNoItems() {
|
||||
assertThat(this.map.get(123)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applySupplementalHash() {
|
||||
void shouldApplySupplementalHash() {
|
||||
Integer key = 123;
|
||||
this.map.put(key, "123");
|
||||
assertThat(this.map.getSupplementalHash()).isNotEqualTo(key.hashCode());
|
||||
@@ -170,7 +170,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFollowingNexts() {
|
||||
void shouldGetFollowingNexts() {
|
||||
// Use loadFactor to disable resize
|
||||
this.map = new TestWeakConcurrentCache<>(1, 10.0f, 1);
|
||||
this.map.put(1, "1");
|
||||
@@ -184,7 +184,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void resize() {
|
||||
void shouldResize() {
|
||||
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
|
||||
this.map.put(1, "1");
|
||||
assertThat(this.map.getSegment(0).getSize()).isEqualTo(1);
|
||||
@@ -214,7 +214,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void purgeOnGet() {
|
||||
void shouldPurgeOnGet() {
|
||||
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
this.map.put(i, String.valueOf(i));
|
||||
@@ -229,7 +229,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void purgeOnPut() {
|
||||
void shouldPurgeOnPut() {
|
||||
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
this.map.put(i, String.valueOf(i));
|
||||
@@ -245,28 +245,28 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void putIfAbsent() {
|
||||
void shouldPutIfAbsent() {
|
||||
assertThat(this.map.putIfAbsent(123, "123")).isNull();
|
||||
assertThat(this.map.putIfAbsent(123, "123b")).isEqualTo("123");
|
||||
assertThat(this.map.get(123)).isEqualTo("123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void putIfAbsentWithNullValue() {
|
||||
void shouldPutIfAbsentWithNullValue() {
|
||||
assertThat(this.map.putIfAbsent(123, null)).isNull();
|
||||
assertThat(this.map.putIfAbsent(123, "123")).isNull();
|
||||
assertThat(this.map.get(123)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void putIfAbsentWithNullKey() {
|
||||
void shouldPutIfAbsentWithNullKey() {
|
||||
assertThat(this.map.putIfAbsent(null, "123")).isNull();
|
||||
assertThat(this.map.putIfAbsent(null, "123b")).isEqualTo("123");
|
||||
assertThat(this.map.get(null)).isEqualTo("123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeKeyAndValue() {
|
||||
void shouldRemoveKeyAndValue() {
|
||||
this.map.put(123, "123");
|
||||
assertThat(this.map.remove(123, "456")).isFalse();
|
||||
assertThat(this.map.get(123)).isEqualTo("123");
|
||||
@@ -276,7 +276,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeKeyAndValueWithExistingNull() {
|
||||
void shouldRemoveKeyAndValueWithExistingNull() {
|
||||
this.map.put(123, null);
|
||||
assertThat(this.map.remove(123, "456")).isFalse();
|
||||
assertThat(this.map.get(123)).isNull();
|
||||
@@ -286,7 +286,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceOldValueWithNewValue() {
|
||||
void shouldReplaceOldValueWithNewValue() {
|
||||
this.map.put(123, "123");
|
||||
assertThat(this.map.replace(123, "456", "789")).isFalse();
|
||||
assertThat(this.map.get(123)).isEqualTo("123");
|
||||
@@ -295,7 +295,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceOldNullValueWithNewValue() {
|
||||
void shouldReplaceOldNullValueWithNewValue() {
|
||||
this.map.put(123, null);
|
||||
assertThat(this.map.replace(123, "456", "789")).isFalse();
|
||||
assertThat(this.map.get(123)).isNull();
|
||||
@@ -304,61 +304,21 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceValue() {
|
||||
void shouldReplaceValue() {
|
||||
this.map.put(123, "123");
|
||||
assertThat(this.map.replace(123, "456")).isEqualTo("123");
|
||||
assertThat(this.map.get(123)).isEqualTo("456");
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceNullValue() {
|
||||
void shouldReplaceNullValue() {
|
||||
this.map.put(123, null);
|
||||
assertThat(this.map.replace(123, "456")).isNull();
|
||||
assertThat(this.map.get(123)).isEqualTo("456");
|
||||
}
|
||||
|
||||
@Test
|
||||
void computeIfAbsent() {
|
||||
assertThat(this.map.computeIfAbsent(123, k -> "123")).isEqualTo("123");
|
||||
assertThat(this.map.computeIfAbsent(123, k -> "123b")).isEqualTo("123");
|
||||
assertThat(this.map.get(123)).isEqualTo("123");
|
||||
this.map.remove(123);
|
||||
assertThat(this.map.computeIfAbsent(123, k -> null)).isNull();
|
||||
assertThat(this.map.containsKey(123)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void computeIfPresent() {
|
||||
assertThat(this.map.computeIfPresent(123, (k, v) -> "123")).isNull();
|
||||
this.map.put(123, "123");
|
||||
assertThat(this.map.computeIfPresent(123, (k, v) -> v + "b")).isEqualTo("123b");
|
||||
assertThat(this.map.get(123)).isEqualTo("123b");
|
||||
assertThat(this.map.computeIfPresent(123, (k, v) -> null)).isNull();
|
||||
assertThat(this.map.containsKey(123)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void compute() {
|
||||
assertThat(this.map.compute(123, (k, v) -> "123" + v)).isEqualTo("123null");
|
||||
assertThat(this.map.compute(123, (k, v) -> null)).isNull();
|
||||
assertThat(this.map.compute(123, (k, v) -> null)).isNull();
|
||||
assertThat(this.map.compute(123, (k, v) -> "123")).isEqualTo("123");
|
||||
assertThat(this.map.compute(123, (k, v) -> v + "b")).isEqualTo("123b");
|
||||
assertThat(this.map.get(123)).isEqualTo("123b");
|
||||
}
|
||||
|
||||
@Test
|
||||
void merge() {
|
||||
assertThat(this.map.merge(123, "123", (v1, v2) -> v1 + v2)).isEqualTo("123");
|
||||
assertThat(this.map.merge(123, null, (v1, v2) -> v1 + v2)).isEqualTo("123null");
|
||||
assertThat(this.map.merge(123, null, (v1, v2) -> null)).isNull();
|
||||
assertThat(this.map.merge(123, "123", (v1, v2) -> v1 + v2)).isEqualTo("123");
|
||||
assertThat(this.map.merge(123, "b", (v1, v2) -> v1 + v2)).isEqualTo("123b");
|
||||
assertThat(this.map.get(123)).isEqualTo("123b");
|
||||
}
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
void shouldGetSize() {
|
||||
assertThat(this.map).isEmpty();
|
||||
this.map.put(123, "123");
|
||||
this.map.put(123, null);
|
||||
@@ -367,7 +327,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmpty() {
|
||||
void shouldSupportIsEmpty() {
|
||||
assertThat(this.map).isEmpty();
|
||||
this.map.put(123, "123");
|
||||
this.map.put(123, null);
|
||||
@@ -376,7 +336,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsKey() {
|
||||
void shouldContainKey() {
|
||||
assertThat(this.map.containsKey(123)).isFalse();
|
||||
assertThat(this.map.containsKey(456)).isFalse();
|
||||
this.map.put(123, "123");
|
||||
@@ -386,7 +346,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsValue() {
|
||||
void shouldContainValue() {
|
||||
assertThat(this.map.containsValue("123")).isFalse();
|
||||
assertThat(this.map.containsValue(null)).isFalse();
|
||||
this.map.put(123, "123");
|
||||
@@ -396,7 +356,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeWhenKeyIsInMap() {
|
||||
void shouldRemoveWhenKeyIsInMap() {
|
||||
this.map.put(123, null);
|
||||
this.map.put(456, "456");
|
||||
this.map.put(null, "789");
|
||||
@@ -407,14 +367,14 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeWhenKeyIsNotInMap() {
|
||||
void shouldRemoveWhenKeyIsNotInMap() {
|
||||
assertThat(this.map.remove(123)).isNull();
|
||||
assertThat(this.map.remove(null)).isNull();
|
||||
assertThat(this.map).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void putAll() {
|
||||
void shouldPutAll() {
|
||||
Map<Integer, String> m = new HashMap<>();
|
||||
m.put(123, "123");
|
||||
m.put(456, null);
|
||||
@@ -427,7 +387,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void clear() {
|
||||
void shouldClear() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
@@ -439,7 +399,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void keySet() {
|
||||
void shouldGetKeySet() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
@@ -451,7 +411,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void valuesCollection() {
|
||||
void shouldGetValues() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
@@ -466,7 +426,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntrySet() {
|
||||
void shouldGetEntrySet() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
@@ -478,7 +438,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntrySetFollowingNext() {
|
||||
void shouldGetEntrySetFollowingNext() {
|
||||
// Use loadFactor to disable resize
|
||||
this.map = new TestWeakConcurrentCache<>(1, 10.0f, 1);
|
||||
this.map.put(1, "1");
|
||||
@@ -492,7 +452,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeViaEntrySet() {
|
||||
void shouldRemoveViaEntrySet() {
|
||||
this.map.put(1, "1");
|
||||
this.map.put(2, "2");
|
||||
this.map.put(3, "3");
|
||||
@@ -508,7 +468,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void setViaEntrySet() {
|
||||
void shouldSetViaEntrySet() {
|
||||
this.map.put(1, "1");
|
||||
this.map.put(2, "2");
|
||||
this.map.put(3, "3");
|
||||
@@ -542,7 +502,7 @@ class ConcurrentReferenceHashMapTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportNullReference() {
|
||||
void shouldSupportNullReference() {
|
||||
// GC could happen during restructure so we must be able to create a reference for a null entry
|
||||
map.createReferenceManager().createReference(null, 1234, null);
|
||||
}
|
||||
|
||||
@@ -21,15 +21,6 @@ package org.springframework.util;
|
||||
*/
|
||||
public class PublicSuperclass {
|
||||
|
||||
/**
|
||||
* This method intentionally has the exact same signature as
|
||||
* {@link org.springframework.util.ClassUtilsTests.PublicSubclass#getCacheKey()} and
|
||||
* {@link org.springframework.util.ClassUtilsTests.PrivateSubclass#getCacheKey()}.
|
||||
*/
|
||||
public static String getCacheKey() {
|
||||
return "parent";
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return "goodbye";
|
||||
}
|
||||
|
||||
+8
-19
@@ -18,10 +18,8 @@ package org.springframework.jdbc.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
@@ -74,26 +72,17 @@ public class SortedResourcesFactoryBean extends AbstractFactoryBean<Resource[]>
|
||||
protected Resource[] createInstance() throws Exception {
|
||||
List<Resource> scripts = new ArrayList<>();
|
||||
for (String location : this.locations) {
|
||||
Resource[] resources = this.resourcePatternResolver.getResources(location);
|
||||
|
||||
// Cache URLs to avoid repeated I/O during sorting
|
||||
Map<Resource, String> urlCache = new LinkedHashMap<>(resources.length);
|
||||
for (Resource resource : resources) {
|
||||
List<Resource> resources = new ArrayList<>(
|
||||
Arrays.asList(this.resourcePatternResolver.getResources(location)));
|
||||
resources.sort((r1, r2) -> {
|
||||
try {
|
||||
urlCache.put(resource, resource.getURL().toString());
|
||||
return r1.getURL().toString().compareTo(r2.getURL().toString());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to resolve URL for resource [" + resource +
|
||||
"] from location pattern [" + location + "]", ex);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort using cached URLs
|
||||
List<Resource> sortedResources = new ArrayList<>(urlCache.keySet());
|
||||
sortedResources.sort(Comparator.comparing(urlCache::get));
|
||||
|
||||
scripts.addAll(sortedResources);
|
||||
});
|
||||
scripts.addAll(resources);
|
||||
}
|
||||
return scripts.toArray(new Resource[0]);
|
||||
}
|
||||
|
||||
+3
-42
@@ -126,8 +126,6 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
|
||||
private boolean enforceReadOnly = false;
|
||||
|
||||
private volatile @Nullable Boolean defaultReadOnly;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code DataSourceTransactionManager} instance.
|
||||
@@ -272,18 +270,13 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
|
||||
}
|
||||
if (definition.isReadOnly()) {
|
||||
checkDefaultReadOnly(newCon);
|
||||
}
|
||||
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
|
||||
}
|
||||
|
||||
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
|
||||
con = txObject.getConnectionHolder().getConnection();
|
||||
|
||||
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,
|
||||
definition.getIsolationLevel(),
|
||||
(definition.isReadOnly() && !isDefaultReadOnly()));
|
||||
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
|
||||
txObject.setPreviousIsolationLevel(previousIsolationLevel);
|
||||
txObject.setReadOnly(definition.isReadOnly());
|
||||
|
||||
@@ -388,9 +381,8 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
if (txObject.isMustRestoreAutoCommit()) {
|
||||
con.setAutoCommit(true);
|
||||
}
|
||||
DataSourceUtils.resetConnectionAfterTransaction(con,
|
||||
txObject.getPreviousIsolationLevel(),
|
||||
(txObject.isReadOnly() && !isDefaultReadOnly()));
|
||||
DataSourceUtils.resetConnectionAfterTransaction(
|
||||
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not reset JDBC Connection after transaction", ex);
|
||||
@@ -407,37 +399,6 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check the default {@link Connection#isReadOnly()} flag on a freshly
|
||||
* obtained connection from the {@code DataSource}, assuming that the
|
||||
* same flag applies to all connections obtained from the given setup.
|
||||
* @param newCon the Connection to check
|
||||
* @since 6.2.13
|
||||
* @see #isDefaultReadOnly()
|
||||
*/
|
||||
private void checkDefaultReadOnly(Connection newCon) {
|
||||
if (this.defaultReadOnly == null) {
|
||||
try {
|
||||
this.defaultReadOnly = newCon.isReadOnly();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not determine default JDBC Connection isReadOnly - assuming false", ex);
|
||||
this.defaultReadOnly = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the default read-only flag has been determined as {@code true},
|
||||
* assuming that all encountered connections will be read-only by default and
|
||||
* therefore do not need explicit {@link Connection#setReadOnly} (re)setting.
|
||||
* @since 6.2.13
|
||||
* @see #checkDefaultReadOnly(Connection)
|
||||
*/
|
||||
private boolean isDefaultReadOnly() {
|
||||
return (this.defaultReadOnly == Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the transactional {@code Connection} right after transaction begin.
|
||||
* <p>The default implementation executes a "SET TRANSACTION READ ONLY" statement
|
||||
|
||||
@@ -170,38 +170,19 @@ public abstract class DataSourceUtils {
|
||||
* @param definition the transaction definition to apply
|
||||
* @return the previous isolation level, if any
|
||||
* @throws SQLException if thrown by JDBC methods
|
||||
* @see #prepareConnectionForTransaction(Connection, int, boolean)
|
||||
*/
|
||||
@Nullable
|
||||
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
|
||||
throws SQLException {
|
||||
|
||||
return prepareConnectionForTransaction(con,
|
||||
(definition != null ? definition.getIsolationLevel() : TransactionDefinition.ISOLATION_DEFAULT),
|
||||
(definition != null && definition.isReadOnly()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given Connection with the given transaction semantics.
|
||||
* @param con the Connection to prepare
|
||||
* @param isolationLevel the isolation level to apply
|
||||
* @param setReadOnly whether to set the read-only flag
|
||||
* @return the previous isolation level, if any
|
||||
* @throws SQLException if thrown by JDBC methods
|
||||
* @since 6.2.13
|
||||
* @see #resetConnectionAfterTransaction(Connection, Integer, boolean)
|
||||
* @see #resetConnectionAfterTransaction
|
||||
* @see Connection#setTransactionIsolation
|
||||
* @see Connection#setReadOnly
|
||||
*/
|
||||
@Nullable
|
||||
static Integer prepareConnectionForTransaction(Connection con, int isolationLevel, boolean setReadOnly)
|
||||
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
|
||||
throws SQLException {
|
||||
|
||||
Assert.notNull(con, "No Connection specified");
|
||||
|
||||
boolean debugEnabled = logger.isDebugEnabled();
|
||||
// Set read-only flag.
|
||||
if (setReadOnly) {
|
||||
if (definition != null && definition.isReadOnly()) {
|
||||
try {
|
||||
if (debugEnabled) {
|
||||
logger.debug("Setting JDBC Connection [" + con + "] read-only");
|
||||
@@ -224,14 +205,15 @@ public abstract class DataSourceUtils {
|
||||
|
||||
// Apply specific isolation level, if any.
|
||||
Integer previousIsolationLevel = null;
|
||||
if (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
|
||||
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
|
||||
if (debugEnabled) {
|
||||
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " + isolationLevel);
|
||||
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
|
||||
definition.getIsolationLevel());
|
||||
}
|
||||
int currentIsolation = con.getTransactionIsolation();
|
||||
if (currentIsolation != isolationLevel) {
|
||||
if (currentIsolation != definition.getIsolationLevel()) {
|
||||
previousIsolationLevel = currentIsolation;
|
||||
con.setTransactionIsolation(isolationLevel);
|
||||
con.setTransactionIsolation(definition.getIsolationLevel());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -153,9 +153,6 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
|
||||
*/
|
||||
public void setReadOnlyDataSource(@Nullable DataSource readOnlyDataSource) {
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
if (getTargetDataSource() == null) {
|
||||
setTargetDataSource(readOnlyDataSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,7 +395,7 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
|
||||
return null;
|
||||
}
|
||||
case "isReadOnly" -> {
|
||||
return (this.readOnly || getTargetDataSource() == readOnlyDataSource);
|
||||
return this.readOnly;
|
||||
}
|
||||
case "setReadOnly" -> {
|
||||
this.readOnly = (Boolean) args[0];
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jdbc.support;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} implementation that registers runtime hints for
|
||||
* {@link JdbcUtils}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.2.13
|
||||
*/
|
||||
class JdbcUtilsRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
hints.reflection().registerType(Types.class, MemberCategory.PUBLIC_FIELDS);
|
||||
}
|
||||
|
||||
}
|
||||
+12
-12
@@ -80,43 +80,43 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
|
||||
|
||||
if (sqlEx instanceof SQLTransientException) {
|
||||
if (sqlEx instanceof SQLTransientConnectionException) {
|
||||
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
if (sqlEx instanceof SQLTransactionRollbackException) {
|
||||
if (SQLStateSQLExceptionTranslator.indicatesCannotAcquireLock(sqlEx.getSQLState())) {
|
||||
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
if (sqlEx instanceof SQLTimeoutException) {
|
||||
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
}
|
||||
else if (sqlEx instanceof SQLNonTransientException) {
|
||||
if (sqlEx instanceof SQLNonTransientConnectionException) {
|
||||
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
if (sqlEx instanceof SQLDataException) {
|
||||
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
if (sqlEx instanceof SQLIntegrityConstraintViolationException) {
|
||||
if (SQLStateSQLExceptionTranslator.indicatesDuplicateKey(sqlEx.getSQLState(), sqlEx.getErrorCode())) {
|
||||
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
if (sqlEx instanceof SQLInvalidAuthorizationSpecException) {
|
||||
return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
if (sqlEx instanceof SQLSyntaxErrorException) {
|
||||
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
|
||||
return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
|
||||
}
|
||||
if (sqlEx instanceof SQLFeatureNotSupportedException) {
|
||||
return new InvalidDataAccessApiUsageException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new InvalidDataAccessApiUsageException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
}
|
||||
else if (sqlEx instanceof SQLRecoverableException) {
|
||||
return new RecoverableDataAccessException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new RecoverableDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
|
||||
// Fallback to Spring's own SQL state translation...
|
||||
|
||||
+9
-9
@@ -131,35 +131,35 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
|
||||
logger.debug("Extracted SQL state class '" + classCode + "' from value '" + sqlState + "'");
|
||||
}
|
||||
if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) {
|
||||
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
|
||||
return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
|
||||
}
|
||||
else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) {
|
||||
if (indicatesDuplicateKey(sqlState, sqlEx.getErrorCode())) {
|
||||
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
else if (PESSIMISTIC_LOCKING_FAILURE_CODES.contains(classCode)) {
|
||||
if (indicatesCannotAcquireLock(sqlState)) {
|
||||
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) {
|
||||
if (indicatesQueryTimeout(sqlState)) {
|
||||
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) {
|
||||
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
}
|
||||
|
||||
// For MySQL: exception class name indicating a timeout?
|
||||
// (since MySQL doesn't throw the JDBC 4 SQLTimeoutException)
|
||||
if (sqlEx.getClass().getName().contains("Timeout")) {
|
||||
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex);
|
||||
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||
}
|
||||
|
||||
// Couldn't resolve anything proper - resort to UncategorizedSQLException.
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryRuntimeHints,\
|
||||
org.springframework.jdbc.support.JdbcUtilsRuntimeHints
|
||||
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryRuntimeHints
|
||||
|
||||
+764
-617
File diff suppressed because it is too large
Load Diff
+70
-45
@@ -25,7 +25,9 @@ import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManagerTests;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.TransactionSystemException;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
@@ -51,30 +53,36 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
|
||||
|
||||
@Override
|
||||
@Test
|
||||
protected void transactionWithExceptionOnCommit() throws Exception {
|
||||
protected void testTransactionWithExceptionOnCommit() throws Exception {
|
||||
willThrow(new SQLException("Cannot commit")).given(con).commit();
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
|
||||
// plain TransactionSystemException
|
||||
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() ->
|
||||
tt.executeWithoutResult(status -> {
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
// something transactional
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
verify(con).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void transactionWithDataAccessExceptionOnCommit() throws Exception {
|
||||
void testTransactionWithDataAccessExceptionOnCommit() throws Exception {
|
||||
willThrow(new SQLException("Cannot commit")).given(con).commit();
|
||||
((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task));
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
|
||||
// specific ConcurrencyFailureException
|
||||
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
|
||||
tt.executeWithoutResult(status -> {
|
||||
// something transactional
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
// something transactional
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
@@ -82,14 +90,17 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void transactionWithDataAccessExceptionOnCommitFromLazyExceptionTranslator() throws Exception {
|
||||
void testTransactionWithDataAccessExceptionOnCommitFromLazyExceptionTranslator() throws Exception {
|
||||
willThrow(new SQLException("Cannot commit", "40")).given(con).commit();
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
|
||||
// specific ConcurrencyFailureException
|
||||
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
|
||||
tt.executeWithoutResult(status -> {
|
||||
// something transactional
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
// something transactional
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
@@ -98,7 +109,7 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
|
||||
|
||||
@Override
|
||||
@Test
|
||||
protected void transactionWithExceptionOnCommitAndRollbackOnCommitFailure() throws Exception {
|
||||
protected void testTransactionWithExceptionOnCommitAndRollbackOnCommitFailure() throws Exception {
|
||||
willThrow(new SQLException("Cannot commit")).given(con).commit();
|
||||
|
||||
tm.setRollbackOnCommitFailure(true);
|
||||
@@ -106,9 +117,12 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
|
||||
|
||||
// plain TransactionSystemException
|
||||
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() ->
|
||||
tt.executeWithoutResult(status -> {
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
// something transactional
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
verify(con).rollback();
|
||||
@@ -117,14 +131,16 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
|
||||
|
||||
@Override
|
||||
@Test
|
||||
protected void transactionWithExceptionOnRollback() throws Exception {
|
||||
protected void testTransactionWithExceptionOnRollback() throws Exception {
|
||||
given(con.getAutoCommit()).willReturn(true);
|
||||
willThrow(new SQLException("Cannot rollback")).given(con).rollback();
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
|
||||
// plain TransactionSystemException
|
||||
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() ->
|
||||
tt.executeWithoutResult(status -> {
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
|
||||
assertThat(status.getTransactionName()).isEmpty();
|
||||
assertThat(status.hasTransaction()).isTrue();
|
||||
assertThat(status.isNewTransaction()).isTrue();
|
||||
@@ -135,6 +151,31 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
|
||||
status.setRollbackOnly();
|
||||
assertThat(status.isRollbackOnly()).isTrue();
|
||||
assertThat(status.isCompleted()).isFalse();
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
InOrder ordered = inOrder(con);
|
||||
ordered.verify(con).setAutoCommit(false);
|
||||
ordered.verify(con).rollback();
|
||||
ordered.verify(con).setAutoCommit(true);
|
||||
verify(con).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTransactionWithDataAccessExceptionOnRollback() throws Exception {
|
||||
given(con.getAutoCommit()).willReturn(true);
|
||||
willThrow(new SQLException("Cannot rollback")).given(con).rollback();
|
||||
((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task));
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
|
||||
// specific ConcurrencyFailureException
|
||||
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
|
||||
status.setRollbackOnly();
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
@@ -146,43 +187,27 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void transactionWithDataAccessExceptionOnRollback() throws Exception {
|
||||
given(con.getAutoCommit()).willReturn(true);
|
||||
willThrow(new SQLException("Cannot rollback")).given(con).rollback();
|
||||
((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task));
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
|
||||
// specific ConcurrencyFailureException
|
||||
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
|
||||
tt.executeWithoutResult(status -> status.setRollbackOnly()));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
InOrder ordered = inOrder(con);
|
||||
ordered.verify(con).setAutoCommit(false);
|
||||
ordered.verify(con).rollback();
|
||||
ordered.verify(con).setAutoCommit(true);
|
||||
verify(con).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void transactionWithDataAccessExceptionOnRollbackFromLazyExceptionTranslator() throws Exception {
|
||||
void testTransactionWithDataAccessExceptionOnRollbackFromLazyExceptionTranslator() throws Exception {
|
||||
given(con.getAutoCommit()).willReturn(true);
|
||||
willThrow(new SQLException("Cannot rollback", "40")).given(con).rollback();
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
|
||||
// specific ConcurrencyFailureException
|
||||
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
|
||||
tt.executeWithoutResult(status -> {
|
||||
assertThat(status.getTransactionName()).isEmpty();
|
||||
assertThat(status.hasTransaction()).isTrue();
|
||||
assertThat(status.isNewTransaction()).isTrue();
|
||||
assertThat(status.isNested()).isFalse();
|
||||
assertThat(status.hasSavepoint()).isFalse();
|
||||
assertThat(status.isReadOnly()).isFalse();
|
||||
assertThat(status.isRollbackOnly()).isFalse();
|
||||
status.setRollbackOnly();
|
||||
assertThat(status.isRollbackOnly()).isTrue();
|
||||
assertThat(status.isCompleted()).isFalse();
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
|
||||
assertThat(status.getTransactionName()).isEmpty();
|
||||
assertThat(status.hasTransaction()).isTrue();
|
||||
assertThat(status.isNewTransaction()).isTrue();
|
||||
assertThat(status.isNested()).isFalse();
|
||||
assertThat(status.hasSavepoint()).isFalse();
|
||||
assertThat(status.isReadOnly()).isFalse();
|
||||
assertThat(status.isRollbackOnly()).isFalse();
|
||||
status.setRollbackOnly();
|
||||
assertThat(status.isRollbackOnly()).isTrue();
|
||||
assertThat(status.isCompleted()).isFalse();
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jdbc.support;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Types;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.beans.factory.aot.AotServices;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JdbcUtilsRuntimeHints}.
|
||||
*/
|
||||
class JdbcUtilsRuntimeHintsTests {
|
||||
|
||||
private final RuntimeHints hints = new RuntimeHints();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
AotServices.factories().load(RuntimeHintsRegistrar.class)
|
||||
.forEach(registrar -> registrar.registerHints(this.hints,
|
||||
ClassUtils.getDefaultClassLoader()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sqlTypesShouldHaveFieldAccess() {
|
||||
for (Field field : Types.class.getFields()) {
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onField(field)).accepts(this.hints);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -181,7 +181,8 @@ class SQLStateSQLExceptionTranslatorTests {
|
||||
static void assertTranslation(DataAccessException dae, SQLException ex, Class<?> dataAccessExceptionType) {
|
||||
assertThat(dae).as("Specific translation must not result in null").isNotNull();
|
||||
assertThat(dae).as("Wrong DataAccessException type returned").isExactlyInstanceOf(dataAccessExceptionType);
|
||||
assertThat(dae.getCause()).as("The exact same original SQLException must be preserved").isSameAs(ex);
|
||||
assertThat(dae.getCause()).as("The exact same original SQLException must be preserved").isSameAs(
|
||||
ex instanceof BatchUpdateException bue ? bue.getNextException() : ex);
|
||||
}
|
||||
|
||||
static BatchUpdateException buildBatchUpdateException(@Nullable String sqlState, SQLException next) {
|
||||
|
||||
+5
-6
@@ -398,16 +398,15 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
|
||||
* Determine the PersistenceUnitInfo to use for the EntityManagerFactory
|
||||
* created by this bean.
|
||||
* <p>The default implementation reads in all persistence unit infos from
|
||||
* {@code persistence.xml}, as defined in the JPA specification, selecting a unit
|
||||
* by name. If no persistence unit name was specified, it takes the default one
|
||||
* if configured, or otherwise the first persistence unit as found by the reader.
|
||||
* {@code persistence.xml}, as defined in the JPA specification.
|
||||
* If no entity manager name was specified, it takes the first info in the
|
||||
* array as returned by the reader. Otherwise, it checks for a matching name.
|
||||
* @param persistenceUnitManager the PersistenceUnitManager to obtain from
|
||||
* @return the chosen PersistenceUnitInfo
|
||||
*/
|
||||
protected PersistenceUnitInfo determinePersistenceUnitInfo(PersistenceUnitManager persistenceUnitManager) {
|
||||
String persistenceUnitName = getPersistenceUnitName();
|
||||
if (persistenceUnitName != null) {
|
||||
return persistenceUnitManager.obtainPersistenceUnitInfo(persistenceUnitName);
|
||||
if (getPersistenceUnitName() != null) {
|
||||
return persistenceUnitManager.obtainPersistenceUnitInfo(getPersistenceUnitName());
|
||||
}
|
||||
else {
|
||||
return persistenceUnitManager.obtainDefaultPersistenceUnitInfo();
|
||||
|
||||
+17
-17
@@ -193,7 +193,7 @@ public class DefaultPersistenceUnitManager
|
||||
|
||||
/**
|
||||
* Set the {@link PersistenceManagedTypes} to use to build the list of managed types
|
||||
* for the default persistence unit, as an alternative to entity scanning.
|
||||
* as an alternative to entity scanning.
|
||||
* @param managedTypes the managed types
|
||||
* @since 6.0
|
||||
*/
|
||||
@@ -540,33 +540,33 @@ public class DefaultPersistenceUnitManager
|
||||
* @see #setPackagesToScan
|
||||
*/
|
||||
private SpringPersistenceUnitInfo buildDefaultPersistenceUnitInfo() {
|
||||
SpringPersistenceUnitInfo defaultUnit = new SpringPersistenceUnitInfo();
|
||||
SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo();
|
||||
if (this.defaultPersistenceUnitName != null) {
|
||||
defaultUnit.setPersistenceUnitName(this.defaultPersistenceUnitName);
|
||||
scannedUnit.setPersistenceUnitName(this.defaultPersistenceUnitName);
|
||||
}
|
||||
defaultUnit.setExcludeUnlistedClasses(true);
|
||||
scannedUnit.setExcludeUnlistedClasses(true);
|
||||
|
||||
if (this.managedTypes != null) {
|
||||
applyManagedTypes(defaultUnit, this.managedTypes);
|
||||
applyManagedTypes(scannedUnit, this.managedTypes);
|
||||
}
|
||||
else if (this.packagesToScan != null) {
|
||||
PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(
|
||||
this.resourcePatternResolver, this.managedClassNameFilter);
|
||||
applyManagedTypes(defaultUnit, scanner.scan(this.packagesToScan));
|
||||
applyManagedTypes(scannedUnit, scanner.scan(this.packagesToScan));
|
||||
}
|
||||
|
||||
if (this.mappingResources != null) {
|
||||
for (String mappingFileName : this.mappingResources) {
|
||||
defaultUnit.addMappingFileName(mappingFileName);
|
||||
scannedUnit.addMappingFileName(mappingFileName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Resource ormXml = getOrmXmlForDefaultPersistenceUnit();
|
||||
if (ormXml != null) {
|
||||
defaultUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
|
||||
if (defaultUnit.getPersistenceUnitRootUrl() == null) {
|
||||
scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
|
||||
if (scannedUnit.getPersistenceUnitRootUrl() == null) {
|
||||
try {
|
||||
defaultUnit.setPersistenceUnitRootUrl(
|
||||
scannedUnit.setPersistenceUnitRootUrl(
|
||||
PersistenceUnitReader.determinePersistenceUnitRootUrl(ormXml));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
@@ -576,7 +576,7 @@ public class DefaultPersistenceUnitManager
|
||||
}
|
||||
}
|
||||
|
||||
return defaultUnit;
|
||||
return scannedUnit;
|
||||
}
|
||||
|
||||
private void applyManagedTypes(SpringPersistenceUnitInfo scannedUnit, PersistenceManagedTypes managedTypes) {
|
||||
@@ -639,9 +639,9 @@ public class DefaultPersistenceUnitManager
|
||||
|
||||
|
||||
/**
|
||||
* Return the specified {@link MutablePersistenceUnitInfo} from this manager's cache
|
||||
* of processed persistence units, keeping it in the cache (i.e. not 'obtaining' it
|
||||
* for use but rather just accessing it for post-processing).
|
||||
* Return the specified PersistenceUnitInfo from this manager's cache
|
||||
* of processed persistence units, keeping it in the cache (i.e. not
|
||||
* 'obtaining' it for use but rather just accessing it for post-processing).
|
||||
* <p>This can be used in {@link #postProcessPersistenceUnitInfo} implementations,
|
||||
* detecting existing persistence units of the same name and potentially merging them.
|
||||
* @param persistenceUnitName the name of the desired persistence unit
|
||||
@@ -654,12 +654,12 @@ public class DefaultPersistenceUnitManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook method allowing subclasses to customize each {@link MutablePersistenceUnitInfo}.
|
||||
* Hook method allowing subclasses to customize each PersistenceUnitInfo.
|
||||
* <p>The default implementation delegates to all registered PersistenceUnitPostProcessors.
|
||||
* It is usually preferable to register further entity classes, jar files etc there
|
||||
* rather than in a subclass of this manager, to be able to reuse the post-processors.
|
||||
* @param pui the chosen persistence unit configuration, as read from
|
||||
* {@code persistence.xml}. Passed in as MutablePersistenceUnitInfo.
|
||||
* @param pui the chosen PersistenceUnitInfo, as read from {@code persistence.xml}.
|
||||
* Passed in as MutablePersistenceUnitInfo.
|
||||
* @see #setPersistenceUnitPostProcessors
|
||||
*/
|
||||
protected void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
|
||||
|
||||
+3
-4
@@ -33,10 +33,9 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Spring's mutable equivalent of the JPA
|
||||
* Spring's base implementation of the JPA
|
||||
* {@link jakarta.persistence.spi.PersistenceUnitInfo} interface,
|
||||
* used to bootstrap an {@code EntityManagerFactory} in a container.
|
||||
* This is the type exposed to {@link PersistenceUnitPostProcessor}.
|
||||
*
|
||||
* <p>This implementation is largely a JavaBean, offering mutators
|
||||
* for all standard {@code PersistenceUnitInfo} properties.
|
||||
@@ -58,10 +57,10 @@ public class MutablePersistenceUnitInfo implements SmartPersistenceUnitInfo {
|
||||
private PersistenceUnitTransactionType transactionType;
|
||||
|
||||
@Nullable
|
||||
private DataSource jtaDataSource;
|
||||
private DataSource nonJtaDataSource;
|
||||
|
||||
@Nullable
|
||||
private DataSource nonJtaDataSource;
|
||||
private DataSource jtaDataSource;
|
||||
|
||||
private final List<String> mappingFileNames = new ArrayList<>();
|
||||
|
||||
|
||||
-1
@@ -57,7 +57,6 @@ public interface PersistenceManagedTypes {
|
||||
@Nullable
|
||||
URL getPersistenceUnitRootUrl();
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance using the specified managed class names.
|
||||
* @param managedClassNames the managed class names
|
||||
|
||||
+17
-37
@@ -64,13 +64,14 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||
|
||||
private static final boolean jpaPresent = ClassUtils.isPresent("jakarta.persistence.Entity",
|
||||
PersistenceManagedTypesBeanRegistrationAotProcessor.class.getClassLoader());
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
if (jpaPresent) {
|
||||
if (PersistenceManagedTypes.class.isAssignableFrom(registeredBean.getBeanClass())) {
|
||||
@@ -81,12 +82,12 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static final class JpaManagedTypesBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator {
|
||||
|
||||
private static final List<Class<? extends Annotation>> CALLBACK_TYPES = List.of(PreUpdate.class,
|
||||
PostUpdate.class, PrePersist.class, PostPersist.class, PreRemove.class, PostRemove.class, PostLoad.class);
|
||||
|
||||
|
||||
private static final ParameterizedTypeName LIST_OF_STRINGS_TYPE = ParameterizedTypeName.get(List.class, String.class);
|
||||
|
||||
private final RegisteredBean registeredBean;
|
||||
@@ -101,8 +102,8 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
|
||||
@Override
|
||||
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
|
||||
|
||||
BeanRegistrationCode beanRegistrationCode,
|
||||
boolean allowDirectSupplierShortcut) {
|
||||
PersistenceManagedTypes persistenceManagedTypes = this.registeredBean.getBeanFactory()
|
||||
.getBean(this.registeredBean.getBeanName(), PersistenceManagedTypes.class);
|
||||
contributeHints(generationContext.getRuntimeHints(),
|
||||
@@ -137,10 +138,9 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
contributeConverterHints(hints, managedClass);
|
||||
contributeCallbackHints(hints, managedClass);
|
||||
contributeHibernateHints(hints, classLoader, managedClass);
|
||||
contributePackagePrivateHints(hints, managedClass);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalArgumentException("Failed to instantiate JPA managed class: " + managedClassName, ex);
|
||||
throw new IllegalArgumentException("Failed to instantiate the managed class: " + managedClassName, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,8 +149,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
EntityListeners entityListeners = AnnotationUtils.findAnnotation(managedClass, EntityListeners.class);
|
||||
if (entityListeners != null) {
|
||||
for (Class<?> entityListener : entityListeners.value()) {
|
||||
hints.reflection().registerType(entityListener,
|
||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
hints.reflection().registerType(entityListener, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,14 +169,12 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
}
|
||||
Convert convertClassAnnotation = AnnotationUtils.findAnnotation(managedClass, Convert.class);
|
||||
if (convertClassAnnotation != null) {
|
||||
reflectionHints.registerType(convertClassAnnotation.converter(),
|
||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
reflectionHints.registerType(convertClassAnnotation.converter(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
}
|
||||
ReflectionUtils.doWithFields(managedClass, field -> {
|
||||
Convert convertFieldAnnotation = AnnotationUtils.findAnnotation(field, Convert.class);
|
||||
if (convertFieldAnnotation != null && convertFieldAnnotation.converter() != void.class) {
|
||||
reflectionHints.registerType(convertFieldAnnotation.converter(),
|
||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
reflectionHints.registerType(convertFieldAnnotation.converter(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -189,11 +186,11 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
method -> CALLBACK_TYPES.stream().anyMatch(method::isAnnotationPresent));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void contributeHibernateHints(RuntimeHints hints, @Nullable ClassLoader classLoader, Class<?> managedClass) {
|
||||
ReflectionHints reflection = hints.reflection();
|
||||
|
||||
Class<? extends Annotation> embeddableInstantiatorClass =
|
||||
loadClass("org.hibernate.annotations.EmbeddableInstantiator", classLoader);
|
||||
Class<? extends Annotation> embeddableInstantiatorClass = loadClass("org.hibernate.annotations.EmbeddableInstantiator", classLoader);
|
||||
if (embeddableInstantiatorClass != null) {
|
||||
registerForReflection(reflection,
|
||||
AnnotationUtils.findAnnotation(managedClass, embeddableInstantiatorClass), "value");
|
||||
@@ -207,8 +204,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
AnnotationUtils.findAnnotation(method, embeddableInstantiatorClass), "value"));
|
||||
}
|
||||
|
||||
Class<? extends Annotation> valueGenerationTypeClass =
|
||||
loadClass("org.hibernate.annotations.ValueGenerationType", classLoader);
|
||||
Class<? extends Annotation> valueGenerationTypeClass = loadClass("org.hibernate.annotations.ValueGenerationType", classLoader);
|
||||
if (valueGenerationTypeClass != null) {
|
||||
ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection,
|
||||
AnnotationUtils.findAnnotation(field, valueGenerationTypeClass), "generatedBy"));
|
||||
@@ -216,8 +212,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
AnnotationUtils.findAnnotation(method, valueGenerationTypeClass), "generatedBy"));
|
||||
}
|
||||
|
||||
Class<? extends Annotation> idGeneratorTypeClass =
|
||||
loadClass("org.hibernate.annotations.IdGeneratorType", classLoader);
|
||||
Class<? extends Annotation> idGeneratorTypeClass = loadClass("org.hibernate.annotations.IdGeneratorType", classLoader);
|
||||
if (idGeneratorTypeClass != null) {
|
||||
ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection,
|
||||
AnnotationUtils.findAnnotation(field, idGeneratorTypeClass), "value"));
|
||||
@@ -225,8 +220,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
AnnotationUtils.findAnnotation(method, idGeneratorTypeClass), "value"));
|
||||
}
|
||||
|
||||
Class<? extends Annotation> attributeBinderTypeClass =
|
||||
loadClass("org.hibernate.annotations.AttributeBinderType", classLoader);
|
||||
Class<? extends Annotation> attributeBinderTypeClass = loadClass("org.hibernate.annotations.AttributeBinderType", classLoader);
|
||||
if (attributeBinderTypeClass != null) {
|
||||
ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection,
|
||||
AnnotationUtils.findAnnotation(field, attributeBinderTypeClass), "binder"));
|
||||
@@ -235,19 +229,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
}
|
||||
}
|
||||
|
||||
private void contributePackagePrivateHints(RuntimeHints hints, Class<?> managedClass) {
|
||||
ReflectionHints reflection = hints.reflection();
|
||||
ReflectionUtils.doWithMethods(managedClass, method ->
|
||||
reflection.registerMethod(method, ExecutableMode.INVOKE),
|
||||
method -> {
|
||||
int modifiers = method.getModifiers();
|
||||
return !(java.lang.reflect.Modifier.isProtected(modifiers) ||
|
||||
java.lang.reflect.Modifier.isPrivate(modifiers) ||
|
||||
java.lang.reflect.Modifier.isPublic(modifiers));
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private static Class<? extends Annotation> loadClass(String className, @Nullable ClassLoader classLoader) {
|
||||
try {
|
||||
@@ -258,14 +239,13 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullAway") // Not-null assertion performed in ReflectionHints.registerType
|
||||
@SuppressWarnings("NullAway")
|
||||
private void registerForReflection(ReflectionHints reflection, @Nullable Annotation annotation, String attribute) {
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
Class<?> type = (Class<?>) AnnotationUtils.getAnnotationAttributes(annotation).get(attribute);
|
||||
reflection.registerType(type, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
Class<?> embeddableInstantiatorClass = (Class<?>) AnnotationUtils.getAnnotationAttributes(annotation).get(attribute);
|
||||
reflection.registerType(embeddableInstantiatorClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -40,7 +40,6 @@ class SimplePersistenceManagedTypes implements PersistenceManagedTypes {
|
||||
|
||||
SimplePersistenceManagedTypes(List<String> managedClassNames, List<String> managedPackages,
|
||||
@Nullable URL persistenceUnitRootUrl) {
|
||||
|
||||
this.managedClassNames = managedClassNames;
|
||||
this.managedPackages = managedPackages;
|
||||
this.persistenceUnitRootUrl = persistenceUnitRootUrl;
|
||||
@@ -50,7 +49,6 @@ class SimplePersistenceManagedTypes implements PersistenceManagedTypes {
|
||||
this(managedClassNames, managedPackages, null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> getManagedClassNames() {
|
||||
return this.managedClassNames;
|
||||
|
||||
+9
-7
@@ -20,6 +20,7 @@ import java.beans.PropertyDescriptor;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -428,7 +429,7 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar
|
||||
}
|
||||
|
||||
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
|
||||
Class<?> targetClass = ClassUtils.getUserClass(clazz);
|
||||
Class<?> targetClass = clazz;
|
||||
|
||||
do {
|
||||
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
|
||||
@@ -444,20 +445,21 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar
|
||||
});
|
||||
|
||||
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
|
||||
if (method.isBridge()) {
|
||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
|
||||
return;
|
||||
}
|
||||
if ((method.isAnnotationPresent(PersistenceContext.class) ||
|
||||
method.isAnnotationPresent(PersistenceUnit.class)) &&
|
||||
method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
if ((bridgedMethod.isAnnotationPresent(PersistenceContext.class) ||
|
||||
bridgedMethod.isAnnotationPresent(PersistenceUnit.class)) &&
|
||||
method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("Persistence annotations are not supported on static methods");
|
||||
}
|
||||
if (method.getParameterCount() != 1) {
|
||||
throw new IllegalStateException("Persistence annotation requires a single-arg method: " + method);
|
||||
}
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
currElements.add(new PersistenceElement(method, method, pd));
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
|
||||
currElements.add(new PersistenceElement(method, bridgedMethod, pd));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+10
-10
@@ -39,27 +39,27 @@ public abstract class AbstractEntityManagerFactoryBeanTests {
|
||||
|
||||
protected static EntityManagerFactory mockEmf;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
void setUp() {
|
||||
mockEmf = mock();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
void tearDown() {
|
||||
assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty();
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
|
||||
assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse();
|
||||
assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse();
|
||||
}
|
||||
|
||||
protected void checkInvariants(AbstractEntityManagerFactoryBean emfb) {
|
||||
assertThat(EntityManagerFactory.class.isAssignableFrom(emfb.getObjectType())).isTrue();
|
||||
EntityManagerFactory emf = emfb.getObject();
|
||||
assertThat(emf instanceof EntityManagerFactoryInfo).as("Object created by factory implements EntityManagerFactoryInfo").isTrue();
|
||||
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) emf;
|
||||
assertThat(emfb.getObject()).as("Successive invocations of getObject() return same object").isSameAs(emfi);
|
||||
assertThat(emfb.getObject()).isSameAs(emfi);
|
||||
protected void checkInvariants(AbstractEntityManagerFactoryBean demf) {
|
||||
assertThat(EntityManagerFactory.class.isAssignableFrom(demf.getObjectType())).isTrue();
|
||||
Object gotObject = demf.getObject();
|
||||
boolean condition = gotObject instanceof EntityManagerFactoryInfo;
|
||||
assertThat(condition).as("Object created by factory implements EntityManagerFactoryInfo").isTrue();
|
||||
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) demf.getObject();
|
||||
assertThat(demf.getObject()).as("Successive invocations of getObject() return same object").isSameAs(emfi);
|
||||
assertThat(demf.getObject()).isSameAs(emfi);
|
||||
assertThat(mockEmf).isSameAs(emfi.getNativeEntityManagerFactory());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.orm.jpa.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class Car {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
@Column
|
||||
private String model;
|
||||
|
||||
Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
String getModel() {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
+20
-32
@@ -38,7 +38,6 @@ import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.domain.Car;
|
||||
import org.springframework.orm.jpa.domain.DriversLicense;
|
||||
import org.springframework.orm.jpa.domain.Employee;
|
||||
import org.springframework.orm.jpa.domain.EmployeeCategoryConverter;
|
||||
@@ -67,12 +66,13 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.registerBean(JpaDomainConfiguration.class);
|
||||
compile(context, (initializer, compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
|
||||
initializer);
|
||||
PersistenceManagedTypes persistenceManagedTypes = freshApplicationContext.getBean(
|
||||
"persistenceManagedTypes", PersistenceManagedTypes.class);
|
||||
assertThat(persistenceManagedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
DriversLicense.class.getName(), Person.class.getName(), Employee.class.getName(),
|
||||
EmployeeLocationConverter.class.getName(), Car.class.getName());
|
||||
EmployeeLocationConverter.class.getName());
|
||||
assertThat(persistenceManagedTypes.getManagedPackages()).isEmpty();
|
||||
assertThat(freshApplicationContext.getBean(
|
||||
JpaDomainConfiguration.class).scanningInvoked).isFalse();
|
||||
@@ -105,14 +105,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocation.class)
|
||||
.withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setId")
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getId")
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setModel")
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getModel")
|
||||
.invoke()).accepts(hints);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,7 +121,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(GenericApplicationContext applicationContext,
|
||||
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
|
||||
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
generator.processAheadOfTime(applicationContext, generationContext);
|
||||
@@ -140,7 +131,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
|
||||
private GenericApplicationContext toFreshApplicationContext(
|
||||
ApplicationContextInitializer<GenericApplicationContext> initializer) {
|
||||
|
||||
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
|
||||
initializer.initialize(freshApplicationContext);
|
||||
freshApplicationContext.refresh();
|
||||
@@ -154,6 +144,21 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
result.accept(generationContext.getRuntimeHints());
|
||||
}
|
||||
|
||||
public static class JpaDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
|
||||
|
||||
@Override
|
||||
protected String packageToScan() {
|
||||
return "org.springframework.orm.jpa.domain";
|
||||
}
|
||||
}
|
||||
|
||||
public static class HibernateDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
|
||||
|
||||
@Override
|
||||
protected String packageToScan() {
|
||||
return "org.springframework.orm.jpa.hibernate.domain";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class AbstractEntityManagerWithPackagesToScanConfiguration {
|
||||
|
||||
@@ -174,13 +179,13 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
@Bean
|
||||
public PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
|
||||
this.scanningInvoked = true;
|
||||
return new PersistenceManagedTypesScanner(resourceLoader).scan(packageToScan());
|
||||
return new PersistenceManagedTypesScanner(resourceLoader)
|
||||
.scan(packageToScan());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
|
||||
JpaVendorAdapter jpaVendorAdapter, PersistenceManagedTypes persistenceManagedTypes) {
|
||||
|
||||
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
entityManagerFactoryBean.setDataSource(dataSource);
|
||||
entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
|
||||
@@ -189,24 +194,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
}
|
||||
|
||||
protected abstract String packageToScan();
|
||||
}
|
||||
|
||||
|
||||
public static class JpaDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
|
||||
|
||||
@Override
|
||||
protected String packageToScan() {
|
||||
return "org.springframework.orm.jpa.domain";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class HibernateDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
|
||||
|
||||
@Override
|
||||
protected String packageToScan() {
|
||||
return "org.springframework.orm.jpa.hibernate.domain";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-3
@@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.testfixture.index.CandidateComponentsTestClassLoader;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.orm.jpa.domain.Car;
|
||||
import org.springframework.orm.jpa.domain.DriversLicense;
|
||||
import org.springframework.orm.jpa.domain.Employee;
|
||||
import org.springframework.orm.jpa.domain.EmployeeLocationConverter;
|
||||
@@ -53,7 +52,7 @@ class PersistenceManagedTypesScannerTests {
|
||||
PersistenceManagedTypes managedTypes = this.scanner.scan("org.springframework.orm.jpa.domain");
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
Person.class.getName(), DriversLicense.class.getName(), Employee.class.getName(),
|
||||
EmployeeLocationConverter.class.getName(), Car.class.getName());
|
||||
EmployeeLocationConverter.class.getName());
|
||||
assertThat(managedTypes.getManagedPackages()).isEmpty();
|
||||
}
|
||||
|
||||
@@ -67,7 +66,6 @@ class PersistenceManagedTypesScannerTests {
|
||||
verify(filter).matches(DriversLicense.class.getName());
|
||||
verify(filter).matches(Employee.class.getName());
|
||||
verify(filter).matches(EmployeeLocationConverter.class.getName());
|
||||
verify(filter).matches(Car.class.getName());
|
||||
verifyNoMoreInteractions(filter);
|
||||
}
|
||||
|
||||
|
||||
+21
-20
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import jakarta.persistence.spi.PersistenceUnitInfo;
|
||||
import jakarta.persistence.spi.PersistenceUnitTransactionType;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -53,7 +54,7 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/META-INF/persistence.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info).hasSize(1);
|
||||
@@ -70,7 +71,7 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-example1.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info).hasSize(1);
|
||||
@@ -84,7 +85,7 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-example2.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info).hasSize(1);
|
||||
@@ -102,7 +103,7 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-example3.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info).hasSize(1);
|
||||
@@ -128,7 +129,7 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-example4.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info).hasSize(1);
|
||||
@@ -152,7 +153,7 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-example5.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info).hasSize(1);
|
||||
@@ -182,11 +183,11 @@ class PersistenceXmlParsingTests {
|
||||
dataSourceLookup.setDataSources(dataSources);
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), dataSourceLookup);
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).hasSize(2);
|
||||
|
||||
SpringPersistenceUnitInfo pu1 = info[0];
|
||||
PersistenceUnitInfo pu1 = info[0];
|
||||
|
||||
assertThat(pu1.getPersistenceUnitName()).isEqualTo("pu1");
|
||||
|
||||
@@ -209,7 +210,7 @@ class PersistenceXmlParsingTests {
|
||||
|
||||
assertThat(pu1.excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse();
|
||||
|
||||
SpringPersistenceUnitInfo pu2 = info[1];
|
||||
PersistenceUnitInfo pu2 = info[1];
|
||||
|
||||
assertThat(pu2.getTransactionType()).isSameAs(PersistenceUnitTransactionType.JTA);
|
||||
assertThat(pu2.getPersistenceProviderClassName()).isEqualTo("com.acme.AcmePersistence");
|
||||
@@ -233,7 +234,7 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-example6.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
assertThat(info).hasSize(1);
|
||||
assertThat(info[0].getPersistenceUnitName()).isEqualTo("pu");
|
||||
assertThat(info[0].getProperties()).isEmpty();
|
||||
@@ -285,27 +286,27 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-exclude-1.0.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info.length).as("The number of persistence units is incorrect.").isEqualTo(4);
|
||||
|
||||
SpringPersistenceUnitInfo noExclude = info[0];
|
||||
PersistenceUnitInfo noExclude = info[0];
|
||||
assertThat(noExclude).as("noExclude should not be null.").isNotNull();
|
||||
assertThat(noExclude.getPersistenceUnitName()).as("noExclude name is not correct.").isEqualTo("NoExcludeElement");
|
||||
assertThat(noExclude.excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse();
|
||||
|
||||
SpringPersistenceUnitInfo emptyExclude = info[1];
|
||||
PersistenceUnitInfo emptyExclude = info[1];
|
||||
assertThat(emptyExclude).as("emptyExclude should not be null.").isNotNull();
|
||||
assertThat(emptyExclude.getPersistenceUnitName()).as("emptyExclude name is not correct.").isEqualTo("EmptyExcludeElement");
|
||||
assertThat(emptyExclude.excludeUnlistedClasses()).as("emptyExclude should be true.").isTrue();
|
||||
|
||||
SpringPersistenceUnitInfo trueExclude = info[2];
|
||||
PersistenceUnitInfo trueExclude = info[2];
|
||||
assertThat(trueExclude).as("trueExclude should not be null.").isNotNull();
|
||||
assertThat(trueExclude.getPersistenceUnitName()).as("trueExclude name is not correct.").isEqualTo("TrueExcludeElement");
|
||||
assertThat(trueExclude.excludeUnlistedClasses()).as("trueExclude should be true.").isTrue();
|
||||
|
||||
SpringPersistenceUnitInfo falseExclude = info[3];
|
||||
PersistenceUnitInfo falseExclude = info[3];
|
||||
assertThat(falseExclude).as("falseExclude should not be null.").isNotNull();
|
||||
assertThat(falseExclude.getPersistenceUnitName()).as("falseExclude name is not correct.").isEqualTo("FalseExcludeElement");
|
||||
assertThat(falseExclude.excludeUnlistedClasses()).as("falseExclude should be false.").isFalse();
|
||||
@@ -316,27 +317,27 @@ class PersistenceXmlParsingTests {
|
||||
PersistenceUnitReader reader = new PersistenceUnitReader(
|
||||
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
|
||||
String resource = "/org/springframework/orm/jpa/persistence-exclude-2.0.xml";
|
||||
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
|
||||
|
||||
assertThat(info).isNotNull();
|
||||
assertThat(info.length).as("The number of persistence units is incorrect.").isEqualTo(4);
|
||||
|
||||
SpringPersistenceUnitInfo noExclude = info[0];
|
||||
PersistenceUnitInfo noExclude = info[0];
|
||||
assertThat(noExclude).as("noExclude should not be null.").isNotNull();
|
||||
assertThat(noExclude.getPersistenceUnitName()).as("noExclude name is not correct.").isEqualTo("NoExcludeElement");
|
||||
assertThat(noExclude.excludeUnlistedClasses()).as("Exclude unlisted still defaults to false in 2.0.").isFalse();
|
||||
|
||||
SpringPersistenceUnitInfo emptyExclude = info[1];
|
||||
PersistenceUnitInfo emptyExclude = info[1];
|
||||
assertThat(emptyExclude).as("emptyExclude should not be null.").isNotNull();
|
||||
assertThat(emptyExclude.getPersistenceUnitName()).as("emptyExclude name is not correct.").isEqualTo("EmptyExcludeElement");
|
||||
assertThat(emptyExclude.excludeUnlistedClasses()).as("emptyExclude should be true.").isTrue();
|
||||
|
||||
SpringPersistenceUnitInfo trueExclude = info[2];
|
||||
PersistenceUnitInfo trueExclude = info[2];
|
||||
assertThat(trueExclude).as("trueExclude should not be null.").isNotNull();
|
||||
assertThat(trueExclude.getPersistenceUnitName()).as("trueExclude name is not correct.").isEqualTo("TrueExcludeElement");
|
||||
assertThat(trueExclude.excludeUnlistedClasses()).as("trueExclude should be true.").isTrue();
|
||||
|
||||
SpringPersistenceUnitInfo falseExclude = info[3];
|
||||
PersistenceUnitInfo falseExclude = info[3];
|
||||
assertThat(falseExclude).as("falseExclude should not be null.").isNotNull();
|
||||
assertThat(falseExclude.getPersistenceUnitName()).as("falseExclude name is not correct.").isEqualTo("FalseExcludeElement");
|
||||
assertThat(falseExclude.excludeUnlistedClasses()).as("falseExclude should be false.").isFalse();
|
||||
|
||||
@@ -46,6 +46,40 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="jibx-marshaller">
|
||||
<xsd:complexType>
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.oxm.jibx.JibxMarshaller">
|
||||
Defines a JiBX Marshaller. Deprecated as of Spring Framework 5.1.5!
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:exports type="org.springframework.oxm.jibx.JibxMarshaller"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="beans:identifiedType">
|
||||
<xsd:attribute name="target-class" type="classType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>The target class to be bound with JiBX.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="target-package" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>The target package for the JiBX binding.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="binding-name" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>The binding name used by this marshaller.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:simpleType name="classType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:java.lang.Class">A class supported by a marshaller.</xsd:documentation>
|
||||
|
||||
@@ -63,9 +63,9 @@ import org.springframework.web.util.WebUtils;
|
||||
*
|
||||
* <p>As of Spring 6.0, this set of mocks is designed on a Servlet 6.0 baseline.
|
||||
*
|
||||
* <p>Compatible with Servlet 6.0 but can be configured to expose a specific version
|
||||
* through {@link #setMajorVersion}/{@link #setMinorVersion}; default is 6.0.
|
||||
* Note that some Servlet SPI support is limited: servlet, filter and listener
|
||||
* <p>Compatible with Servlet 3.1 but can be configured to expose a specific version
|
||||
* through {@link #setMajorVersion}/{@link #setMinorVersion}; default is 3.1.
|
||||
* Note that Servlet 3.1 support is limited: servlet, filter and listener
|
||||
* registration methods are not supported; neither is JSP configuration.
|
||||
* We generally do not recommend to unit test your ServletContainerInitializers and
|
||||
* WebApplicationInitializers which is where those registration methods would be used.
|
||||
|
||||
@@ -98,20 +98,6 @@ public interface TestContext extends AttributeAccessor, Serializable {
|
||||
|
||||
/**
|
||||
* Get the {@linkplain Class test class} for this test context.
|
||||
* <p>Since JUnit Jupiter 5.12, if the
|
||||
* {@link org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
* SpringExtension} is used with a {@linkplain
|
||||
* org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope#TEST_METHOD
|
||||
* test-method scoped} {@link org.junit.jupiter.api.extension.ExtensionContext
|
||||
* ExtensionContext}, the {@code Class} returned from this method may refer
|
||||
* to the test class for the current {@linkplain #getTestMethod() test method},
|
||||
* which may be a {@link org.junit.jupiter.api.Nested @Nested} test class
|
||||
* within the class for the {@linkplain #getTestInstance() test instance}.
|
||||
* Thus, if you need consistent access to the class for the current test
|
||||
* instance within an implementation of
|
||||
* {@link TestExecutionListener#prepareTestInstance(TestContext)}, you should
|
||||
* invoke {@code testContext.getTestInstance().getClass()} instead of
|
||||
* {@code testContext.getTestClass()}.
|
||||
* @return the test class (never {@code null})
|
||||
*/
|
||||
Class<?> getTestClass();
|
||||
|
||||
@@ -150,19 +150,6 @@ public interface TestExecutionListener {
|
||||
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule
|
||||
* SpringMethodRule}). In any case, this method must be called prior to any
|
||||
* framework-specific lifecycle callbacks.
|
||||
* <p>Since JUnit Jupiter 5.12, if the
|
||||
* {@link org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
* SpringExtension} is used with a {@linkplain
|
||||
* org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope#TEST_METHOD
|
||||
* test-method scoped} {@link org.junit.jupiter.api.extension.ExtensionContext
|
||||
* ExtensionContext}, the {@link Class} returned from
|
||||
* {@link TestContext#getTestClass()} may refer to the test class for the
|
||||
* current {@linkplain TestContext#getTestMethod() test method}, which may be
|
||||
* a {@link org.junit.jupiter.api.Nested @Nested} test class within the class
|
||||
* for the {@linkplain TestContext#getTestInstance() test instance}. Thus, if
|
||||
* you need consistent access to the class for the current test instance, you
|
||||
* should invoke {@code testContext.getTestInstance().getClass()} instead of
|
||||
* {@code testContext.getTestClass()}.
|
||||
* <p>See the {@linkplain TestExecutionListener class-level documentation}
|
||||
* for details on wrapping behavior for listeners.
|
||||
* <p>The default implementation is <em>empty</em>. Can be overridden by
|
||||
|
||||
+3
-11
@@ -93,23 +93,15 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
|
||||
* a corresponding bean override instance.
|
||||
*/
|
||||
private static void injectFields(TestContext testContext) {
|
||||
Object testInstance = testContext.getTestInstance();
|
||||
// Since JUnit Jupiter 5.12, if the SpringExtension is used with Jupiter's
|
||||
// TEST_METHOD ExtensionContextScope, the value returned from
|
||||
// testContext.getTestClass() may refer to the declaring class of the test
|
||||
// method which is about to be invoked (which may be in a @Nested class
|
||||
// within the class for the test instance). Thus, we use the class for the
|
||||
// test instance as the "test class".
|
||||
Class<?> testClass = testInstance.getClass();
|
||||
|
||||
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(testClass);
|
||||
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(testContext.getTestClass());
|
||||
if (!handlers.isEmpty()) {
|
||||
Object testInstance = testContext.getTestInstance();
|
||||
ApplicationContext applicationContext = testContext.getApplicationContext();
|
||||
|
||||
Assert.state(applicationContext.containsBean(BeanOverrideRegistry.BEAN_NAME), () -> """
|
||||
Test class %s declares @BeanOverride fields %s, but no BeanOverrideHandler has been registered. \
|
||||
If you are using @ContextHierarchy, ensure that context names for bean overrides match \
|
||||
configured @ContextConfiguration names.""".formatted(testClass.getSimpleName(),
|
||||
configured @ContextConfiguration names.""".formatted(testContext.getTestClass().getSimpleName(),
|
||||
handlers.stream().map(BeanOverrideHandler::getField).filter(Objects::nonNull)
|
||||
.map(Field::getName).toList()));
|
||||
BeanOverrideRegistry beanOverrideRegistry = applicationContext.getBean(BeanOverrideRegistry.BEAN_NAME,
|
||||
|
||||
+9
-9
@@ -282,7 +282,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
* <ol>
|
||||
* <li>The {@linkplain ParameterContext#getDeclaringExecutable() declaring
|
||||
* executable} is a {@link Constructor} and
|
||||
* {@link TestConstructorUtils#isAutowirableConstructor(Executable, PropertyProvider)}
|
||||
* {@link TestConstructorUtils#isAutowirableConstructor(Constructor, Class, PropertyProvider)}
|
||||
* returns {@code true}. Note that {@code isAutowirableConstructor()} will be
|
||||
* invoked with a fallback {@link PropertyProvider} that delegates its lookup
|
||||
* to {@link ExtensionContext#getConfigurationParameter(String)}.</li>
|
||||
@@ -296,25 +296,25 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
* constructor. Consequently, no other registered {@link ParameterResolver}
|
||||
* will be able to resolve parameters.
|
||||
* @see #resolveParameter
|
||||
* @see TestConstructorUtils#isAutowirableConstructor(Executable, PropertyProvider)
|
||||
* @see TestConstructorUtils#isAutowirableConstructor(Constructor, Class)
|
||||
* @see ParameterResolutionDelegate#isAutowirable
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
|
||||
Parameter parameter = parameterContext.getParameter();
|
||||
Class<?> parameterType = parameter.getType();
|
||||
Executable executable = parameter.getDeclaringExecutable();
|
||||
Class<?> testClass = extensionContext.getRequiredTestClass();
|
||||
PropertyProvider junitPropertyProvider = propertyName ->
|
||||
extensionContext.getConfigurationParameter(propertyName).orElse(null);
|
||||
return (TestConstructorUtils.isAutowirableConstructor(executable, junitPropertyProvider) ||
|
||||
ApplicationContext.class.isAssignableFrom(parameterType) ||
|
||||
supportsApplicationEvents(parameterType, executable) ||
|
||||
return (TestConstructorUtils.isAutowirableConstructor(executable, testClass, junitPropertyProvider) ||
|
||||
ApplicationContext.class.isAssignableFrom(parameter.getType()) ||
|
||||
supportsApplicationEvents(parameterContext) ||
|
||||
ParameterResolutionDelegate.isAutowirable(parameter, parameterContext.getIndex()));
|
||||
}
|
||||
|
||||
private boolean supportsApplicationEvents(Class<?> parameterType, Executable executable) {
|
||||
if (ApplicationEvents.class.isAssignableFrom(parameterType)) {
|
||||
Assert.isTrue(executable instanceof Method,
|
||||
private boolean supportsApplicationEvents(ParameterContext parameterContext) {
|
||||
if (ApplicationEvents.class.isAssignableFrom(parameterContext.getParameter().getType())) {
|
||||
Assert.isTrue(parameterContext.getDeclaringExecutable() instanceof Method,
|
||||
"ApplicationEvents can only be injected into test and lifecycle methods");
|
||||
return true;
|
||||
}
|
||||
|
||||
+4
-60
@@ -78,7 +78,6 @@ public abstract class TestConstructorUtils {
|
||||
private TestConstructorUtils() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the supplied executable for the given test class is an
|
||||
* autowirable constructor.
|
||||
@@ -87,11 +86,8 @@ public abstract class TestConstructorUtils {
|
||||
* @param executable an executable for the test class
|
||||
* @param testClass the test class
|
||||
* @return {@code true} if the executable is an autowirable constructor
|
||||
* @see #isAutowirableConstructor(Executable, PropertyProvider)
|
||||
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
|
||||
* to be removed in Spring Framework 7.1
|
||||
* @see #isAutowirableConstructor(Executable, Class, PropertyProvider)
|
||||
*/
|
||||
@Deprecated(since = "6.2.13", forRemoval = true)
|
||||
public static boolean isAutowirableConstructor(Executable executable, Class<?> testClass) {
|
||||
return isAutowirableConstructor(executable, testClass, null);
|
||||
}
|
||||
@@ -105,10 +101,7 @@ public abstract class TestConstructorUtils {
|
||||
* @param testClass the test class
|
||||
* @return {@code true} if the constructor is autowirable
|
||||
* @see #isAutowirableConstructor(Constructor, Class, PropertyProvider)
|
||||
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
|
||||
* to be removed in Spring Framework 7.1
|
||||
*/
|
||||
@Deprecated(since = "6.2.13", forRemoval = true)
|
||||
public static boolean isAutowirableConstructor(Constructor<?> constructor, Class<?> testClass) {
|
||||
return isAutowirableConstructor(constructor, testClass, null);
|
||||
}
|
||||
@@ -126,10 +119,7 @@ public abstract class TestConstructorUtils {
|
||||
* @return {@code true} if the executable is an autowirable constructor
|
||||
* @since 5.3
|
||||
* @see #isAutowirableConstructor(Constructor, Class, PropertyProvider)
|
||||
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
|
||||
* to be removed in Spring Framework 7.1
|
||||
*/
|
||||
@Deprecated(since = "6.2.13", forRemoval = true)
|
||||
public static boolean isAutowirableConstructor(Executable executable, Class<?> testClass,
|
||||
@Nullable PropertyProvider fallbackPropertyProvider) {
|
||||
|
||||
@@ -158,62 +148,16 @@ public abstract class TestConstructorUtils {
|
||||
* {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}).</li>
|
||||
* </ol>
|
||||
* @param constructor a constructor for the test class
|
||||
* @param testClass the test class, typically the declaring class of the constructor
|
||||
* @param testClass the test class
|
||||
* @param fallbackPropertyProvider fallback property provider used to look up
|
||||
* the value for {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}
|
||||
* if no such value is found in {@link SpringProperties}; may be {@code null}
|
||||
* if there is no fallback support
|
||||
* the value for the default <em>test constructor autowire mode</em> if no
|
||||
* such value is found in {@link SpringProperties}
|
||||
* @return {@code true} if the constructor is autowirable
|
||||
* @since 5.3
|
||||
* @see #isAutowirableConstructor(Executable, PropertyProvider)
|
||||
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
|
||||
* to be removed in Spring Framework 7.1
|
||||
*/
|
||||
@Deprecated(since = "6.2.13", forRemoval = true)
|
||||
public static boolean isAutowirableConstructor(Constructor<?> constructor, Class<?> testClass,
|
||||
@Nullable PropertyProvider fallbackPropertyProvider) {
|
||||
|
||||
return isAutowirableConstructorInternal(constructor, testClass, fallbackPropertyProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link Executable} is an autowirable {@link Constructor}.
|
||||
*
|
||||
* <p>A constructor is considered to be autowirable if one of the following
|
||||
* conditions is {@code true}.
|
||||
*
|
||||
* <ol>
|
||||
* <li>The constructor is annotated with {@link Autowired @Autowired},
|
||||
* {@link jakarta.inject.Inject @jakarta.inject.Inject}, or
|
||||
* {@link javax.inject.Inject @javax.inject.Inject}.</li>
|
||||
* <li>{@link TestConstructor @TestConstructor} is <em>present</em> or
|
||||
* <em>meta-present</em> on the test class with
|
||||
* {@link TestConstructor#autowireMode() autowireMode} set to
|
||||
* {@link AutowireMode#ALL ALL}.</li>
|
||||
* <li>The default <em>test constructor autowire mode</em> has been set to
|
||||
* {@code ALL} in {@link SpringProperties} or in the supplied fallback
|
||||
* {@link PropertyProvider}.</li>
|
||||
* </ol>
|
||||
* @param executable an {@code Executable} for a test class
|
||||
* @param fallbackPropertyProvider fallback property provider used to look up
|
||||
* the value for {@value TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}
|
||||
* if no such value is found in {@link SpringProperties}; may be {@code null}
|
||||
* if there is no fallback support
|
||||
* @return {@code true} if the executable is an autowirable constructor
|
||||
* @since 6.2.13
|
||||
* @see TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME
|
||||
*/
|
||||
public static boolean isAutowirableConstructor(Executable executable,
|
||||
@Nullable PropertyProvider fallbackPropertyProvider) {
|
||||
|
||||
return (executable instanceof Constructor<?> constructor &&
|
||||
isAutowirableConstructorInternal(constructor, constructor.getDeclaringClass(), fallbackPropertyProvider));
|
||||
}
|
||||
|
||||
|
||||
private static boolean isAutowirableConstructorInternal(Constructor<?> constructor, Class<?> testClass,
|
||||
@Nullable PropertyProvider fallbackPropertyProvider) {
|
||||
|
||||
// Is the constructor annotated with @Autowired/@Inject?
|
||||
if (isAnnotatedWithAutowiredOrInject(constructor)) {
|
||||
return true;
|
||||
|
||||
@@ -200,8 +200,8 @@ class MockServletContextTests {
|
||||
assertThat(filterRegistrations).isNotNull();
|
||||
assertThat(filterRegistrations).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1.11
|
||||
@@ -242,6 +242,7 @@ class MockServletContextTests {
|
||||
assertThat(realPath).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-205
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.bean.override.convention;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
|
||||
/**
|
||||
* Integration tests for {@link TestBean} that use by-name lookup with
|
||||
* {@link ExtensionContextScope#TEST_METHOD}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.13
|
||||
*/
|
||||
public class TestBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests {
|
||||
|
||||
@Test
|
||||
void runTests() {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name())
|
||||
.selectors(selectClass(TestCase.class))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(12).succeeded(12).failed(0));
|
||||
}
|
||||
|
||||
|
||||
@SpringJUnitConfig
|
||||
public static class TestCase {
|
||||
|
||||
@TestBean(name = "field")
|
||||
String field;
|
||||
|
||||
@TestBean(name = "methodRenamed1", methodName = "field")
|
||||
String methodRenamed1;
|
||||
|
||||
static String field() {
|
||||
return "fieldOverride";
|
||||
}
|
||||
|
||||
static String nestedField() {
|
||||
return "nestedFieldOverride";
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
|
||||
assertThat(field).as("injection point").isEqualTo("fieldOverride");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
|
||||
assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride");
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
@DisplayName("With @TestBean in enclosing class and in @Nested class")
|
||||
public class TestBeanFieldInEnclosingClassTestCase {
|
||||
|
||||
@TestBean(name = "nestedField")
|
||||
String nestedField;
|
||||
|
||||
@TestBean(name = "methodRenamed2", methodName = "nestedField")
|
||||
String methodRenamed2;
|
||||
|
||||
|
||||
@Test
|
||||
void fieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
|
||||
assertThat(field).as("injection point").isEqualTo("fieldOverride");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
|
||||
assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedFieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
|
||||
assertThat(nestedField).isEqualTo("nestedFieldOverride");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
|
||||
assertThat(methodRenamed2).isEqualTo("nestedFieldOverride");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("With @TestBean in the enclosing classes")
|
||||
public class TestBeanFieldInEnclosingClassLevel2TestCase {
|
||||
|
||||
@Test
|
||||
void fieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
|
||||
assertThat(field).as("injection point").isEqualTo("fieldOverride");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
|
||||
assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedFieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
|
||||
assertThat(nestedField).isEqualTo("nestedFieldOverride");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
|
||||
assertThat(methodRenamed2).isEqualTo("nestedFieldOverride");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("With factory method in enclosing class")
|
||||
public class TestBeanFactoryMethodInEnclosingClassTestCase {
|
||||
|
||||
@TestBean(methodName = "nestedField", name = "nestedField")
|
||||
String nestedField;
|
||||
|
||||
@Test
|
||||
void nestedFieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
|
||||
assertThat(nestedField).isEqualTo("nestedFieldOverride");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("With factory method in the enclosing class of the enclosing class")
|
||||
public class TestBeanFactoryMethodInEnclosingClassLevel2TestCase {
|
||||
|
||||
@TestBean(methodName = "nestedField", name = "nestedNestedField")
|
||||
String nestedNestedField;
|
||||
|
||||
@Test
|
||||
void nestedFieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nestedNestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
|
||||
assertThat(nestedNestedField).isEqualTo("nestedFieldOverride");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean("field")
|
||||
String bean1() {
|
||||
return "prod";
|
||||
}
|
||||
|
||||
@Bean("nestedField")
|
||||
String bean2() {
|
||||
return "nestedProd";
|
||||
}
|
||||
|
||||
@Bean("methodRenamed1")
|
||||
String bean3() {
|
||||
return "Prod";
|
||||
}
|
||||
|
||||
@Bean("methodRenamed2")
|
||||
String bean4() {
|
||||
return "NestedProd";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-4
@@ -19,7 +19,6 @@ package org.springframework.test.context.bean.override.convention;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -29,15 +28,14 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link TestBean} that use by-name lookup with test class
|
||||
* {@link ExtensionContextScope}.
|
||||
* Integration tests for {@link TestBean} that use by-name lookup.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 6.2
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
public class TestBeanByNameLookupTestClassScopedExtensionContextIntegrationTests {
|
||||
public class TestBeanForByNameLookupIntegrationTests {
|
||||
|
||||
@TestBean(name = "field")
|
||||
String field;
|
||||
+1
-1
@@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 6.2
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
public class TestBeanByTypeLookupIntegrationTests {
|
||||
public class TestBeanForByTypeLookupIntegrationTests {
|
||||
|
||||
@TestBean
|
||||
MessageService messageService;
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.mockito.MockitoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean} that use by-name lookup with
|
||||
* {@link ExtensionContextScope#TEST_METHOD}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.13
|
||||
*/
|
||||
public class MockitoBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests {
|
||||
|
||||
@Test
|
||||
void runTests() {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name())
|
||||
.selectors(selectClass(TestCase.class))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(6).succeeded(6).failed(0));
|
||||
}
|
||||
|
||||
|
||||
@SpringJUnitConfig
|
||||
public static class TestCase {
|
||||
|
||||
@MockitoBean("field")
|
||||
ExampleService field;
|
||||
|
||||
@MockitoBean("nonExistingBean")
|
||||
ExampleService nonExisting;
|
||||
|
||||
|
||||
@Test
|
||||
void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(field);
|
||||
|
||||
assertThat(field.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nonExistingBean"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(nonExisting);
|
||||
|
||||
assertThat(nonExisting.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
@DisplayName("With @MockitoBean in enclosing class and in @Nested class")
|
||||
public class MockitoBeanNestedTestCase {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("field")
|
||||
ExampleService localField;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("nonExistingBean")
|
||||
ExampleService localNonExisting;
|
||||
|
||||
@MockitoBean("nestedField")
|
||||
ExampleService nestedField;
|
||||
|
||||
@MockitoBean("nestedNonExistingBean")
|
||||
ExampleService nestedNonExisting;
|
||||
|
||||
|
||||
@Test
|
||||
void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(localField);
|
||||
|
||||
assertThat(localField.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nonExistingBean"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(localNonExisting);
|
||||
|
||||
assertThat(localNonExisting.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedFieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nestedField"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(nestedField);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedFieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("nestedNonExistingBean"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(nestedNonExisting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean("field")
|
||||
ExampleService bean1() {
|
||||
return new RealExampleService("Hello Field");
|
||||
}
|
||||
|
||||
@Bean("nestedField")
|
||||
ExampleService bean2() {
|
||||
return new RealExampleService("Hello Nested Field");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-4
@@ -19,7 +19,6 @@ package org.springframework.test.context.bean.override.mockito;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -34,15 +33,14 @@ import org.springframework.test.mockito.MockitoAssertions;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean} that use by-name lookup with test class
|
||||
* {@link ExtensionContextScope}.
|
||||
* Integration tests for {@link MockitoBean} that use by-name lookup.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 6.2
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
public class MockitoBeanByNameLookupTestClassScopedExtensionContextIntegrationTests {
|
||||
public class MockitoBeanForByNameLookupIntegrationTests {
|
||||
|
||||
@MockitoBean("field")
|
||||
ExampleService field;
|
||||
+1
-1
@@ -47,7 +47,7 @@ import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||
* @since 6.2
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
public class MockitoBeanByTypeLookupIntegrationTests {
|
||||
public class MockitoBeanForByTypeLookupIntegrationTests {
|
||||
|
||||
@MockitoBean
|
||||
AnotherService serviceIsNotABean;
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.mockito.MockitoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoSpyBean} that use by-name lookup with
|
||||
* {@link ExtensionContextScope#TEST_METHOD}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.13
|
||||
*/
|
||||
public class MockitoSpyBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests {
|
||||
|
||||
@Test
|
||||
void runTests() {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name())
|
||||
.selectors(selectClass(TestCase.class))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(3).succeeded(3).failed(0));
|
||||
}
|
||||
|
||||
|
||||
@SpringJUnitConfig(Config.class)
|
||||
public static class TestCase {
|
||||
|
||||
@MockitoSpyBean("field1")
|
||||
ExampleService field;
|
||||
|
||||
|
||||
@Test
|
||||
void fieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field1"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(field);
|
||||
|
||||
assertThat(field.greeting()).isEqualTo("bean1");
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
@DisplayName("With @MockitoSpyBean in enclosing class and in @Nested class")
|
||||
public class MockitoSpyBeanNestedTestCase {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("field1")
|
||||
ExampleService localField;
|
||||
|
||||
@MockitoSpyBean("field2")
|
||||
ExampleService nestedField;
|
||||
|
||||
@Test
|
||||
void fieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field1"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(localField);
|
||||
|
||||
assertThat(localField.greeting()).isEqualTo("bean1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedFieldHasOverride(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("field2"))
|
||||
.isInstanceOf(ExampleService.class)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(nestedField);
|
||||
|
||||
assertThat(nestedField.greeting()).isEqualTo("bean2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean("field1")
|
||||
ExampleService bean1() {
|
||||
return new RealExampleService("bean1");
|
||||
}
|
||||
|
||||
@Bean("field2")
|
||||
ExampleService bean2() {
|
||||
return new RealExampleService("bean2");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+3
-5
@@ -19,7 +19,6 @@ package org.springframework.test.context.bean.override.mockito;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -28,22 +27,21 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.Config;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBeanForByNameLookupIntegrationTests.Config;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.mockito.MockitoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoSpyBean} that use by-name lookup with test class
|
||||
* {@link ExtensionContextScope}.
|
||||
* Integration tests for {@link MockitoSpyBean} that use by-name lookup.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 6.2
|
||||
*/
|
||||
@SpringJUnitConfig(Config.class)
|
||||
public class MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests {
|
||||
public class MockitoSpyBeanForByNameLookupIntegrationTests {
|
||||
|
||||
@MockitoSpyBean("field1")
|
||||
ExampleService field;
|
||||
+1
-1
@@ -42,7 +42,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
* @since 6.2
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
public class MockitoSpyBeanByTypeLookupIntegrationTests {
|
||||
public class MockitoSpyBeanForByTypeLookupIntegrationTests {
|
||||
|
||||
@MockitoSpyBean
|
||||
ExampleService anyNameForService;
|
||||
-1
@@ -72,7 +72,6 @@ class AfterTestClassSqlScriptsTests extends AbstractTransactionalTests {
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
@Sql(scripts = "recreate-schema.sql", executionPhase = BEFORE_TEST_CLASS)
|
||||
@Sql(scripts = "drop-schema.sql", executionPhase = AFTER_TEST_CLASS)
|
||||
class NestedAfterTestClassSqlScriptsTests {
|
||||
|
||||
+33
@@ -65,9 +65,11 @@ class AutowiredConfigurationErrorsIntegrationTests {
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {
|
||||
StaticAutowiredBeforeAllMethod.class,
|
||||
StaticAutowiredPrivateBeforeAllMethod.class,
|
||||
StaticAutowiredAfterAllMethod.class,
|
||||
AutowiredBeforeEachMethod.class,
|
||||
AutowiredAfterEachMethod.class,
|
||||
AutowiredPrivateAfterEachMethod.class,
|
||||
AutowiredTestMethod.class,
|
||||
AutowiredRepeatedTestMethod.class,
|
||||
AutowiredParameterizedTestMethod.class
|
||||
@@ -166,6 +168,21 @@ class AutowiredConfigurationErrorsIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@SpringJUnitConfig(Config.class)
|
||||
@FailingTestCase
|
||||
static class StaticAutowiredPrivateBeforeAllMethod {
|
||||
|
||||
@Autowired
|
||||
@BeforeAll
|
||||
private static void beforeAll(TestInfo testInfo) {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(DISPLAY_NAME)
|
||||
void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@SpringJUnitConfig(Config.class)
|
||||
@TestInstance(PER_CLASS)
|
||||
@FailingTestCase
|
||||
@@ -243,6 +260,22 @@ class AutowiredConfigurationErrorsIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@SpringJUnitConfig(Config.class)
|
||||
@FailingTestCase
|
||||
static class AutowiredPrivateAfterEachMethod {
|
||||
|
||||
@Test
|
||||
@DisplayName(DISPLAY_NAME)
|
||||
void test() {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@AfterEach
|
||||
private void afterEach(TestInfo testInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringJUnitConfig(Config.class)
|
||||
@FailingTestCase
|
||||
static class AutowiredTestMethod {
|
||||
|
||||
+5
-8
@@ -20,7 +20,6 @@ import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -31,7 +30,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.NestedTestConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.junit.jupiter.nested.ActiveProfilesTestClassScopedExtensionContextNestedTests.Config1;
|
||||
import org.springframework.test.context.junit.jupiter.nested.ActiveProfilesNestedTests.Config1;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT;
|
||||
@@ -40,8 +39,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
|
||||
/**
|
||||
* Integration tests that verify support for {@code @Nested} test classes using
|
||||
* {@link ActiveProfiles @ActiveProfiles} in conjunction with the
|
||||
* {@link SpringExtension} in a JUnit Jupiter environment with test class
|
||||
* {@link ExtensionContextScope}.
|
||||
* {@link SpringExtension} in a JUnit Jupiter environment.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3
|
||||
@@ -49,7 +47,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
|
||||
@SpringJUnitConfig(Config1.class)
|
||||
@ActiveProfiles("1")
|
||||
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
|
||||
class ActiveProfilesTestClassScopedExtensionContextNestedTests {
|
||||
class ActiveProfilesNestedTests {
|
||||
|
||||
@Autowired
|
||||
List<String> strings;
|
||||
@@ -71,9 +69,8 @@ class ActiveProfilesTestClassScopedExtensionContextNestedTests {
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(strings)
|
||||
.isEqualTo(this.localStrings)
|
||||
.containsExactlyInAnyOrder("X", "A1");
|
||||
assertThat(strings).containsExactlyInAnyOrder("X", "A1");
|
||||
assertThat(this.localStrings).containsExactlyInAnyOrder("X", "A1");
|
||||
}
|
||||
}
|
||||
|
||||
-236
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit.jupiter.nested;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.NestedTestConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT;
|
||||
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
|
||||
|
||||
/**
|
||||
* Integration tests that verify support for {@code @Nested} test classes using
|
||||
* {@link ActiveProfiles @ActiveProfiles} in conjunction with the
|
||||
* {@link SpringExtension} in a JUnit Jupiter environment with
|
||||
* {@link ExtensionContextScope#TEST_METHOD}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.13
|
||||
*/
|
||||
class ActiveProfilesTestMethodScopedExtensionContextNestedTests {
|
||||
|
||||
@Test
|
||||
void runTests() {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name())
|
||||
.selectors(selectClass(TestCase.class))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(7).succeeded(7).failed(0));
|
||||
}
|
||||
|
||||
|
||||
@SpringJUnitConfig(Config1.class)
|
||||
@ActiveProfiles("1")
|
||||
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
|
||||
static class TestCase {
|
||||
|
||||
@Autowired
|
||||
List<String> strings;
|
||||
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(this.strings).containsExactlyInAnyOrder("X", "A1");
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
@NestedTestConfiguration(INHERIT)
|
||||
class InheritedConfigTestCase {
|
||||
|
||||
@Autowired
|
||||
List<String> localStrings;
|
||||
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(strings)
|
||||
.isEqualTo(this.localStrings)
|
||||
.containsExactlyInAnyOrder("X", "A1");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SpringJUnitConfig(Config2.class)
|
||||
@ActiveProfiles("2")
|
||||
class ConfigOverriddenByDefaultTestCase {
|
||||
|
||||
@Autowired
|
||||
List<String> localStrings;
|
||||
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(strings)
|
||||
.isEqualTo(this.localStrings)
|
||||
.containsExactlyInAnyOrder("Y", "A2");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@NestedTestConfiguration(INHERIT)
|
||||
@ContextConfiguration(classes = Config2.class)
|
||||
@ActiveProfiles("2")
|
||||
class InheritedAndExtendedConfigTestCase {
|
||||
|
||||
@Autowired
|
||||
List<String> localStrings;
|
||||
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(strings)
|
||||
.isEqualTo(this.localStrings)
|
||||
.containsExactlyInAnyOrder("X", "A1", "Y", "A2");
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
@NestedTestConfiguration(OVERRIDE)
|
||||
@SpringJUnitConfig({ Config1.class, Config2.class, Config3.class })
|
||||
@ActiveProfiles("3")
|
||||
class DoubleNestedWithOverriddenConfigTestCase {
|
||||
|
||||
@Autowired
|
||||
List<String> localStrings;
|
||||
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(strings)
|
||||
.isEqualTo(this.localStrings)
|
||||
.containsExactlyInAnyOrder("X", "Y", "Z", "A3");
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
@NestedTestConfiguration(INHERIT)
|
||||
@ActiveProfiles(profiles = "2", inheritProfiles = false)
|
||||
class TripleNestedWithInheritedConfigButOverriddenProfilesTestCase {
|
||||
|
||||
@Autowired
|
||||
List<String> localStrings;
|
||||
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(strings)
|
||||
.isEqualTo(this.localStrings)
|
||||
.containsExactlyInAnyOrder("X", "Y", "Z", "A2");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@NestedTestConfiguration(INHERIT)
|
||||
class TripleNestedWithInheritedConfigAndTestInterfaceTestCase implements TestInterface {
|
||||
|
||||
@Autowired
|
||||
List<String> localStrings;
|
||||
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(strings)
|
||||
.isEqualTo(this.localStrings)
|
||||
.containsExactlyInAnyOrder("X", "Y", "Z", "A2", "A3");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Configuration
|
||||
static class Config1 {
|
||||
|
||||
@Bean
|
||||
String x() {
|
||||
return "X";
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("1")
|
||||
String a1() {
|
||||
return "A1";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config2 {
|
||||
|
||||
@Bean
|
||||
String y() {
|
||||
return "Y";
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("2")
|
||||
String a2() {
|
||||
return "A2";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config3 {
|
||||
|
||||
@Bean
|
||||
String z() {
|
||||
return "Z";
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("3")
|
||||
String a3() {
|
||||
return "A3";
|
||||
}
|
||||
}
|
||||
|
||||
@ActiveProfiles("2")
|
||||
interface TestInterface {
|
||||
}
|
||||
|
||||
}
|
||||
+6
-8
@@ -19,7 +19,6 @@ package org.springframework.test.context.junit.jupiter.nested;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -29,29 +28,28 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.NestedTestConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionTestClassScopedExtensionContextNestedTests.TopLevelConfig;
|
||||
import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionNestedTests.TopLevelConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
|
||||
|
||||
/**
|
||||
* Integration tests that verify support for {@code @Nested} test classes in conjunction
|
||||
* with the {@link SpringExtension} in a JUnit Jupiter environment with test class
|
||||
* {@link ExtensionContextScope} ... when using constructor injection as opposed
|
||||
* to field injection (see SPR-16653).
|
||||
* with the {@link SpringExtension} in a JUnit Jupiter environment ... when using
|
||||
* constructor injection as opposed to field injection (see SPR-16653).
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.0.5
|
||||
* @see ContextConfigurationTestClassScopedExtensionContextNestedTests
|
||||
* @see ContextConfigurationNestedTests
|
||||
* @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests
|
||||
*/
|
||||
@SpringJUnitConfig(TopLevelConfig.class)
|
||||
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
|
||||
class ConstructorInjectionTestClassScopedExtensionContextNestedTests {
|
||||
class ConstructorInjectionNestedTests {
|
||||
|
||||
final String foo;
|
||||
|
||||
ConstructorInjectionTestClassScopedExtensionContextNestedTests(TestInfo testInfo, @Autowired String foo) {
|
||||
ConstructorInjectionNestedTests(TestInfo testInfo, @Autowired String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit.jupiter.nested;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.NestedTestConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;
|
||||
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
|
||||
|
||||
/**
|
||||
* Integration tests that verify support for {@code @Nested} test classes in conjunction
|
||||
* with the {@link SpringExtension} in a JUnit Jupiter environment with
|
||||
* {@link ExtensionContextScope#TEST_METHOD} ... when using constructor injection
|
||||
* as opposed to field injection (see SPR-16653).
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.13
|
||||
* @see ContextConfigurationTestClassScopedExtensionContextNestedTests
|
||||
* @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests
|
||||
*/
|
||||
class ConstructorInjectionTestMethodScopedExtensionContextNestedTests {
|
||||
|
||||
@Test
|
||||
void runTests() {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name())
|
||||
.selectors(selectClass(TestCase.class))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(5).succeeded(5).failed(0));
|
||||
}
|
||||
|
||||
|
||||
@SpringJUnitConfig(TopLevelConfig.class)
|
||||
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
|
||||
static class TestCase {
|
||||
|
||||
final String foo;
|
||||
|
||||
TestCase(TestInfo testInfo, @Autowired String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void topLevelTest() {
|
||||
assertThat(foo).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SpringJUnitConfig(NestedConfig.class)
|
||||
class AutowiredConstructorTestCase {
|
||||
|
||||
final String bar;
|
||||
|
||||
@Autowired
|
||||
AutowiredConstructorTestCase(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedTest() {
|
||||
assertThat(foo).isEqualTo("bar");
|
||||
assertThat(bar).isEqualTo("bar");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SpringJUnitConfig(NestedConfig.class)
|
||||
class AutowiredConstructorParameterTestCase {
|
||||
|
||||
final String bar;
|
||||
|
||||
AutowiredConstructorParameterTestCase(@Autowired String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedTest() {
|
||||
assertThat(foo).isEqualTo("bar");
|
||||
assertThat(bar).isEqualTo("bar");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SpringJUnitConfig(NestedConfig.class)
|
||||
class QualifiedConstructorParameterTestCase {
|
||||
|
||||
final String bar;
|
||||
|
||||
QualifiedConstructorParameterTestCase(TestInfo testInfo, @Qualifier("bar") String s) {
|
||||
this.bar = s;
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedTest() {
|
||||
assertThat(foo).isEqualTo("bar");
|
||||
assertThat(bar).isEqualTo("bar");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SpringJUnitConfig(NestedConfig.class)
|
||||
class SpelConstructorParameterTestCase {
|
||||
|
||||
final String bar;
|
||||
final int answer;
|
||||
|
||||
SpelConstructorParameterTestCase(@Autowired String bar, TestInfo testInfo, @Value("#{ 6 * 7 }") int answer) {
|
||||
this.bar = bar;
|
||||
this.answer = answer;
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedTest() {
|
||||
assertThat(foo).isEqualTo("bar");
|
||||
assertThat(bar).isEqualTo("bar");
|
||||
assertThat(answer).isEqualTo(42);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Configuration
|
||||
static class TopLevelConfig {
|
||||
|
||||
@Bean
|
||||
String foo() {
|
||||
return "foo";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class NestedConfig {
|
||||
|
||||
@Bean
|
||||
String bar() {
|
||||
return "bar";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+4
-6
@@ -18,7 +18,6 @@ package org.springframework.test.context.junit.jupiter.nested;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -28,7 +27,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.NestedTestConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.junit.jupiter.nested.ContextConfigurationTestClassScopedExtensionContextNestedTests.TopLevelConfig;
|
||||
import org.springframework.test.context.junit.jupiter.nested.ContextConfigurationNestedTests.TopLevelConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT;
|
||||
@@ -37,17 +36,16 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
|
||||
/**
|
||||
* Integration tests that verify support for {@code @Nested} test classes using
|
||||
* {@link ContextConfiguration @ContextConfiguration} in conjunction with the
|
||||
* {@link SpringExtension} in a JUnit Jupiter environment with test class
|
||||
* {@link ExtensionContextScope}.
|
||||
* {@link SpringExtension} in a JUnit Jupiter environment.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.0
|
||||
* @see ConstructorInjectionTestClassScopedExtensionContextNestedTests
|
||||
* @see ConstructorInjectionNestedTests
|
||||
* @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests
|
||||
*/
|
||||
@SpringJUnitConfig(TopLevelConfig.class)
|
||||
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
|
||||
class ContextConfigurationTestClassScopedExtensionContextNestedTests {
|
||||
class ContextConfigurationNestedTests {
|
||||
|
||||
private static final String FOO = "foo";
|
||||
private static final String BAR = "bar";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user