diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java index ebdc073b9b..767895a356 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.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. @@ -23,6 +23,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.beans.factory.support.RootBeanDefinition; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @@ -58,6 +59,39 @@ public class BeanMethodPolymorphismTests { assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); } + @Test + public void beanMethodOverridingOnASM() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.registerBeanDefinition("config", new RootBeanDefinition(OverridingConfig.class.getName())); + ctx.setAllowBeanDefinitionOverriding(false); + ctx.refresh(); + assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + assertEquals("overridden", ctx.getBean("testBean", TestBean.class).toString()); + assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + } + + @Test + public void beanMethodOverridingWithNarrowedReturnType() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(NarrowedOverridingConfig.class); + ctx.setAllowBeanDefinitionOverriding(false); + ctx.refresh(); + assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + assertEquals("overridden", ctx.getBean("testBean", TestBean.class).toString()); + assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + } + + @Test + public void beanMethodOverridingWithNarrowedReturnTypeOnASM() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.registerBeanDefinition("config", new RootBeanDefinition(NarrowedOverridingConfig.class.getName())); + ctx.setAllowBeanDefinitionOverriding(false); + ctx.refresh(); + assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + assertEquals("overridden", ctx.getBean("testBean", TestBean.class).toString()); + assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + } + @Test public void beanMethodOverloadingWithoutInheritance() { @@ -138,6 +172,26 @@ public class BeanMethodPolymorphismTests { } + static class ExtendedTestBean extends TestBean { + } + + + @Configuration + static class NarrowedOverridingConfig extends BaseConfig { + + @Bean @Lazy + @Override + public ExtendedTestBean testBean() { + return new ExtendedTestBean() { + @Override + public String toString() { + return "overridden"; + } + }; + } + } + + @Configuration static class SuperConfig { diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 37473c5a77..1a4e53650f 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.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. @@ -160,14 +160,16 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements public boolean hasAnnotatedMethods(String annotationType) { Method[] methods = getIntrospectedClass().getDeclaredMethods(); for (Method method : methods) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; + if (!method.isBridge()) { + for (Annotation ann : method.getAnnotations()) { + if (ann.annotationType().getName().equals(annotationType)) { + return true; + } + else { + for (Annotation metaAnn : ann.annotationType().getAnnotations()) { + if (metaAnn.annotationType().getName().equals(annotationType)) { + return true; + } } } } @@ -177,19 +179,21 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } public Set getAnnotatedMethods(String annotationType) { - Method[] methods = getIntrospectedClass().getDeclaredMethods(); Set annotatedMethods = new LinkedHashSet(); + Method[] methods = getIntrospectedClass().getDeclaredMethods(); for (Method method : methods) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; + if (!method.isBridge()) { + for (Annotation ann : method.getAnnotations()) { + if (ann.annotationType().getName().equals(annotationType)) { + annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); + break; + } + else { + for (Annotation metaAnn : ann.annotationType().getAnnotations()) { + if (metaAnn.annotationType().getName().equals(annotationType)) { + annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); + break; + } } } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 80a49d9e55..e173984608 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.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. @@ -19,12 +19,12 @@ package org.springframework.core.type.classreading; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; @@ -60,6 +60,11 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // Skip bridge methods - we're only interested in original annotation-defining user methods. + // On JDK 8, we'd otherwise run into double detection of the same annotated method... + if ((access & Opcodes.ACC_BRIDGE) != 0) { + return super.visitMethod(access, name, desc, signature, exceptions); + } return new MethodMetadataReadingVisitor(name, access, getClassName(), this.classLoader, this.methodMetadataSet); }