Skip to content

Commit

Permalink
Implement equals, hashCode, & toString in BeanMethod and *Metadata types
Browse files Browse the repository at this point in the history
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 spring-projectsgh-27076
  • Loading branch information
sbrannen authored and lxbzmy committed Mar 26, 2022
1 parent 9e8c0ce commit dbaf59a
Show file tree
Hide file tree
Showing 11 changed files with 451 additions and 11 deletions.
Expand Up @@ -19,13 +19,15 @@
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.core.type.MethodMetadata;
import org.springframework.lang.Nullable;

/**
* Represents a {@link Configuration @Configuration} class method annotated with
* {@link Bean @Bean}.
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
* @see ConfigurationClass
* @see ConfigurationClassParser
Expand All @@ -52,6 +54,21 @@ public void validate(ProblemReporter problemReporter) {
}
}

@Override
public boolean equals(@Nullable Object obj) {
return ((this == obj) || ((obj instanceof BeanMethod) &&
this.metadata.equals(((BeanMethod) obj).metadata)));
}

@Override
public int hashCode() {
return this.metadata.hashCode();
}

@Override
public String toString() {
return "BeanMethod: " + this.metadata;
}

private class NonOverridableMethodError extends Problem {

Expand Down
@@ -0,0 +1,202 @@
/*
* Copyright 2002-2021 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.context.annotation;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Integration tests for {@link ConfigurationClassParser}, {@link ConfigurationClass},
* and {@link BeanMethod}.
*
* @author Sam Brannen
* @since 5.3.9
*/
class ConfigurationClassAndBeanMethodTests {

@Test
void verifyEquals() throws Exception {
ConfigurationClass configurationClass1 = newConfigurationClass(Config1.class);
ConfigurationClass configurationClass2 = newConfigurationClass(Config1.class);
ConfigurationClass configurationClass3 = newConfigurationClass(Config2.class);

assertThat(configurationClass1.equals(null)).isFalse();
assertThat(configurationClass1).isNotSameAs(configurationClass2);

assertThat(configurationClass1.equals(configurationClass1)).isTrue();
assertThat(configurationClass2.equals(configurationClass2)).isTrue();
assertThat(configurationClass1.equals(configurationClass2)).isTrue();
assertThat(configurationClass2.equals(configurationClass1)).isTrue();

assertThat(configurationClass1.equals(configurationClass3)).isFalse();
assertThat(configurationClass3.equals(configurationClass2)).isFalse();

// ---------------------------------------------------------------------

List<BeanMethod> beanMethods1 = getBeanMethods(configurationClass1);
BeanMethod beanMethod_1_0 = beanMethods1.get(0);
BeanMethod beanMethod_1_1 = beanMethods1.get(1);
BeanMethod beanMethod_1_2 = beanMethods1.get(2);

List<BeanMethod> beanMethods2 = getBeanMethods(configurationClass2);
BeanMethod beanMethod_2_0 = beanMethods2.get(0);
BeanMethod beanMethod_2_1 = beanMethods2.get(1);
BeanMethod beanMethod_2_2 = beanMethods2.get(2);

List<BeanMethod> beanMethods3 = getBeanMethods(configurationClass3);
BeanMethod beanMethod_3_0 = beanMethods3.get(0);
BeanMethod beanMethod_3_1 = beanMethods3.get(1);
BeanMethod beanMethod_3_2 = beanMethods3.get(2);

assertThat(beanMethod_1_0.equals(null)).isFalse();
assertThat(beanMethod_1_0).isNotSameAs(beanMethod_2_0);

assertThat(beanMethod_1_0.equals(beanMethod_1_0)).isTrue();
assertThat(beanMethod_1_0.equals(beanMethod_2_0)).isTrue();
assertThat(beanMethod_1_1.equals(beanMethod_2_1)).isTrue();
assertThat(beanMethod_1_2.equals(beanMethod_2_2)).isTrue();

assertThat(beanMethod_1_0.getMetadata().getMethodName()).isEqualTo(beanMethod_3_0.getMetadata().getMethodName());
assertThat(beanMethod_1_0.equals(beanMethod_3_0)).isFalse();
assertThat(beanMethod_1_1.equals(beanMethod_3_1)).isFalse();
assertThat(beanMethod_1_2.equals(beanMethod_3_2)).isFalse();
}

@Test
void verifyHashCode() throws Exception {
ConfigurationClass configurationClass1 = newConfigurationClass(Config1.class);
ConfigurationClass configurationClass2 = newConfigurationClass(Config1.class);
ConfigurationClass configurationClass3 = newConfigurationClass(Config2.class);

assertThat(configurationClass1).hasSameHashCodeAs(configurationClass2);
assertThat(configurationClass1).doesNotHaveSameHashCodeAs(configurationClass3);

// ---------------------------------------------------------------------

List<BeanMethod> beanMethods1 = getBeanMethods(configurationClass1);
BeanMethod beanMethod_1_0 = beanMethods1.get(0);
BeanMethod beanMethod_1_1 = beanMethods1.get(1);
BeanMethod beanMethod_1_2 = beanMethods1.get(2);

List<BeanMethod> beanMethods2 = getBeanMethods(configurationClass2);
BeanMethod beanMethod_2_0 = beanMethods2.get(0);
BeanMethod beanMethod_2_1 = beanMethods2.get(1);
BeanMethod beanMethod_2_2 = beanMethods2.get(2);

List<BeanMethod> beanMethods3 = getBeanMethods(configurationClass3);
BeanMethod beanMethod_3_0 = beanMethods3.get(0);
BeanMethod beanMethod_3_1 = beanMethods3.get(1);
BeanMethod beanMethod_3_2 = beanMethods3.get(2);

assertThat(beanMethod_1_0).hasSameHashCodeAs(beanMethod_2_0);
assertThat(beanMethod_1_1).hasSameHashCodeAs(beanMethod_2_1);
assertThat(beanMethod_1_2).hasSameHashCodeAs(beanMethod_2_2);

assertThat(beanMethod_1_0).doesNotHaveSameHashCodeAs(beanMethod_3_0);
assertThat(beanMethod_1_1).doesNotHaveSameHashCodeAs(beanMethod_3_1);
assertThat(beanMethod_1_2).doesNotHaveSameHashCodeAs(beanMethod_3_2);
}

@Test
void verifyToString() throws Exception {
ConfigurationClass configurationClass = newConfigurationClass(Config1.class);
assertThat(configurationClass.toString())
.startsWith("ConfigurationClass: beanName 'Config1', class path resource");

List<BeanMethod> beanMethods = getBeanMethods(configurationClass);
String prefix = "BeanMethod: " + Config1.class.getName();
assertThat(beanMethods.get(0).toString()).isEqualTo(prefix + ".bean0()");
assertThat(beanMethods.get(1).toString()).isEqualTo(prefix + ".bean1(java.lang.String)");
assertThat(beanMethods.get(2).toString()).isEqualTo(prefix + ".bean2(java.lang.String,java.lang.Integer)");
}


private static ConfigurationClass newConfigurationClass(Class<?> clazz) throws Exception {
ConfigurationClassParser parser = newParser();
parser.parse(clazz.getName(), clazz.getSimpleName());
assertThat(parser.getConfigurationClasses()).hasSize(1);
return parser.getConfigurationClasses().iterator().next();
}

private static ConfigurationClassParser newParser() {
return new ConfigurationClassParser(
new CachingMetadataReaderFactory(),
new FailFastProblemReporter(),
new StandardEnvironment(),
new DefaultResourceLoader(),
new AnnotationBeanNameGenerator(),
new DefaultListableBeanFactory());
}

private static List<BeanMethod> getBeanMethods(ConfigurationClass configurationClass) {
List<BeanMethod> beanMethods = configurationClass.getBeanMethods().stream()
.sorted(Comparator.comparing(beanMethod -> beanMethod.getMetadata().getMethodName()))
.collect(Collectors.toList());
assertThat(beanMethods).hasSize(3);
return beanMethods;
}

static class Config1 {

@Bean
String bean0() {
return "";
}

@Bean
String bean1(String text) {
return "";
}

@Bean
String bean2(String text, Integer num) {
return "";
}

}

static class Config2 {

@Bean
String bean0() {
return "";
}

@Bean
String bean1(String text) {
return "";
}

@Bean
String bean2(String text, Integer num) {
return "";
}

}

}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
Expand Down Expand Up @@ -28,6 +28,7 @@
* to introspect a given {@code Class}.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
*/
public class StandardClassMetadata implements ClassMetadata {
Expand Down Expand Up @@ -119,4 +120,20 @@ public String[] getMemberClassNames() {
return StringUtils.toStringArray(memberClassNames);
}

@Override
public boolean equals(@Nullable Object obj) {
return ((this == obj) || ((obj instanceof StandardClassMetadata) &&
getIntrospectedClass().equals(((StandardClassMetadata) obj).getIntrospectedClass())));
}

@Override
public int hashCode() {
return getIntrospectedClass().hashCode();
}

@Override
public String toString() {
return getClassName();
}

}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
Expand Down Expand Up @@ -36,6 +36,7 @@
* @author Mark Pollack
* @author Chris Beams
* @author Phillip Webb
* @author Sam Brannen
* @since 3.0
*/
public class StandardMethodMetadata implements MethodMetadata {
Expand Down Expand Up @@ -150,4 +151,20 @@ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotatio
annotationName, classValuesAsString, false);
}

@Override
public boolean equals(@Nullable Object obj) {
return ((this == obj) || ((obj instanceof StandardMethodMetadata) &&
this.introspectedMethod.equals(((StandardMethodMetadata) obj).introspectedMethod)));
}

@Override
public int hashCode() {
return this.introspectedMethod.hashCode();
}

@Override
public String toString() {
return this.introspectedMethod.toString();
}

}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
Expand Down Expand Up @@ -31,6 +31,7 @@
* {@link SimpleAnnotationMetadataReadingVisitor}.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
*/
final class SimpleAnnotationMetadata implements AnnotationMetadata {
Expand Down Expand Up @@ -156,4 +157,20 @@ public MergedAnnotations getAnnotations() {
return this.annotations;
}

@Override
public boolean equals(@Nullable Object obj) {
return ((this == obj) || ((obj instanceof SimpleAnnotationMetadata) &&
this.className.equals(((SimpleAnnotationMetadata) obj).className)));
}

@Override
public int hashCode() {
return this.className.hashCode();
}

@Override
public String toString() {
return this.className;
}

}

0 comments on commit dbaf59a

Please sign in to comment.