Skip to content

Commit 2bc7a3a

Browse files
committed
Implement equals, hashCode, & toString in BeanMethod and *Metadata types
Prior to this commit, ConfigurationClass implemented equals(), hashCode(), and toString(), but BeanMethod did not. This commit introduces equals(), hashCode(), and toString() implementations in BeanMethod for consistency with ConfigurationClass to make it possible to use BeanMethod instances to index additional metadata as well. In order to properly implement equals() in BeanMethod, the method argument types are required, but these are not directly available in BeanMethod. However, they are available via ASM when processing @bean methods. This commit therefore implements equals(), hashCode(), and toString() in SimpleMethodMetadata which BeanMethod delegates to. For completeness, this commit also implements equals(), hashCode(), and toString() in StandardClassMetadata, StandardMethodMetadata, and SimpleAnnotationMetadata. Closes gh-27076
1 parent 882004f commit 2bc7a3a

File tree

11 files changed

+451
-11
lines changed

11 files changed

+451
-11
lines changed

spring-context/src/main/java/org/springframework/context/annotation/BeanMethod.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
import org.springframework.beans.factory.parsing.Problem;
2020
import org.springframework.beans.factory.parsing.ProblemReporter;
2121
import org.springframework.core.type.MethodMetadata;
22+
import org.springframework.lang.Nullable;
2223

2324
/**
2425
* Represents a {@link Configuration @Configuration} class method annotated with
2526
* {@link Bean @Bean}.
2627
*
2728
* @author Chris Beams
2829
* @author Juergen Hoeller
30+
* @author Sam Brannen
2931
* @since 3.0
3032
* @see ConfigurationClass
3133
* @see ConfigurationClassParser
@@ -52,6 +54,21 @@ public void validate(ProblemReporter problemReporter) {
5254
}
5355
}
5456

57+
@Override
58+
public boolean equals(@Nullable Object obj) {
59+
return ((this == obj) || ((obj instanceof BeanMethod) &&
60+
this.metadata.equals(((BeanMethod) obj).metadata)));
61+
}
62+
63+
@Override
64+
public int hashCode() {
65+
return this.metadata.hashCode();
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return "BeanMethod: " + this.metadata;
71+
}
5572

5673
private class NonOverridableMethodError extends Problem {
5774

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/*
2+
* Copyright 2002-2021 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.context.annotation;
18+
19+
import java.util.Comparator;
20+
import java.util.List;
21+
import java.util.stream.Collectors;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
26+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
27+
import org.springframework.core.env.StandardEnvironment;
28+
import org.springframework.core.io.DefaultResourceLoader;
29+
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
/**
34+
* Integration tests for {@link ConfigurationClassParser}, {@link ConfigurationClass},
35+
* and {@link BeanMethod}.
36+
*
37+
* @author Sam Brannen
38+
* @since 5.3.9
39+
*/
40+
class ConfigurationClassAndBeanMethodTests {
41+
42+
@Test
43+
void verifyEquals() throws Exception {
44+
ConfigurationClass configurationClass1 = newConfigurationClass(Config1.class);
45+
ConfigurationClass configurationClass2 = newConfigurationClass(Config1.class);
46+
ConfigurationClass configurationClass3 = newConfigurationClass(Config2.class);
47+
48+
assertThat(configurationClass1.equals(null)).isFalse();
49+
assertThat(configurationClass1).isNotSameAs(configurationClass2);
50+
51+
assertThat(configurationClass1.equals(configurationClass1)).isTrue();
52+
assertThat(configurationClass2.equals(configurationClass2)).isTrue();
53+
assertThat(configurationClass1.equals(configurationClass2)).isTrue();
54+
assertThat(configurationClass2.equals(configurationClass1)).isTrue();
55+
56+
assertThat(configurationClass1.equals(configurationClass3)).isFalse();
57+
assertThat(configurationClass3.equals(configurationClass2)).isFalse();
58+
59+
// ---------------------------------------------------------------------
60+
61+
List<BeanMethod> beanMethods1 = getBeanMethods(configurationClass1);
62+
BeanMethod beanMethod_1_0 = beanMethods1.get(0);
63+
BeanMethod beanMethod_1_1 = beanMethods1.get(1);
64+
BeanMethod beanMethod_1_2 = beanMethods1.get(2);
65+
66+
List<BeanMethod> beanMethods2 = getBeanMethods(configurationClass2);
67+
BeanMethod beanMethod_2_0 = beanMethods2.get(0);
68+
BeanMethod beanMethod_2_1 = beanMethods2.get(1);
69+
BeanMethod beanMethod_2_2 = beanMethods2.get(2);
70+
71+
List<BeanMethod> beanMethods3 = getBeanMethods(configurationClass3);
72+
BeanMethod beanMethod_3_0 = beanMethods3.get(0);
73+
BeanMethod beanMethod_3_1 = beanMethods3.get(1);
74+
BeanMethod beanMethod_3_2 = beanMethods3.get(2);
75+
76+
assertThat(beanMethod_1_0.equals(null)).isFalse();
77+
assertThat(beanMethod_1_0).isNotSameAs(beanMethod_2_0);
78+
79+
assertThat(beanMethod_1_0.equals(beanMethod_1_0)).isTrue();
80+
assertThat(beanMethod_1_0.equals(beanMethod_2_0)).isTrue();
81+
assertThat(beanMethod_1_1.equals(beanMethod_2_1)).isTrue();
82+
assertThat(beanMethod_1_2.equals(beanMethod_2_2)).isTrue();
83+
84+
assertThat(beanMethod_1_0.getMetadata().getMethodName()).isEqualTo(beanMethod_3_0.getMetadata().getMethodName());
85+
assertThat(beanMethod_1_0.equals(beanMethod_3_0)).isFalse();
86+
assertThat(beanMethod_1_1.equals(beanMethod_3_1)).isFalse();
87+
assertThat(beanMethod_1_2.equals(beanMethod_3_2)).isFalse();
88+
}
89+
90+
@Test
91+
void verifyHashCode() throws Exception {
92+
ConfigurationClass configurationClass1 = newConfigurationClass(Config1.class);
93+
ConfigurationClass configurationClass2 = newConfigurationClass(Config1.class);
94+
ConfigurationClass configurationClass3 = newConfigurationClass(Config2.class);
95+
96+
assertThat(configurationClass1).hasSameHashCodeAs(configurationClass2);
97+
assertThat(configurationClass1).doesNotHaveSameHashCodeAs(configurationClass3);
98+
99+
// ---------------------------------------------------------------------
100+
101+
List<BeanMethod> beanMethods1 = getBeanMethods(configurationClass1);
102+
BeanMethod beanMethod_1_0 = beanMethods1.get(0);
103+
BeanMethod beanMethod_1_1 = beanMethods1.get(1);
104+
BeanMethod beanMethod_1_2 = beanMethods1.get(2);
105+
106+
List<BeanMethod> beanMethods2 = getBeanMethods(configurationClass2);
107+
BeanMethod beanMethod_2_0 = beanMethods2.get(0);
108+
BeanMethod beanMethod_2_1 = beanMethods2.get(1);
109+
BeanMethod beanMethod_2_2 = beanMethods2.get(2);
110+
111+
List<BeanMethod> beanMethods3 = getBeanMethods(configurationClass3);
112+
BeanMethod beanMethod_3_0 = beanMethods3.get(0);
113+
BeanMethod beanMethod_3_1 = beanMethods3.get(1);
114+
BeanMethod beanMethod_3_2 = beanMethods3.get(2);
115+
116+
assertThat(beanMethod_1_0).hasSameHashCodeAs(beanMethod_2_0);
117+
assertThat(beanMethod_1_1).hasSameHashCodeAs(beanMethod_2_1);
118+
assertThat(beanMethod_1_2).hasSameHashCodeAs(beanMethod_2_2);
119+
120+
assertThat(beanMethod_1_0).doesNotHaveSameHashCodeAs(beanMethod_3_0);
121+
assertThat(beanMethod_1_1).doesNotHaveSameHashCodeAs(beanMethod_3_1);
122+
assertThat(beanMethod_1_2).doesNotHaveSameHashCodeAs(beanMethod_3_2);
123+
}
124+
125+
@Test
126+
void verifyToString() throws Exception {
127+
ConfigurationClass configurationClass = newConfigurationClass(Config1.class);
128+
assertThat(configurationClass.toString())
129+
.startsWith("ConfigurationClass: beanName 'Config1', class path resource");
130+
131+
List<BeanMethod> beanMethods = getBeanMethods(configurationClass);
132+
String prefix = "BeanMethod: " + Config1.class.getName();
133+
assertThat(beanMethods.get(0).toString()).isEqualTo(prefix + ".bean0()");
134+
assertThat(beanMethods.get(1).toString()).isEqualTo(prefix + ".bean1(java.lang.String)");
135+
assertThat(beanMethods.get(2).toString()).isEqualTo(prefix + ".bean2(java.lang.String,java.lang.Integer)");
136+
}
137+
138+
139+
private static ConfigurationClass newConfigurationClass(Class<?> clazz) throws Exception {
140+
ConfigurationClassParser parser = newParser();
141+
parser.parse(clazz.getName(), clazz.getSimpleName());
142+
assertThat(parser.getConfigurationClasses()).hasSize(1);
143+
return parser.getConfigurationClasses().iterator().next();
144+
}
145+
146+
private static ConfigurationClassParser newParser() {
147+
return new ConfigurationClassParser(
148+
new CachingMetadataReaderFactory(),
149+
new FailFastProblemReporter(),
150+
new StandardEnvironment(),
151+
new DefaultResourceLoader(),
152+
new AnnotationBeanNameGenerator(),
153+
new DefaultListableBeanFactory());
154+
}
155+
156+
private static List<BeanMethod> getBeanMethods(ConfigurationClass configurationClass) {
157+
List<BeanMethod> beanMethods = configurationClass.getBeanMethods().stream()
158+
.sorted(Comparator.comparing(beanMethod -> beanMethod.getMetadata().getMethodName()))
159+
.collect(Collectors.toList());
160+
assertThat(beanMethods).hasSize(3);
161+
return beanMethods;
162+
}
163+
164+
static class Config1 {
165+
166+
@Bean
167+
String bean0() {
168+
return "";
169+
}
170+
171+
@Bean
172+
String bean1(String text) {
173+
return "";
174+
}
175+
176+
@Bean
177+
String bean2(String text, Integer num) {
178+
return "";
179+
}
180+
181+
}
182+
183+
static class Config2 {
184+
185+
@Bean
186+
String bean0() {
187+
return "";
188+
}
189+
190+
@Bean
191+
String bean1(String text) {
192+
return "";
193+
}
194+
195+
@Bean
196+
String bean2(String text, Integer num) {
197+
return "";
198+
}
199+
200+
}
201+
202+
}

spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 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.
@@ -28,6 +28,7 @@
2828
* to introspect a given {@code Class}.
2929
*
3030
* @author Juergen Hoeller
31+
* @author Sam Brannen
3132
* @since 2.5
3233
*/
3334
public class StandardClassMetadata implements ClassMetadata {
@@ -119,4 +120,20 @@ public String[] getMemberClassNames() {
119120
return StringUtils.toStringArray(memberClassNames);
120121
}
121122

123+
@Override
124+
public boolean equals(@Nullable Object obj) {
125+
return ((this == obj) || ((obj instanceof StandardClassMetadata) &&
126+
getIntrospectedClass().equals(((StandardClassMetadata) obj).getIntrospectedClass())));
127+
}
128+
129+
@Override
130+
public int hashCode() {
131+
return getIntrospectedClass().hashCode();
132+
}
133+
134+
@Override
135+
public String toString() {
136+
return getClassName();
137+
}
138+
122139
}

spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 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
* @author Mark Pollack
3737
* @author Chris Beams
3838
* @author Phillip Webb
39+
* @author Sam Brannen
3940
* @since 3.0
4041
*/
4142
public class StandardMethodMetadata implements MethodMetadata {
@@ -150,4 +151,20 @@ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotatio
150151
annotationName, classValuesAsString, false);
151152
}
152153

154+
@Override
155+
public boolean equals(@Nullable Object obj) {
156+
return ((this == obj) || ((obj instanceof StandardMethodMetadata) &&
157+
this.introspectedMethod.equals(((StandardMethodMetadata) obj).introspectedMethod)));
158+
}
159+
160+
@Override
161+
public int hashCode() {
162+
return this.introspectedMethod.hashCode();
163+
}
164+
165+
@Override
166+
public String toString() {
167+
return this.introspectedMethod.toString();
168+
}
169+
153170
}

spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 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.
@@ -31,6 +31,7 @@
3131
* {@link SimpleAnnotationMetadataReadingVisitor}.
3232
*
3333
* @author Phillip Webb
34+
* @author Sam Brannen
3435
* @since 5.2
3536
*/
3637
final class SimpleAnnotationMetadata implements AnnotationMetadata {
@@ -156,4 +157,20 @@ public MergedAnnotations getAnnotations() {
156157
return this.annotations;
157158
}
158159

160+
@Override
161+
public boolean equals(@Nullable Object obj) {
162+
return ((this == obj) || ((obj instanceof SimpleAnnotationMetadata) &&
163+
this.className.equals(((SimpleAnnotationMetadata) obj).className)));
164+
}
165+
166+
@Override
167+
public int hashCode() {
168+
return this.className.hashCode();
169+
}
170+
171+
@Override
172+
public String toString() {
173+
return this.className;
174+
}
175+
159176
}

0 commit comments

Comments
 (0)