Merge branch '6.0.x'

# Conflicts:
#	spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java
This commit is contained in:
Juergen Hoeller 2023-10-10 21:58:31 +02:00
commit de6692e7d8
6 changed files with 176 additions and 29 deletions

View File

@ -229,30 +229,27 @@ in the testing chapter for examples.
====
You can apply the `@Transactional` annotation to an interface definition, a method
on an interface, a class definition, or a method on a class. However, the
mere presence of the `@Transactional` annotation is not enough to activate the
transactional behavior. The `@Transactional` annotation is merely metadata that can
be consumed by some runtime infrastructure that is `@Transactional`-aware and that
can use the metadata to configure the appropriate beans with transactional behavior.
In the preceding example, the `<tx:annotation-driven/>` element switches on the
transactional behavior.
on an interface, a class definition, or a method on a class. However, the mere presence
of the `@Transactional` annotation is not enough to activate the transactional behavior.
The `@Transactional` annotation is merely metadata that can be consumed by corresponding
runtime infrastructure which uses that metadata to configure the appropriate beans with
transactional behavior. In the preceding example, the `<tx:annotation-driven/>` element
switches on actual transaction management at runtime.
TIP: The Spring team recommends that you annotate only concrete classes (and methods of
concrete classes) with the `@Transactional` annotation, as opposed to annotating interfaces.
You certainly can place the `@Transactional` annotation on an interface (or an interface
method), but this works only as you would expect it to if you use interface-based
proxies. The fact that Java annotations are not inherited from interfaces means that,
if you use class-based proxies (`proxy-target-class="true"`) or the weaving-based
aspect (`mode="aspectj"`), the transaction settings are not recognized by the proxying
and weaving infrastructure, and the object is not wrapped in a transactional proxy.
TIP: The Spring team recommends that you annotate methods of concrete classes with the
`@Transactional` annotation, rather than relying on annotated methods in interfaces,
even if the latter does work for interface-based and target-class proxies as of 5.0.
Since Java annotations are not inherited from interfaces, interface-declared annotations
are still not recognized by the weaving infrastructure when using AspectJ mode, so the
aspect does not get applied. As a consequence, your transaction annotations may be
silently ignored: Your code might appear to "work" until you test a rollback scenario.
NOTE: In proxy mode (which is the default), only external method calls coming in through
the proxy are intercepted. This means that self-invocation (in effect, a method within
the target object calling another method of the target object) does not lead to an actual
transaction at runtime even if the invoked method is marked with `@Transactional`. Also,
the proxy must be fully initialized to provide the expected behavior, so you should not
rely on this feature in your initialization code -- for example, in a `@PostConstruct`
method.
rely on this feature in your initialization code -- e.g. in a `@PostConstruct` method.
Consider using AspectJ mode (see the `mode` attribute in the following table) if you
expect self-invocations to be wrapped with transactions as well. In this case, there is
@ -277,20 +274,20 @@ is modified) to support `@Transactional` runtime behavior on any kind of method.
framework (following proxy semantics, as discussed earlier, applying to method calls
coming in through the proxy only). The alternative mode (`aspectj`) instead weaves the
affected classes with Spring's AspectJ transaction aspect, modifying the target class
byte code to apply to any kind of method call. AspectJ weaving requires
`spring-aspects.jar` in the classpath as well as having load-time weaving (or compile-time
weaving) enabled. (See xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[Spring configuration]
for details on how to set up load-time weaving.)
byte code to apply to any kind of method call. AspectJ weaving requires `spring-aspects.jar`
in the classpath as well as having load-time weaving (or compile-time weaving) enabled.
(See xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[Spring configuration] for details
on how to set up load-time weaving.)
| `proxy-target-class`
| `proxyTargetClass`
| `false`
| Applies to `proxy` mode only. Controls what type of transactional proxies are created
for classes annotated with the `@Transactional` annotation. If the
`proxy-target-class` attribute is set to `true`, class-based proxies are created.
If `proxy-target-class` is `false` or if the attribute is omitted, then standard JDK
interface-based proxies are created. (See xref:core/aop/proxying.adoc[Proxying Mechanisms]
for a detailed examination of the different proxy types.)
for classes annotated with the `@Transactional` annotation. If the `proxy-target-class`
attribute is set to `true`, class-based proxies are created. If `proxy-target-class` is
`false` or if the attribute is omitted, then standard JDK interface-based proxies are
created. (See xref:core/aop/proxying.adoc[Proxying Mechanisms] for a detailed examination
of the different proxy types.)
| `order`
| `order`

View File

@ -45,6 +45,7 @@ import org.springframework.util.StringUtils;
*
* @author Phillip Webb
* @author Sam Brannen
* @author Juergen Hoeller
* @since 5.2
* @see AnnotationTypeMappings
*/
@ -402,9 +403,11 @@ final class AnnotationTypeMapping {
if (type.isAnnotation() || (type.isArray() && type.componentType().isAnnotation())) {
Class<? extends Annotation> annotationType =
(Class<? extends Annotation>) (type.isAnnotation() ? type : type.componentType());
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
if (mapping.isSynthesizable()) {
return true;
if (annotationType != this.annotationType) {
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
if (mapping.isSynthesizable()) {
return true;
}
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
/**
* Tests for {@link MergedAnnotations} and {@link MergedAnnotation} in Kotlin.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 5.3.16
*/
class KotlinMergedAnnotationsTests {
@Test // gh-28012
fun recursiveAnnotationWithAlias() {
val method = javaClass.getMethod("personWithAliasMethod")
// MergedAnnotations
val mergedAnnotations = MergedAnnotations.from(method)
assertThat(mergedAnnotations.isPresent(PersonWithAlias::class.java)).isTrue();
// MergedAnnotation
val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(PersonWithAlias::class.java))
assertThat(mergedAnnotation).isNotNull();
// Synthesized Annotations
val jane = mergedAnnotation.synthesize()
assertThat(jane.value).isEqualTo("jane")
assertThat(jane.name).isEqualTo("jane")
val synthesizedFriends = jane.friends
assertThat(synthesizedFriends).hasSize(2)
val john = synthesizedFriends[0]
assertThat(john.value).isEqualTo("john")
assertThat(john.name).isEqualTo("john")
val sally = synthesizedFriends[1]
assertThat(sally.value).isEqualTo("sally")
assertThat(sally.name).isEqualTo("sally")
}
@Test // gh-31400
fun recursiveAnnotationWithoutAlias() {
val method = javaClass.getMethod("personWithoutAliasMethod")
// MergedAnnotations
val mergedAnnotations = MergedAnnotations.from(method)
assertThat(mergedAnnotations.isPresent(PersonWithoutAlias::class.java)).isTrue();
// MergedAnnotation
val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(PersonWithoutAlias::class.java))
assertThat(mergedAnnotation).isNotNull();
// Synthesized Annotations
val jane = mergedAnnotation.synthesize()
val synthesizedFriends = jane.friends
assertThat(synthesizedFriends).hasSize(2)
}
@PersonWithAlias("jane", friends = [PersonWithAlias("john"), PersonWithAlias("sally")])
fun personWithAliasMethod() {
}
@PersonWithoutAlias("jane", friends = [PersonWithoutAlias("john"), PersonWithoutAlias("sally")])
fun personWithoutAliasMethod() {
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class PersonWithAlias(
@get:AliasFor("name")
val value: String = "",
@get:AliasFor("value")
val name: String = "",
vararg val friends: PersonWithAlias = []
)

View File

@ -0,0 +1,29 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class PersonWithoutAlias(
val value: String = "",
val name: String = "",
vararg val friends: PersonWithoutAlias = []
)

View File

@ -480,6 +480,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
* @return a representation of the parsed SQL statement
*/
protected ParsedSql getParsedSql(String sql) {
Assert.notNull(sql, "SQL must not be null");
return this.parsedSqlCache.get(sql);
}