From 9f77ef410274d833c296cf296538d273d3f1fecd Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 12 Feb 2014 16:56:03 +0100 Subject: [PATCH] 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 (cherry picked from commit 9534245) --- .../beans/factory/support/MethodOverride.java | 32 ++++---- ...DefinitionMetadataEqualsHashCodeTests.java | 77 ++++++++++++++----- 2 files changed, 72 insertions(+), 37 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java index ecc984a7cec..4e5d4c3a909 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 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. @@ -23,14 +23,15 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * Object representing the override of a method on a managed - * object by the IoC container. + * Object representing the override of a method on a managed object by the IoC + * container. * - *

Note that the override mechanism is not intended as a - * generic means of inserting crosscutting code: use AOP for that. + *

Note that the override mechanism is not intended as a generic + * means of inserting crosscutting code: use AOP for that. * * @author Rod Johnson * @author Juergen Hoeller + * @author Sam Brannen * @since 1.1 */ public abstract class MethodOverride implements BeanMetadataElement { @@ -59,17 +60,18 @@ public abstract class MethodOverride implements BeanMetadataElement { } /** - * Set whether the overridden method has to be considered as overloaded - * (that is, whether arg type matching has to happen). - *

Default is "true"; can be switched to "false" to optimize runtime performance. + * Set whether the overridden method is overloaded (i.e., whether argument + * type matching needs to occur to disambiguate methods of the same name). + *

Default is {@code true}; can be switched to {@code false} to optimize + * runtime performance. */ protected void setOverloaded(boolean overloaded) { this.overloaded = overloaded; } /** - * Return whether the overridden method has to be considered as overloaded - * (that is, whether arg type matching has to happen). + * Return whether the overridden method is overloaded (i.e., whether argument + * type matching needs to occur to disambiguate methods of the same name). */ protected boolean isOverloaded() { return this.overloaded; @@ -87,17 +89,15 @@ public abstract class MethodOverride implements BeanMetadataElement { return this.source; } - /** - * Subclasses must override this to indicate whether they match - * the given method. This allows for argument list checking - * as well as method name checking. + * Subclasses must override this to indicate whether they match the + * given method. This allows for argument list checking as well as method + * name checking. * @param method the method to check * @return whether this override matches the given method */ public abstract boolean matches(Method method); - @Override public boolean equals(Object other) { if (this == other) { @@ -108,7 +108,6 @@ public abstract class MethodOverride implements BeanMetadataElement { } MethodOverride that = (MethodOverride) other; return (ObjectUtils.nullSafeEquals(this.methodName, that.methodName) && - this.overloaded == that.overloaded && ObjectUtils.nullSafeEquals(this.source, that.source)); } @@ -116,7 +115,6 @@ public abstract class MethodOverride implements BeanMetadataElement { public int hashCode() { int hashCode = ObjectUtils.nullSafeHashCode(this.methodName); hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.source); - hashCode = 29 * hashCode + (this.overloaded ? 1 : 0); return hashCode; } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java index f9f1beba04a..7e44b200b35 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java @@ -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,63 +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; +import static org.junit.Assert.*; /** + * Unit tests for {@code equals()} and {@code hashCode()} in bean definitions. + * * @author Rob Harrop + * @author Sam Brannen */ -public class DefinitionMetadataEqualsHashCodeTests extends TestCase { +@SuppressWarnings("serial") +public class DefinitionMetadataEqualsHashCodeTests { - @SuppressWarnings("serial") - public void testRootBeanDefinitionEqualsAndHashCode() throws Exception { + @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 SPR-11420 + */ + @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); @@ -89,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()); } }