Skip to content

Commit 0637b69

Browse files
committed
Merge branch '6.1.x'
2 parents c3aa0cd + e702733 commit 0637b69

File tree

5 files changed

+126
-5
lines changed

5 files changed

+126
-5
lines changed

spring-core/src/main/java/org/springframework/core/MethodIntrospector.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
*
3737
* @author Juergen Hoeller
3838
* @author Rossen Stoyanchev
39+
* @author Sam Brannen
3940
* @since 4.2.3
4041
*/
4142
public final class MethodIntrospector {
@@ -75,6 +76,7 @@ public static <T> Map<Method, T> selectMethods(Class<?> targetType, final Metada
7576
if (result != null) {
7677
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
7778
if (bridgedMethod == specificMethod || bridgedMethod == method ||
79+
bridgedMethod.equals(specificMethod) || bridgedMethod.equals(method) ||
7880
metadataLookup.inspect(bridgedMethod) == null) {
7981
methodMap.put(specificMethod, result);
8082
}

spring-core/src/main/java/org/springframework/util/ClassUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
305305
}
306306
catch (ClassNotFoundException ex) {
307307
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
308-
int previousDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR, lastDotIndex -1);
308+
int previousDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR, lastDotIndex - 1);
309309
if (lastDotIndex != -1 && previousDotIndex != -1 && Character.isUpperCase(name.charAt(previousDotIndex + 1))) {
310310
String nestedClassName =
311311
name.substring(0, lastDotIndex) + NESTED_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);

spring-core/src/test/java/a/ClassHavingNestedClass.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,9 @@
1919
/**
2020
* Test class for {@code org.springframework.util.ClassUtilsTests}.
2121
*
22+
* <p>The use case for this test class requires that the package name is a single
23+
* character (i.e., length of 1).
24+
*
2225
* @author Johnny Lim
2326
*/
2427
public class ClassHavingNestedClass {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2002-2024 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;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
import java.lang.reflect.Method;
22+
import java.util.Map;
23+
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.core.MethodIntrospector.MetadataLookup;
27+
import org.springframework.core.annotation.MergedAnnotations;
28+
import org.springframework.util.ReflectionUtils;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.TYPE_HIERARCHY;
32+
33+
/**
34+
* Tests for {@link MethodIntrospector}.
35+
*
36+
* @author Sam Brannen
37+
* @since 5.3.34
38+
*/
39+
class MethodIntrospectorTests {
40+
41+
@Test // gh-32586
42+
void selectMethodsAndClearDeclaredMethodsCacheBetweenInvocations() {
43+
Class<?> targetType = ActualController.class;
44+
45+
// Preconditions for this use case.
46+
assertThat(targetType).isPublic();
47+
assertThat(targetType.getSuperclass()).isPackagePrivate();
48+
49+
MetadataLookup<String> metadataLookup = (MetadataLookup<String>) method -> {
50+
if (MergedAnnotations.from(method, TYPE_HIERARCHY).isPresent(Mapped.class)) {
51+
return method.getName();
52+
}
53+
return null;
54+
};
55+
56+
// Start with a clean slate.
57+
ReflectionUtils.clearCache();
58+
59+
// Round #1
60+
Map<Method, String> methods = MethodIntrospector.selectMethods(targetType, metadataLookup);
61+
assertThat(methods.values()).containsExactlyInAnyOrder("update", "delete");
62+
63+
// Simulate ConfigurableApplicationContext#refresh() which clears the
64+
// ReflectionUtils#declaredMethodsCache but NOT the BridgeMethodResolver#cache.
65+
// As a consequence, ReflectionUtils.getDeclaredMethods(...) will return a
66+
// new set of methods that are logically equivalent to but not identical
67+
// to (in terms of object identity) any bridged methods cached in the
68+
// BridgeMethodResolver cache.
69+
ReflectionUtils.clearCache();
70+
71+
// Round #2
72+
methods = MethodIntrospector.selectMethods(targetType, metadataLookup);
73+
assertThat(methods.values()).containsExactlyInAnyOrder("update", "delete");
74+
}
75+
76+
77+
@Retention(RetentionPolicy.RUNTIME)
78+
@interface Mapped {
79+
}
80+
81+
interface Controller {
82+
83+
void unmappedMethod();
84+
85+
@Mapped
86+
void update();
87+
88+
@Mapped
89+
void delete();
90+
}
91+
92+
// Must NOT be public.
93+
abstract static class AbstractController implements Controller {
94+
95+
@Override
96+
public void unmappedMethod() {
97+
}
98+
99+
@Override
100+
public void delete() {
101+
}
102+
}
103+
104+
// MUST be public.
105+
public static class ActualController extends AbstractController {
106+
107+
@Override
108+
public void update() {
109+
}
110+
}
111+
112+
}

spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Set;
3434
import java.util.function.Supplier;
3535

36+
import a.ClassHavingNestedClass;
3637
import org.junit.jupiter.api.BeforeEach;
3738
import org.junit.jupiter.api.Nested;
3839
import org.junit.jupiter.api.Test;
@@ -86,8 +87,11 @@ void forName() throws ClassNotFoundException {
8687
void forNameWithNestedType() throws ClassNotFoundException {
8788
assertThat(ClassUtils.forName("org.springframework.util.ClassUtilsTests$NestedClass", classLoader)).isEqualTo(NestedClass.class);
8889
assertThat(ClassUtils.forName("org.springframework.util.ClassUtilsTests.NestedClass", classLoader)).isEqualTo(NestedClass.class);
89-
assertThat(ClassUtils.forName("a.ClassHavingNestedClass$NestedClass", classLoader)).isEqualTo(a.ClassHavingNestedClass.NestedClass.class);
90-
assertThat(ClassUtils.forName("a.ClassHavingNestedClass.NestedClass", classLoader)).isEqualTo(a.ClassHavingNestedClass.NestedClass.class);
90+
91+
// Precondition: package name must have length == 1.
92+
assertThat(ClassHavingNestedClass.class.getPackageName().length()).isEqualTo(1);
93+
assertThat(ClassUtils.forName("a.ClassHavingNestedClass$NestedClass", classLoader)).isEqualTo(ClassHavingNestedClass.NestedClass.class);
94+
assertThat(ClassUtils.forName("a.ClassHavingNestedClass.NestedClass", classLoader)).isEqualTo(ClassHavingNestedClass.NestedClass.class);
9195
}
9296

9397
@Test

0 commit comments

Comments
 (0)