Exclude overloaded from equals & hashCode in MethodOverride

Prior to this commit, the inclusion of the 'overloaded' flag in the
implementations of equals() and hashCode() in MethodOverride could lead
to adverse effects in the outcome of equals() in AbstractBeanDefinition.

For example, given two bean definitions A and B that represent the
exact same bean definition metadata for a bean that relies on method
injection, if A has been validated and B has not, then A.equals(B) will
potentially return false, which is not acceptable behavior.

This commit addresses this issue by removing the 'overloaded' flag from
the implementations of equals() and hashCode() for MethodOverride.

Issue: SPR-11420
This commit is contained in:
Sam Brannen
2014-02-12 16:56:03 +01:00
parent 8ae3aa7f59
commit 9534245660
2 changed files with 76 additions and 40 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -16,62 +16,95 @@
package org.springframework.beans.factory.support;
import junit.framework.TestCase;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.tests.sample.beans.TestBean;
/**
* @author Rob Harrop
*/
public class DefinitionMetadataEqualsHashCodeTests extends TestCase {
import static org.junit.Assert.*;
@SuppressWarnings("serial")
public void testRootBeanDefinitionEqualsAndHashCode() throws Exception {
/**
* Unit tests for {@code equals()} and {@code hashCode()} in bean definitions.
*
* @author Rob Harrop
* @author Sam Brannen
*/
@SuppressWarnings("serial")
public class DefinitionMetadataEqualsHashCodeTests {
@Test
public void rootBeanDefinition() {
RootBeanDefinition master = new RootBeanDefinition(TestBean.class);
RootBeanDefinition equal = new RootBeanDefinition(TestBean.class);
RootBeanDefinition notEqual = new RootBeanDefinition(String.class);
RootBeanDefinition subclass = new RootBeanDefinition(TestBean.class) {};
RootBeanDefinition subclass = new RootBeanDefinition(TestBean.class) {
};
setBaseProperties(master);
setBaseProperties(equal);
setBaseProperties(notEqual);
setBaseProperties(subclass);
assertEqualsContract(master, equal, notEqual, subclass);
assertEquals("Hash code for equal instances should match", master.hashCode(), equal.hashCode());
assertEqualsAndHashCodeContracts(master, equal, notEqual, subclass);
}
@SuppressWarnings("serial")
public void testChildBeanDefinitionEqualsAndHashCode() throws Exception {
/**
* @since 3.2.8
* @see <a href="https://jira.springsource.org/browse/SPR-11420">SPR-11420</a>
*/
@Test
public void rootBeanDefinitionAndMethodOverridesWithDifferentOverloadedValues() {
RootBeanDefinition master = new RootBeanDefinition(TestBean.class);
RootBeanDefinition equal = new RootBeanDefinition(TestBean.class);
setBaseProperties(master);
setBaseProperties(equal);
// Simulate AbstractBeanDefinition.validate() which delegates to
// AbstractBeanDefinition.prepareMethodOverrides():
master.getMethodOverrides().getOverrides().iterator().next().setOverloaded(false);
// But do not simulate validation of the 'equal' bean. As a consequence, a method
// override in 'equal' will be marked as overloaded, but the corresponding
// override in 'master' will not. But... the bean definitions should still be
// considered equal.
assertEquals("Should be equal", master, equal);
assertEquals("Hash code for equal instances must match", master.hashCode(), equal.hashCode());
}
@Test
public void childBeanDefinition() {
ChildBeanDefinition master = new ChildBeanDefinition("foo");
ChildBeanDefinition equal = new ChildBeanDefinition("foo");
ChildBeanDefinition notEqual = new ChildBeanDefinition("bar");
ChildBeanDefinition subclass = new ChildBeanDefinition("foo"){};
ChildBeanDefinition subclass = new ChildBeanDefinition("foo") {
};
setBaseProperties(master);
setBaseProperties(equal);
setBaseProperties(notEqual);
setBaseProperties(subclass);
assertEqualsContract(master, equal, notEqual, subclass);
assertEquals("Hash code for equal instances should match", master.hashCode(), equal.hashCode());
assertEqualsAndHashCodeContracts(master, equal, notEqual, subclass);
}
public void testRuntimeBeanReference() throws Exception {
@Test
public void runtimeBeanReference() {
RuntimeBeanReference master = new RuntimeBeanReference("name");
RuntimeBeanReference equal = new RuntimeBeanReference("name");
RuntimeBeanReference notEqual = new RuntimeBeanReference("someOtherName");
RuntimeBeanReference subclass = new RuntimeBeanReference("name"){};
assertEqualsContract(master, equal, notEqual, subclass);
RuntimeBeanReference subclass = new RuntimeBeanReference("name") {
};
assertEqualsAndHashCodeContracts(master, equal, notEqual, subclass);
}
private void setBaseProperties(AbstractBeanDefinition definition) {
definition.setAbstract(true);
definition.setAttribute("foo", "bar");
definition.setAutowireCandidate(false);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
//definition.getConstructorArgumentValues().addGenericArgumentValue("foo");
// definition.getConstructorArgumentValues().addGenericArgumentValue("foo");
definition.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_OBJECTS);
definition.setDependsOn(new String[]{"foo", "bar"});
definition.setDependsOn(new String[] { "foo", "bar" });
definition.setDestroyMethodName("destroy");
definition.setEnforceDestroyMethod(false);
definition.setEnforceInitMethod(true);
@@ -88,10 +121,15 @@ public class DefinitionMetadataEqualsHashCodeTests extends TestCase {
definition.setSource("foo");
}
private void assertEqualsContract(Object master, Object equal, Object notEqual, Object subclass) {
private void assertEqualsAndHashCodeContracts(Object master, Object equal, Object notEqual, Object subclass) {
assertEquals("Should be equal", master, equal);
assertFalse("Should not be equal", master.equals(notEqual));
assertEquals("Hash code for equal instances should match", master.hashCode(), equal.hashCode());
assertNotEquals("Should not be equal", master, notEqual);
assertNotEquals("Hash code for non-equal instances should not match", master.hashCode(), notEqual.hashCode());
assertEquals("Subclass should be equal", master, subclass);
assertEquals("Hash code for subclass should match", master.hashCode(), subclass.hashCode());
}
}