From 192462902ef3d97c0b6684d467c7d2023ecd5be1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 19 Mar 2015 16:50:15 +0100 Subject: [PATCH] Consistent support for Java 8 default methods (in interfaces implemented by user classes) Covers ReflectionUtils.doWithMethods as well as affected annotation post-processors. Includes an extension of MethodMetadata for the detection of @Bean default methods. Issue: SPR-12822 Issue: SPR-10919 --- .../AutowiredAnnotationBeanPostProcessor.java | 72 ++++---- ...nitDestroyAnnotationBeanPostProcessor.java | 45 ++--- ...wiredAnnotationBeanPostProcessorTests.java | 62 +++++++ .../CommonAnnotationBeanPostProcessor.java | 123 +++++++------- .../annotation/ConfigurationClassParser.java | 31 +++- ...ommonAnnotationBeanPostProcessorTests.java | 56 +++++++ .../ConfigurationClassPostProcessorTests.java | 61 +++++++ ...duledAnnotationBeanPostProcessorTests.java | 106 ++++++++---- .../core/type/MethodMetadata.java | 10 +- .../core/type/StandardMethodMetadata.java | 7 +- .../MethodMetadataReadingVisitor.java | 7 +- .../springframework/util/ReflectionUtils.java | 157 ++++++++++++++---- .../util/ReflectionUtilsTests.java | 48 ++---- ...ersistenceAnnotationBeanPostProcessor.java | 60 ++++--- 14 files changed, 605 insertions(+), 240 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index bd24ebbf23..fe5fa3336a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -380,48 +380,58 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean return metadata; } - private InjectionMetadata buildAutowiringMetadata(Class clazz) { + private InjectionMetadata buildAutowiringMetadata(final Class clazz) { LinkedList elements = new LinkedList(); Class targetClass = clazz; do { - LinkedList currElements = new LinkedList(); - for (Field field : targetClass.getDeclaredFields()) { - AnnotationAttributes ann = findAutowiredAnnotation(field); - if (ann != null) { - if (Modifier.isStatic(field.getModifiers())) { - if (logger.isWarnEnabled()) { - logger.warn("Autowired annotation is not supported on static fields: " + field); + final LinkedList currElements = + new LinkedList(); + + ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() { + @Override + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + AnnotationAttributes ann = findAutowiredAnnotation(field); + if (ann != null) { + if (Modifier.isStatic(field.getModifiers())) { + if (logger.isWarnEnabled()) { + logger.warn("Autowired annotation is not supported on static fields: " + field); + } + return; } - continue; + boolean required = determineRequiredStatus(ann); + currElements.add(new AutowiredFieldElement(field, required)); } - boolean required = determineRequiredStatus(ann); - currElements.add(new AutowiredFieldElement(field, required)); } - } - for (Method method : targetClass.getDeclaredMethods()) { - Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); - if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { - continue; - } - AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); - if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { - if (Modifier.isStatic(method.getModifiers())) { - if (logger.isWarnEnabled()) { - logger.warn("Autowired annotation is not supported on static methods: " + method); + }); + + ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); + if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { + return; + } + AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); + if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { + if (Modifier.isStatic(method.getModifiers())) { + if (logger.isWarnEnabled()) { + logger.warn("Autowired annotation is not supported on static methods: " + method); + } + return; } - continue; - } - if (method.getParameterTypes().length == 0) { - if (logger.isWarnEnabled()) { - logger.warn("Autowired annotation should be used on methods with actual parameters: " + method); + if (method.getParameterTypes().length == 0) { + if (logger.isWarnEnabled()) { + logger.warn("Autowired annotation should be used on methods with parameters: " + method); + } } + boolean required = determineRequiredStatus(ann); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); + currElements.add(new AutowiredMethodElement(method, required, pd)); } - boolean required = determineRequiredStatus(ann); - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); - currElements.add(new AutowiredMethodElement(method, required, pd)); } - } + }); + elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java index 9045f5c802..914c44b6c9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -187,34 +187,39 @@ public class InitDestroyAnnotationBeanPostProcessor return metadata; } - private LifecycleMetadata buildLifecycleMetadata(Class clazz) { + private LifecycleMetadata buildLifecycleMetadata(final Class clazz) { final boolean debug = logger.isDebugEnabled(); LinkedList initMethods = new LinkedList(); LinkedList destroyMethods = new LinkedList(); Class targetClass = clazz; do { - LinkedList currInitMethods = new LinkedList(); - LinkedList currDestroyMethods = new LinkedList(); - for (Method method : targetClass.getDeclaredMethods()) { - if (this.initAnnotationType != null) { - if (method.getAnnotation(this.initAnnotationType) != null) { - LifecycleElement element = new LifecycleElement(method); - currInitMethods.add(element); - if (debug) { - logger.debug("Found init method on class [" + clazz.getName() + "]: " + method); + final LinkedList currInitMethods = new LinkedList(); + final LinkedList currDestroyMethods = new LinkedList(); + + ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + if (initAnnotationType != null) { + if (method.getAnnotation(initAnnotationType) != null) { + LifecycleElement element = new LifecycleElement(method); + currInitMethods.add(element); + if (debug) { + logger.debug("Found init method on class [" + clazz.getName() + "]: " + method); + } + } + } + if (destroyAnnotationType != null) { + if (method.getAnnotation(destroyAnnotationType) != null) { + currDestroyMethods.add(new LifecycleElement(method)); + if (debug) { + logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method); + } } } } - if (this.destroyAnnotationType != null) { - if (method.getAnnotation(this.destroyAnnotationType) != null) { - currDestroyMethods.add(new LifecycleElement(method)); - if (debug) { - logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method); - } - } - } - } + }); + initMethods.addAll(0, currInitMethods); destroyMethods.addAll(currDestroyMethods); targetClass = targetClass.getSuperclass(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 8c0ff7db98..68b24b20ee 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -189,6 +189,32 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testExtendedResourceInjectionWithDefaultMethod() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerResolvableDependency(BeanFactory.class, bf); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition annotatedBd = new RootBeanDefinition(DefaultMethodResourceInjectionBean.class); + bf.registerBeanDefinition("annotatedBean", annotatedBd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb = new NestedTestBean(); + bf.registerSingleton("nestedTestBean", ntb); + + DefaultMethodResourceInjectionBean bean = (DefaultMethodResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertNull(bean.getTestBean2()); + assertSame(tb, bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertSame(ntb, bean.getNestedTestBean()); + assertNull(bean.getBeanFactory()); + assertTrue(bean.baseInjected); + assertTrue(bean.subInjected); + bf.destroySingletons(); + } + @Test public void testExtendedResourceInjectionWithAtRequired() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -1876,6 +1902,42 @@ public class AutowiredAnnotationBeanPostProcessorTests { } + public interface InterfaceWithDefaultMethod { + + @Autowired + void setTestBean2(TestBean testBean2); + + @Autowired + default void injectDefault(ITestBean testBean4) { + markSubInjected(); + } + + void markSubInjected(); + } + + + public static class DefaultMethodResourceInjectionBean extends NonPublicResourceInjectionBean + implements InterfaceWithDefaultMethod { + + public boolean subInjected = false; + + @Override + public void setTestBean2(TestBean testBean2) { + super.setTestBean2(testBean2); + } + + @Override + protected void initBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public void markSubInjected() { + subInjected = true; + } + } + + public static class OptionalResourceInjectionBean extends ResourceInjectionBean { @Autowired(required = false) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index ffb9b55320..96f68ae39e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -66,6 +66,7 @@ import org.springframework.core.Ordered; import org.springframework.jndi.support.SimpleJndiBeanFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -339,75 +340,85 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean return metadata; } - private InjectionMetadata buildResourceMetadata(Class clazz) { + private InjectionMetadata buildResourceMetadata(final Class clazz) { LinkedList elements = new LinkedList(); Class targetClass = clazz; do { - LinkedList currElements = new LinkedList(); - for (Field field : targetClass.getDeclaredFields()) { - if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) { - if (Modifier.isStatic(field.getModifiers())) { - throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields"); + final LinkedList currElements = + new LinkedList(); + + ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() { + @Override + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) { + if (Modifier.isStatic(field.getModifiers())) { + throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields"); + } + currElements.add(new WebServiceRefElement(field, field, null)); } - currElements.add(new WebServiceRefElement(field, field, null)); - } - else if (ejbRefClass != null && field.isAnnotationPresent(ejbRefClass)) { - if (Modifier.isStatic(field.getModifiers())) { - throw new IllegalStateException("@EJB annotation is not supported on static fields"); + else if (ejbRefClass != null && field.isAnnotationPresent(ejbRefClass)) { + if (Modifier.isStatic(field.getModifiers())) { + throw new IllegalStateException("@EJB annotation is not supported on static fields"); + } + currElements.add(new EjbRefElement(field, field, null)); } - currElements.add(new EjbRefElement(field, field, null)); - } - else if (field.isAnnotationPresent(Resource.class)) { - if (Modifier.isStatic(field.getModifiers())) { - throw new IllegalStateException("@Resource annotation is not supported on static fields"); - } - if (!ignoredResourceTypes.contains(field.getType().getName())) { - currElements.add(new ResourceElement(field, field, null)); + else if (field.isAnnotationPresent(Resource.class)) { + if (Modifier.isStatic(field.getModifiers())) { + throw new IllegalStateException("@Resource annotation is not supported on static fields"); + } + if (!ignoredResourceTypes.contains(field.getType().getName())) { + currElements.add(new ResourceElement(field, field, null)); + } } } - } - for (Method method : targetClass.getDeclaredMethods()) { - Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); - if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { - continue; - } - if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { - if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) { - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods"); - } - if (method.getParameterTypes().length != 1) { - throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method); - } - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); - currElements.add(new WebServiceRefElement(method, bridgedMethod, pd)); + }); + + ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); + if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { + return; } - else if (ejbRefClass != null && bridgedMethod.isAnnotationPresent(ejbRefClass)) { - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalStateException("@EJB annotation is not supported on static methods"); - } - if (method.getParameterTypes().length != 1) { - throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method); - } - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); - currElements.add(new EjbRefElement(method, bridgedMethod, pd)); - } - else if (bridgedMethod.isAnnotationPresent(Resource.class)) { - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalStateException("@Resource annotation is not supported on static methods"); - } - Class[] paramTypes = method.getParameterTypes(); - if (paramTypes.length != 1) { - throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method); - } - if (!ignoredResourceTypes.contains(paramTypes[0].getName())) { + if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { + if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods"); + } + if (method.getParameterTypes().length != 1) { + throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method); + } PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); - currElements.add(new ResourceElement(method, bridgedMethod, pd)); + currElements.add(new WebServiceRefElement(method, bridgedMethod, pd)); + } + else if (ejbRefClass != null && bridgedMethod.isAnnotationPresent(ejbRefClass)) { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("@EJB annotation is not supported on static methods"); + } + if (method.getParameterTypes().length != 1) { + throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method); + } + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); + currElements.add(new EjbRefElement(method, bridgedMethod, pd)); + } + else if (bridgedMethod.isAnnotationPresent(Resource.class)) { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("@Resource annotation is not supported on static methods"); + } + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) { + throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method); + } + if (!ignoredResourceTypes.contains(paramTypes[0].getName())) { + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); + currElements.add(new ResourceElement(method, bridgedMethod, pd)); + } } } } - } + }); + elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 3d64a89ba9..5231c306f3 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -288,6 +288,17 @@ class ConfigurationClassParser { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } + // Process default methods on interfaces + for (SourceClass ifc : sourceClass.getInterfaces()) { + beanMethods = ifc.getMetadata().getAnnotatedMethods(Bean.class.getName()); + for (MethodMetadata methodMetadata : beanMethods) { + if (!methodMetadata.isAbstract()) { + // A default method or other concrete method on a Java 8+ interface... + configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); + } + } + } + // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); @@ -474,7 +485,7 @@ class ConfigurationClassParser { } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> - // process it as a @Configuration class + // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); @@ -762,6 +773,22 @@ class ConfigurationClassParser { return asSourceClass(((MetadataReader) this.source).getClassMetadata().getSuperClassName()); } + public Set getInterfaces() throws IOException { + Set result = new LinkedHashSet(); + if (this.source instanceof Class) { + Class sourceClass = (Class) this.source; + for (Class ifcClass : sourceClass.getInterfaces()) { + result.add(asSourceClass(ifcClass)); + } + } + else { + for (String className : this.metadata.getInterfaceNames()) { + result.add(asSourceClass(className)); + } + } + return result; + } + public Set getAnnotations() throws IOException { Set result = new LinkedHashSet(); for (String className : this.metadata.getAnnotationTypes()) { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java index 92c684543f..d4f47fb009 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java @@ -243,6 +243,26 @@ public class CommonAnnotationBeanPostProcessorTests { assertEquals("testBean4", depBeans[0]); } + @Test + public void testResourceInjectionWithDefaultMethod() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + CommonAnnotationBeanPostProcessor bpp = new CommonAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(DefaultMethodResourceInjectionBean.class)); + TestBean tb2 = new TestBean(); + bf.registerSingleton("testBean2", tb2); + NestedTestBean tb7 = new NestedTestBean(); + bf.registerSingleton("testBean7", tb7); + + DefaultMethodResourceInjectionBean bean = (DefaultMethodResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb2, bean.getTestBean2()); + assertSame(2, bean.counter); + + bf.destroySingletons(); + assertSame(3, bean.counter); + } + @Test public void testResourceInjectionWithTwoProcessors() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -694,6 +714,42 @@ public class CommonAnnotationBeanPostProcessorTests { } + public interface InterfaceWithDefaultMethod { + + @Resource + void setTestBean2(TestBean testBean2); + + @Resource + default void setTestBean7(INestedTestBean testBean7) { + increaseCounter(); + } + + @PostConstruct + default void initDefault() { + increaseCounter(); + } + + @PreDestroy + default void destroyDefault() { + increaseCounter(); + } + + void increaseCounter(); + } + + + public static class DefaultMethodResourceInjectionBean extends ResourceInjectionBean + implements InterfaceWithDefaultMethod { + + public int counter = 0; + + @Override + public void increaseCounter() { + counter++; + } + } + + public static class ExtendedEjbInjectionBean extends ResourceInjectionBean { @EJB(name="testBean4", beanInterface=TestBean.class) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index a3a6a8dcfe..55230889c1 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -470,6 +470,36 @@ public class ConfigurationClassPostProcessorTests { beanFactory.registerBeanDefinition("serviceBeanProvider", new RootBeanDefinition(ServiceBeanProvider.class)); new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); beanFactory.preInstantiateSingletons(); + + beanFactory.getBean(ServiceBean.class); + } + + @Test + public void testConfigWithDefaultMethods() { + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(beanFactory); + beanFactory.addBeanPostProcessor(bpp); + beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(ConcreteConfigWithDefaultMethods.class)); + beanFactory.registerBeanDefinition("serviceBeanProvider", new RootBeanDefinition(ServiceBeanProvider.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + beanFactory.preInstantiateSingletons(); + + beanFactory.getBean(ServiceBean.class); + } + + @Test + public void testConfigWithDefaultMethodsUsingAsm() { + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(beanFactory); + beanFactory.addBeanPostProcessor(bpp); + beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(ConcreteConfigWithDefaultMethods.class.getName())); + beanFactory.registerBeanDefinition("serviceBeanProvider", new RootBeanDefinition(ServiceBeanProvider.class.getName())); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + beanFactory.preInstantiateSingletons(); + + beanFactory.getBean(ServiceBean.class); } @Test @@ -944,6 +974,37 @@ public class ConfigurationClassPostProcessorTests { } } + public interface DefaultMethodsConfig { + + @Bean + default ServiceBean serviceBean() { + return provider().getServiceBean(); + } + + @Bean + default ServiceBeanProvider provider() { + return new ServiceBeanProvider(); + } + } + + @Configuration + public static class ConcreteConfigWithDefaultMethods implements DefaultMethodsConfig { + + @Autowired + private ServiceBeanProvider provider; + + @Bean + @Override + public ServiceBeanProvider provider() { + return provider; + } + + @PostConstruct + public void validate() { + Assert.notNull(provider); + } + } + @Primary public static class ServiceBeanProvider { diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java index e6ed71bf2c..0da2dddc0b 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -77,6 +77,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -98,11 +99,11 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Test public void fixedRateTask() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.FixedRateTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -128,6 +129,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -148,19 +150,29 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Test public void severalFixedRatesWithRepeatedScheduledAnnotation() { - BeanDefinition processorDefinition = new - RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class); + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class); severalFixedRates(context, processorDefinition, targetDefinition); } @Test public void severalFixedRatesWithSchedulesContainerAnnotation() { - BeanDefinition processorDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - SeveralFixedRatesWithSchedulesContainerAnnotationTestBean.class); + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithSchedulesContainerAnnotationTestBean.class); + severalFixedRates(context, processorDefinition, targetDefinition); + } + + @Test + public void severalFixedRatesOnBaseClass() { + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesSubBean.class); + severalFixedRates(context, processorDefinition, targetDefinition); + } + + @Test + public void severalFixedRatesOnDefaultMethod() { + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + BeanDefinition targetDefinition = new RootBeanDefinition(FixedRatesDefaultBean.class); severalFixedRates(context, processorDefinition, targetDefinition); } @@ -170,6 +182,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -201,11 +214,11 @@ public class ScheduledAnnotationBeanPostProcessorTests { Assume.group(TestGroup.LONG_RUNNING); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.CronTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -229,11 +242,11 @@ public class ScheduledAnnotationBeanPostProcessorTests { Assume.group(TestGroup.LONG_RUNNING); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.CronWithTimezoneTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(CronWithTimezoneTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -274,8 +287,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { Assume.group(TestGroup.LONG_RUNNING); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.CronWithInvalidTimezoneTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(CronWithInvalidTimezoneTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -286,8 +298,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { public void cronTaskWithMethodValidation() throws InterruptedException { BeanDefinition validationDefinition = new RootBeanDefinition(MethodValidationPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.CronTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(CronTestBean.class); context.registerBeanDefinition("methodValidation", validationDefinition); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); @@ -301,6 +312,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -321,11 +333,11 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Test public void metaAnnotationWithCronExpression() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.MetaAnnotationCronTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationCronTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -356,6 +368,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -386,6 +399,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -417,6 +431,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -443,12 +458,12 @@ public class ScheduledAnnotationBeanPostProcessorTests { Properties properties = new Properties(); properties.setProperty("schedules.businessHours", businessHoursCronExpression); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.PropertyPlaceholderMetaAnnotationTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderMetaAnnotationTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); + Object postProcessor = context.getBean("postProcessor"); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -469,8 +484,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Test(expected = BeanCreationException.class) public void emptyAnnotation() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.EmptyAnnotationTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(EmptyAnnotationTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -479,8 +493,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Test(expected = BeanCreationException.class) public void invalidCron() throws Throwable { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.InvalidCronTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(InvalidCronTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -489,8 +502,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Test(expected = BeanCreationException.class) public void nonVoidReturnType() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.NonVoidReturnTypeTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(NonVoidReturnTypeTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -499,8 +511,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { @Test(expected = BeanCreationException.class) public void nonEmptyParamList() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.NonEmptyParamListTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(NonEmptyParamListTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -542,16 +553,39 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean { - // can use Java 8 repeated @Scheduled once we have Eclipse IDE support for it - @Schedules({ - @Scheduled(fixedRate=4000), - @Scheduled(fixedRate=4000, initialDelay=2000) - }) + @Scheduled(fixedRate=4000) + @Scheduled(fixedRate=4000, initialDelay=2000) public void fixedRate() { } } + static class FixedRatesBaseBean { + + @Scheduled(fixedRate=4000) + @Scheduled(fixedRate=4000, initialDelay=2000) + public void fixedRate() { + } + } + + + static class FixedRatesSubBean extends FixedRatesBaseBean { + } + + + static interface FixedRatesDefaultMethod { + + @Scheduled(fixedRate=4000) + @Scheduled(fixedRate=4000, initialDelay=2000) + default void fixedRate() { + } + } + + + static class FixedRatesDefaultBean implements FixedRatesDefaultMethod { + } + + @Validated static class CronTestBean { diff --git a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java index 585965ca32..feca24e63b 100644 --- a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -41,6 +41,14 @@ public interface MethodMetadata extends AnnotatedTypeMetadata { */ public String getDeclaringClassName(); + /** + * Return whether the underlying method is effectively abstract: + * i.e. marked as abstract on a class or declared as a regular, + * non-default method in an interface. + * @since 4.2 + */ + boolean isAbstract(); + /** * Return whether the underlying method is declared as 'static'. */ diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index 6899ed7bf9..a1df6f80a8 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -84,6 +84,11 @@ public class StandardMethodMetadata implements MethodMetadata { return this.introspectedMethod.getDeclaringClass().getName(); } + @Override + public boolean isAbstract() { + return Modifier.isAbstract(this.introspectedMethod.getModifiers()); + } + @Override public boolean isStatic() { return Modifier.isStatic(this.introspectedMethod.getModifiers()); diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index f88b57b0d0..709776cb0b 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -82,6 +82,11 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho return this.name; } + @Override + public boolean isAbstract() { + return ((this.access & Opcodes.ACC_ABSTRACT) != 0); + } + @Override public boolean isStatic() { return ((this.access & Opcodes.ACC_STATIC) != 0); diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index 8b5db25f71..f14d952d67 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -25,6 +25,7 @@ import java.lang.reflect.UndeclaredThrowableException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -51,11 +52,18 @@ public abstract class ReflectionUtils { private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$"; /** - * Cache for {@link Class#getDeclaredMethods()}, allowing for fast resolution. + * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods + * from Java 8 based interfaces, allowing for fast iteration. */ private static final Map, Method[]> declaredMethodsCache = new ConcurrentReferenceHashMap, Method[]>(256); + /** + * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration. + */ + private static final Map, Field[]> declaredFieldsCache = + new ConcurrentReferenceHashMap, Field[]>(256); + /** * Attempt to find a {@link Field field} on the supplied {@link Class} with the @@ -82,7 +90,7 @@ public abstract class ReflectionUtils { Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); Class searchType = clazz; while (!Object.class.equals(searchType) && searchType != null) { - Field[] fields = searchType.getDeclaredFields(); + Field[] fields = getDeclaredFields(searchType); for (Field field : fields) { if ((name == null || name.equals(field.getName())) && (type == null || type.equals(field.getType()))) { return field; @@ -440,8 +448,8 @@ public abstract class ReflectionUtils { * @see java.lang.reflect.Method#setAccessible */ public static void makeAccessible(Method method) { - if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && - !method.isAccessible()) { + if ((!Modifier.isPublic(method.getModifiers()) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { method.setAccessible(true); } } @@ -455,22 +463,43 @@ public abstract class ReflectionUtils { * @see java.lang.reflect.Constructor#setAccessible */ public static void makeAccessible(Constructor ctor) { - if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && - !ctor.isAccessible()) { + if ((!Modifier.isPublic(ctor.getModifiers()) || + !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { ctor.setAccessible(true); } } + /** + * Perform the given callback operation on all matching methods of the given + * class, as locally declared or equivalent thereof (such as default methods + * on Java 8 based interfaces that the given class implements). + * @param clazz the class to introspect + * @param mc the callback to invoke for each method + * @since 4.2 + * @see #doWithMethods + */ + public static void doWithLocalMethods(Class clazz, MethodCallback mc) { + Method[] methods = getDeclaredMethods(clazz); + for (Method method : methods) { + try { + mc.doWith(method); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); + } + } + } + /** * Perform the given callback operation on all matching methods of the given * class and superclasses. *

The same named method occurring on subclass and superclass will appear * twice, unless excluded by a {@link MethodFilter}. - * @param clazz class to start looking at + * @param clazz the class to introspect * @param mc the callback to invoke for each method * @see #doWithMethods(Class, MethodCallback, MethodFilter) */ - public static void doWithMethods(Class clazz, MethodCallback mc) throws IllegalArgumentException { + public static void doWithMethods(Class clazz, MethodCallback mc) { doWithMethods(clazz, mc, null); } @@ -479,13 +508,11 @@ public abstract class ReflectionUtils { * class and superclasses (or given interface and super-interfaces). *

The same named method occurring on subclass and superclass will appear * twice, unless excluded by the specified {@link MethodFilter}. - * @param clazz class to start looking at + * @param clazz the class to introspect * @param mc the callback to invoke for each method * @param mf the filter that determines the methods to apply the callback to */ - public static void doWithMethods(Class clazz, MethodCallback mc, MethodFilter mf) - throws IllegalArgumentException { - + public static void doWithMethods(Class clazz, MethodCallback mc, MethodFilter mf) { // Keep backing up the inheritance hierarchy. Method[] methods = getDeclaredMethods(clazz); for (Method method : methods) { @@ -496,7 +523,7 @@ public abstract class ReflectionUtils { mc.doWith(method); } catch (IllegalAccessException ex) { - throw new IllegalStateException("Shouldn't be illegal to access method '" + method.getName() + "': " + ex); + throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); } } if (clazz.getSuperclass() != null) { @@ -510,10 +537,11 @@ public abstract class ReflectionUtils { } /** - * Get all declared methods on the leaf class and all superclasses. Leaf - * class methods are included first. + * Get all declared methods on the leaf class and all superclasses. + * Leaf class methods are included first. + * @param leafClass the class to introspect */ - public static Method[] getAllDeclaredMethods(Class leafClass) throws IllegalArgumentException { + public static Method[] getAllDeclaredMethods(Class leafClass) { final List methods = new ArrayList(32); doWithMethods(leafClass, new MethodCallback() { @Override @@ -525,11 +553,12 @@ public abstract class ReflectionUtils { } /** - * Get the unique set of declared methods on the leaf class and all superclasses. Leaf - * class methods are included first and while traversing the superclass hierarchy any methods found - * with signatures matching a method already included are filtered out. + * Get the unique set of declared methods on the leaf class and all superclasses. + * Leaf class methods are included first and while traversing the superclass hierarchy + * any methods found with signatures matching a method already included are filtered out. + * @param leafClass the class to introspect */ - public static Method[] getUniqueDeclaredMethods(Class leafClass) throws IllegalArgumentException { + public static Method[] getUniqueDeclaredMethods(Class leafClass) { final List methods = new ArrayList(32); doWithMethods(leafClass, new MethodCallback() { @Override @@ -562,25 +591,77 @@ public abstract class ReflectionUtils { } /** - * This method retrieves {@link Class#getDeclaredMethods()} from a local cache + * This variant retrieves {@link Class#getDeclaredMethods()} from a local cache * in order to avoid the JVM's SecurityManager check and defensive array copying. + * In addition, it also includes Java 8 default methods from locally implemented + * interfaces, since those are effectively to be treated just like declared methods. + * @param clazz the class to introspect + * @return the cached array of methods + * @see Class#getDeclaredMethods() */ private static Method[] getDeclaredMethods(Class clazz) { Method[] result = declaredMethodsCache.get(clazz); if (result == null) { - result = clazz.getDeclaredMethods(); + Method[] declaredMethods = clazz.getDeclaredMethods(); + List defaultMethods = findConcreteMethodsOnInterfaces(clazz); + if (defaultMethods != null) { + result = new Method[declaredMethods.length + defaultMethods.size()]; + System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); + int index = declaredMethods.length; + for (Method defaultMethod : defaultMethods) { + result[index] = defaultMethod; + index++; + } + } + else { + result = declaredMethods; + } declaredMethodsCache.put(clazz, result); } return result; } + private static List findConcreteMethodsOnInterfaces(Class clazz) { + List result = null; + for (Class ifc : clazz.getInterfaces()) { + for (Method ifcMethod : ifc.getMethods()) { + if (!Modifier.isAbstract(ifcMethod.getModifiers())) { + if (result == null) { + result = new LinkedList(); + } + result.add(ifcMethod); + } + } + } + return result; + } + /** * Invoke the given callback on all fields in the target class, going up the * class hierarchy to get all declared fields. * @param clazz the target class to analyze * @param fc the callback to invoke for each field + * @since 4.2 + * @see #doWithFields */ - public static void doWithFields(Class clazz, FieldCallback fc) throws IllegalArgumentException { + public static void doWithLocalFields(Class clazz, FieldCallback fc) { + for (Field field : getDeclaredFields(clazz)) { + try { + fc.doWith(field); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); + } + } + } + + /** + * Invoke the given callback on all fields in the target class, going up the + * class hierarchy to get all declared fields. + * @param clazz the target class to analyze + * @param fc the callback to invoke for each field + */ + public static void doWithFields(Class clazz, FieldCallback fc) { doWithFields(clazz, fc, null); } @@ -591,15 +672,12 @@ public abstract class ReflectionUtils { * @param fc the callback to invoke for each field * @param ff the filter that determines the fields to apply the callback to */ - public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) - throws IllegalArgumentException { - + public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) { // Keep backing up the inheritance hierarchy. Class targetClass = clazz; do { - Field[] fields = targetClass.getDeclaredFields(); + Field[] fields = getDeclaredFields(targetClass); for (Field field : fields) { - // Skip static and final fields. if (ff != null && !ff.matches(field)) { continue; } @@ -607,7 +685,7 @@ public abstract class ReflectionUtils { fc.doWith(field); } catch (IllegalAccessException ex) { - throw new IllegalStateException("Shouldn't be illegal to access field '" + field.getName() + "': " + ex); + throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); } } targetClass = targetClass.getSuperclass(); @@ -615,13 +693,28 @@ public abstract class ReflectionUtils { while (targetClass != null && targetClass != Object.class); } + /** + * This variant retrieves {@link Class#getDeclaredFields()} from a local cache + * in order to avoid the JVM's SecurityManager check and defensive array copying. + * @param clazz the class to introspect + * @return the cached array of fields + * @see Class#getDeclaredFields() + */ + private static Field[] getDeclaredFields(Class clazz) { + Field[] result = declaredFieldsCache.get(clazz); + if (result == null) { + result = clazz.getDeclaredFields(); + declaredFieldsCache.put(clazz, result); + } + return result; + } + /** * Given the source object and the destination, which must be the same class * or a subclass, copy all fields, including inherited fields. Designed to * work on objects with public no-arg constructors. - * @throws IllegalArgumentException if the arguments are incompatible */ - public static void shallowCopyFieldState(final Object src, final Object dest) throws IllegalArgumentException { + public static void shallowCopyFieldState(final Object src, final Object dest) { if (src == null) { throw new IllegalArgumentException("Source for field copy cannot be null"); } diff --git a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java index 7256a1c17e..3c0c8f84b8 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -66,8 +66,8 @@ public class ReflectionUtilsTests { @Test public void setField() { - final TestObjectSubclassWithNewField testBean = new TestObjectSubclassWithNewField(); - final Field field = ReflectionUtils.findField(TestObjectSubclassWithNewField.class, "name", String.class); + TestObjectSubclassWithNewField testBean = new TestObjectSubclassWithNewField(); + Field field = ReflectionUtils.findField(TestObjectSubclassWithNewField.class, "name", String.class); ReflectionUtils.makeAccessible(field); @@ -79,13 +79,6 @@ public class ReflectionUtilsTests { assertNull(testBean.getName()); } - @Test(expected = IllegalStateException.class) - public void setFieldIllegal() { - final TestObjectSubclassWithNewField testBean = new TestObjectSubclassWithNewField(); - final Field field = ReflectionUtils.findField(TestObjectSubclassWithNewField.class, "name", String.class); - ReflectionUtils.setField(field, testBean, "FooBar"); - } - @Test public void invokeMethod() throws Exception { String rob = "Rob Harrop"; @@ -94,65 +87,50 @@ public class ReflectionUtilsTests { bean.setName(rob); Method getName = TestObject.class.getMethod("getName", (Class[]) null); - Method setName = TestObject.class.getMethod("setName", new Class[] { String.class }); + Method setName = TestObject.class.getMethod("setName", String.class); Object name = ReflectionUtils.invokeMethod(getName, bean); assertEquals("Incorrect name returned", rob, name); String juergen = "Juergen Hoeller"; - ReflectionUtils.invokeMethod(setName, bean, new Object[] { juergen }); + ReflectionUtils.invokeMethod(setName, bean, juergen); assertEquals("Incorrect name set", juergen, bean.getName()); } @Test public void declaresException() throws Exception { - Method remoteExMethod = A.class.getDeclaredMethod("foo", new Class[] { Integer.class }); + Method remoteExMethod = A.class.getDeclaredMethod("foo", Integer.class); assertTrue(ReflectionUtils.declaresException(remoteExMethod, RemoteException.class)); assertTrue(ReflectionUtils.declaresException(remoteExMethod, ConnectException.class)); assertFalse(ReflectionUtils.declaresException(remoteExMethod, NoSuchMethodException.class)); assertFalse(ReflectionUtils.declaresException(remoteExMethod, Exception.class)); - Method illegalExMethod = B.class.getDeclaredMethod("bar", new Class[] { String.class }); + Method illegalExMethod = B.class.getDeclaredMethod("bar", String.class); assertTrue(ReflectionUtils.declaresException(illegalExMethod, IllegalArgumentException.class)); assertTrue(ReflectionUtils.declaresException(illegalExMethod, NumberFormatException.class)); assertFalse(ReflectionUtils.declaresException(illegalExMethod, IllegalStateException.class)); assertFalse(ReflectionUtils.declaresException(illegalExMethod, Exception.class)); } - @Test + @Test(expected = IllegalArgumentException.class) public void copySrcToDestinationOfIncorrectClass() { TestObject src = new TestObject(); String dest = new String(); - try { - ReflectionUtils.shallowCopyFieldState(src, dest); - fail(); - } catch (IllegalArgumentException ex) { - // Ok - } + ReflectionUtils.shallowCopyFieldState(src, dest); } - @Test + @Test(expected = IllegalArgumentException.class) public void rejectsNullSrc() { TestObject src = null; String dest = new String(); - try { - ReflectionUtils.shallowCopyFieldState(src, dest); - fail(); - } catch (IllegalArgumentException ex) { - // Ok - } + ReflectionUtils.shallowCopyFieldState(src, dest); } - @Test + @Test(expected = IllegalArgumentException.class) public void rejectsNullDest() { TestObject src = new TestObject(); String dest = null; - try { - ReflectionUtils.shallowCopyFieldState(src, dest); - fail(); - } catch (IllegalArgumentException ex) { - // Ok - } + ReflectionUtils.shallowCopyFieldState(src, dest); } @Test diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java index 697df37f6f..9b7aac98a8 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -402,39 +402,49 @@ public class PersistenceAnnotationBeanPostProcessor return metadata; } - private InjectionMetadata buildPersistenceMetadata(Class clazz) { + private InjectionMetadata buildPersistenceMetadata(final Class clazz) { LinkedList elements = new LinkedList(); Class targetClass = clazz; do { - LinkedList currElements = new LinkedList(); - for (Field field : targetClass.getDeclaredFields()) { - if (field.isAnnotationPresent(PersistenceContext.class) || - field.isAnnotationPresent(PersistenceUnit.class)) { - if (Modifier.isStatic(field.getModifiers())) { - throw new IllegalStateException("Persistence annotations are not supported on static fields"); + final LinkedList currElements = + new LinkedList(); + + ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() { + @Override + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + if (field.isAnnotationPresent(PersistenceContext.class) || + field.isAnnotationPresent(PersistenceUnit.class)) { + if (Modifier.isStatic(field.getModifiers())) { + throw new IllegalStateException("Persistence annotations are not supported on static fields"); + } + currElements.add(new PersistenceElement(field, field, null)); } - currElements.add(new PersistenceElement(field, field, null)); } - } - for (Method method : targetClass.getDeclaredMethods()) { - Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); - if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { - continue; - } - if ((bridgedMethod.isAnnotationPresent(PersistenceContext.class) || - bridgedMethod.isAnnotationPresent(PersistenceUnit.class)) && - method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalStateException("Persistence annotations are not supported on static methods"); + }); + + ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); + if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { + return; } - if (method.getParameterTypes().length != 1) { - throw new IllegalStateException("Persistence annotation requires a single-arg method: " + method); + if ((bridgedMethod.isAnnotationPresent(PersistenceContext.class) || + bridgedMethod.isAnnotationPresent(PersistenceUnit.class)) && + method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("Persistence annotations are not supported on static methods"); + } + if (method.getParameterTypes().length != 1) { + throw new IllegalStateException("Persistence annotation requires a single-arg method: " + method); + } + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); + currElements.add(new PersistenceElement(method, bridgedMethod, pd)); } - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); - currElements.add(new PersistenceElement(method, bridgedMethod, pd)); } - } + }); + elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); }