Skip to content

Commit de6692e

Browse files
committed
Merge branch '6.0.x'
# Conflicts: # spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java
2 parents d339bbb + 387a16b commit de6692e

File tree

6 files changed

+177
-30
lines changed

6 files changed

+177
-30
lines changed

framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -229,30 +229,27 @@ in the testing chapter for examples.
229229
====
230230

231231
You can apply the `@Transactional` annotation to an interface definition, a method
232-
on an interface, a class definition, or a method on a class. However, the
233-
mere presence of the `@Transactional` annotation is not enough to activate the
234-
transactional behavior. The `@Transactional` annotation is merely metadata that can
235-
be consumed by some runtime infrastructure that is `@Transactional`-aware and that
236-
can use the metadata to configure the appropriate beans with transactional behavior.
237-
In the preceding example, the `<tx:annotation-driven/>` element switches on the
238-
transactional behavior.
239-
240-
TIP: The Spring team recommends that you annotate only concrete classes (and methods of
241-
concrete classes) with the `@Transactional` annotation, as opposed to annotating interfaces.
242-
You certainly can place the `@Transactional` annotation on an interface (or an interface
243-
method), but this works only as you would expect it to if you use interface-based
244-
proxies. The fact that Java annotations are not inherited from interfaces means that,
245-
if you use class-based proxies (`proxy-target-class="true"`) or the weaving-based
246-
aspect (`mode="aspectj"`), the transaction settings are not recognized by the proxying
247-
and weaving infrastructure, and the object is not wrapped in a transactional proxy.
232+
on an interface, a class definition, or a method on a class. However, the mere presence
233+
of the `@Transactional` annotation is not enough to activate the transactional behavior.
234+
The `@Transactional` annotation is merely metadata that can be consumed by corresponding
235+
runtime infrastructure which uses that metadata to configure the appropriate beans with
236+
transactional behavior. In the preceding example, the `<tx:annotation-driven/>` element
237+
switches on actual transaction management at runtime.
238+
239+
TIP: The Spring team recommends that you annotate methods of concrete classes with the
240+
`@Transactional` annotation, rather than relying on annotated methods in interfaces,
241+
even if the latter does work for interface-based and target-class proxies as of 5.0.
242+
Since Java annotations are not inherited from interfaces, interface-declared annotations
243+
are still not recognized by the weaving infrastructure when using AspectJ mode, so the
244+
aspect does not get applied. As a consequence, your transaction annotations may be
245+
silently ignored: Your code might appear to "work" until you test a rollback scenario.
248246

249247
NOTE: In proxy mode (which is the default), only external method calls coming in through
250248
the proxy are intercepted. This means that self-invocation (in effect, a method within
251249
the target object calling another method of the target object) does not lead to an actual
252250
transaction at runtime even if the invoked method is marked with `@Transactional`. Also,
253251
the proxy must be fully initialized to provide the expected behavior, so you should not
254-
rely on this feature in your initialization code -- for example, in a `@PostConstruct`
255-
method.
252+
rely on this feature in your initialization code -- e.g. in a `@PostConstruct` method.
256253

257254
Consider using AspectJ mode (see the `mode` attribute in the following table) if you
258255
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.
277274
framework (following proxy semantics, as discussed earlier, applying to method calls
278275
coming in through the proxy only). The alternative mode (`aspectj`) instead weaves the
279276
affected classes with Spring's AspectJ transaction aspect, modifying the target class
280-
byte code to apply to any kind of method call. AspectJ weaving requires
281-
`spring-aspects.jar` in the classpath as well as having load-time weaving (or compile-time
282-
weaving) enabled. (See xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[Spring configuration]
283-
for details on how to set up load-time weaving.)
277+
byte code to apply to any kind of method call. AspectJ weaving requires `spring-aspects.jar`
278+
in the classpath as well as having load-time weaving (or compile-time weaving) enabled.
279+
(See xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[Spring configuration] for details
280+
on how to set up load-time weaving.)
284281

285282
| `proxy-target-class`
286283
| `proxyTargetClass`
287284
| `false`
288285
| Applies to `proxy` mode only. Controls what type of transactional proxies are created
289-
for classes annotated with the `@Transactional` annotation. If the
290-
`proxy-target-class` attribute is set to `true`, class-based proxies are created.
291-
If `proxy-target-class` is `false` or if the attribute is omitted, then standard JDK
292-
interface-based proxies are created. (See xref:core/aop/proxying.adoc[Proxying Mechanisms]
293-
for a detailed examination of the different proxy types.)
286+
for classes annotated with the `@Transactional` annotation. If the `proxy-target-class`
287+
attribute is set to `true`, class-based proxies are created. If `proxy-target-class` is
288+
`false` or if the attribute is omitted, then standard JDK interface-based proxies are
289+
created. (See xref:core/aop/proxying.adoc[Proxying Mechanisms] for a detailed examination
290+
of the different proxy types.)
294291

295292
| `order`
296293
| `order`

spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
*
4646
* @author Phillip Webb
4747
* @author Sam Brannen
48+
* @author Juergen Hoeller
4849
* @since 5.2
4950
* @see AnnotationTypeMappings
5051
*/
@@ -402,9 +403,11 @@ private boolean computeSynthesizableFlag() {
402403
if (type.isAnnotation() || (type.isArray() && type.componentType().isAnnotation())) {
403404
Class<? extends Annotation> annotationType =
404405
(Class<? extends Annotation>) (type.isAnnotation() ? type : type.componentType());
405-
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
406-
if (mapping.isSynthesizable()) {
407-
return true;
406+
if (annotationType != this.annotationType) {
407+
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
408+
if (mapping.isSynthesizable()) {
409+
return true;
410+
}
408411
}
409412
}
410413
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.annotation
18+
19+
import org.assertj.core.api.Assertions.assertThat
20+
import org.junit.jupiter.api.Test
21+
22+
/**
23+
* Tests for {@link MergedAnnotations} and {@link MergedAnnotation} in Kotlin.
24+
*
25+
* @author Sam Brannen
26+
* @author Juergen Hoeller
27+
* @since 5.3.16
28+
*/
29+
class KotlinMergedAnnotationsTests {
30+
31+
@Test // gh-28012
32+
fun recursiveAnnotationWithAlias() {
33+
val method = javaClass.getMethod("personWithAliasMethod")
34+
35+
// MergedAnnotations
36+
val mergedAnnotations = MergedAnnotations.from(method)
37+
assertThat(mergedAnnotations.isPresent(PersonWithAlias::class.java)).isTrue();
38+
39+
// MergedAnnotation
40+
val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(PersonWithAlias::class.java))
41+
assertThat(mergedAnnotation).isNotNull();
42+
43+
// Synthesized Annotations
44+
val jane = mergedAnnotation.synthesize()
45+
assertThat(jane.value).isEqualTo("jane")
46+
assertThat(jane.name).isEqualTo("jane")
47+
val synthesizedFriends = jane.friends
48+
assertThat(synthesizedFriends).hasSize(2)
49+
50+
val john = synthesizedFriends[0]
51+
assertThat(john.value).isEqualTo("john")
52+
assertThat(john.name).isEqualTo("john")
53+
54+
val sally = synthesizedFriends[1]
55+
assertThat(sally.value).isEqualTo("sally")
56+
assertThat(sally.name).isEqualTo("sally")
57+
}
58+
59+
@Test // gh-31400
60+
fun recursiveAnnotationWithoutAlias() {
61+
val method = javaClass.getMethod("personWithoutAliasMethod")
62+
63+
// MergedAnnotations
64+
val mergedAnnotations = MergedAnnotations.from(method)
65+
assertThat(mergedAnnotations.isPresent(PersonWithoutAlias::class.java)).isTrue();
66+
67+
// MergedAnnotation
68+
val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(PersonWithoutAlias::class.java))
69+
assertThat(mergedAnnotation).isNotNull();
70+
71+
// Synthesized Annotations
72+
val jane = mergedAnnotation.synthesize()
73+
val synthesizedFriends = jane.friends
74+
assertThat(synthesizedFriends).hasSize(2)
75+
}
76+
77+
78+
@PersonWithAlias("jane", friends = [PersonWithAlias("john"), PersonWithAlias("sally")])
79+
fun personWithAliasMethod() {
80+
}
81+
82+
@PersonWithoutAlias("jane", friends = [PersonWithoutAlias("john"), PersonWithoutAlias("sally")])
83+
fun personWithoutAliasMethod() {
84+
}
85+
86+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.annotation
18+
19+
@Target(AnnotationTarget.FUNCTION)
20+
@Retention(AnnotationRetention.RUNTIME)
21+
annotation class PersonWithAlias(
22+
23+
@get:AliasFor("name")
24+
val value: String = "",
25+
26+
@get:AliasFor("value")
27+
val name: String = "",
28+
29+
vararg val friends: PersonWithAlias = []
30+
31+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.annotation
18+
19+
@Target(AnnotationTarget.FUNCTION)
20+
@Retention(AnnotationRetention.RUNTIME)
21+
annotation class PersonWithoutAlias(
22+
23+
val value: String = "",
24+
25+
val name: String = "",
26+
27+
vararg val friends: PersonWithoutAlias = []
28+
29+
)

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ protected PreparedStatementCreator getPreparedStatementCreator(String sql, SqlPa
480480
* @return a representation of the parsed SQL statement
481481
*/
482482
protected ParsedSql getParsedSql(String sql) {
483+
Assert.notNull(sql, "SQL must not be null");
483484
return this.parsedSqlCache.get(sql);
484485
}
485486

0 commit comments

Comments
 (0)