From 0e3f0bd9d03285e620a689c0c686c595505f66a7 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 20 Jul 2016 18:05:51 +0200 Subject: [PATCH] Avoid JDK proxy against CGLIB Factory interface and assert required type when resolving dependency Issue: SPR-14478 --- .../aop/framework/ProxyProcessorSupport.java | 5 ++- .../AutowiredAnnotationBeanPostProcessor.java | 18 +++++---- .../factory/config/DependencyDescriptor.java | 37 ++++++++++-------- .../support/DefaultListableBeanFactory.java | 17 ++++---- .../annotation/EnableAsyncTests.java | 39 +++++++++++++++++++ 5 files changed, 82 insertions(+), 34 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java index af1cf60398..a94c53b106 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -139,7 +139,8 @@ public class ProxyProcessorSupport extends ProxyConfig implements Ordered, BeanC * @return whether the given interface is an internal language interface */ protected boolean isInternalLanguageInterface(Class ifc) { - return ifc.getName().equals("groovy.lang.GroovyObject"); + return (ifc.getName().equals("groovy.lang.GroovyObject") || + ifc.getName().endsWith(".cglib.proxy.Factory")); } } 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 f75db08e35..1fb74097f1 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 @@ -577,7 +577,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName)) { if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { - this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName); + this.cachedFieldValue = new ShortcutDependencyDescriptor( + desc, autowiredBeanName, field.getType()); } } } @@ -661,8 +662,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean String autowiredBeanName = it.next(); if (beanFactory.containsBean(autowiredBeanName)) { if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { - this.cachedMethodArguments[i] = - new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName); + this.cachedMethodArguments[i] = new ShortcutDependencyDescriptor( + descriptors[i], autowiredBeanName, paramTypes[i]); } } } @@ -705,16 +706,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @SuppressWarnings("serial") private static class ShortcutDependencyDescriptor extends DependencyDescriptor { - private final String shortcutBeanName; + private final String shortcutName; - public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcutBeanName) { + private final Class requiredType; + + public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcutName, Class requiredType) { super(original); - this.shortcutBeanName = shortcutBeanName; + this.shortcutName = shortcutName; + this.requiredType = requiredType; } @Override public Object resolveShortcut(BeanFactory beanFactory) { - return resolveCandidate(this.shortcutBeanName, beanFactory); + return resolveCandidate(this.shortcutName, this.requiredType, beanFactory); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 3533840635..82c6883cc1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -172,21 +172,6 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } - /** - * Resolve the specified bean name, as a candidate result of the matching - * algorithm for this dependency, to a bean instance from the given factory. - *

The default implementation calls {@link BeanFactory#getBean(String)}. - * Subclasses may provide additional arguments or other customizations. - * @param beanName the bean name, as a candidate result for this dependency - * @param beanFactory the associated factory - * @return the bean instance (never {@code null}) - * @since 4.3 - * @see BeanFactory#getBean(String) - */ - public Object resolveCandidate(String beanName, BeanFactory beanFactory) { - return beanFactory.getBean(beanName); - } - /** * Resolve a shortcut for this dependency against the given factory, for example * taking some pre-resolved information into account. @@ -196,12 +181,32 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable * pre-cached information while still receiving {@link InjectionPoint} exposure etc. * @param beanFactory the associated factory * @return the shortcut result if any, or {@code null} if none + * @throws BeansException if the shortcut could not be obtained * @since 4.3.1 */ - public Object resolveShortcut(BeanFactory beanFactory) { + public Object resolveShortcut(BeanFactory beanFactory) throws BeansException { return null; } + /** + * Resolve the specified bean name, as a candidate result of the matching + * algorithm for this dependency, to a bean instance from the given factory. + *

The default implementation calls {@link BeanFactory#getBean(String)}. + * Subclasses may provide additional arguments or other customizations. + * @param beanName the bean name, as a candidate result for this dependency + * @param requiredType the expected type of the bean (as an assertion) + * @param beanFactory the associated factory + * @return the bean instance (never {@code null}) + * @throws BeansException if the bean could not be obtained + * @since 4.3.2 + * @see BeanFactory#getBean(String) + */ + public Object resolveCandidate(String beanName, Class requiredType, BeanFactory beanFactory) + throws BeansException { + + return beanFactory.getBean(beanName, requiredType); + } + /** * Increase this descriptor's nesting level. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 4f861f1d11..51b1a145e8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1200,7 +1200,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } for (String candidateName : candidateNames) { if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) { - result.put(candidateName, descriptor.resolveCandidate(candidateName, this)); + result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); } } if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) { @@ -1208,14 +1208,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); for (String candidateName : candidateNames) { if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { - result.put(candidateName, descriptor.resolveCandidate(candidateName, this)); + result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); } } if (result.isEmpty()) { // Consider self references before as a final pass for (String candidateName : candidateNames) { if (isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { - result.put(candidateName, descriptor.resolveCandidate(candidateName, this)); + result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); } } } @@ -1399,11 +1399,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto public boolean isRequired() { return false; } - @Override - public Object resolveCandidate(String beanName, BeanFactory beanFactory) { - return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) : - super.resolveCandidate(beanName, beanFactory)); + public Object resolveCandidate(String beanName, Class requiredType, BeanFactory beanFactory) { + return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, requiredType, args) : + super.resolveCandidate(beanName, requiredType, beanFactory)); } }; descriptorToUse.increaseNestingLevel(); @@ -1509,8 +1508,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto else { DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { @Override - public Object resolveCandidate(String beanName, BeanFactory beanFactory) { - return beanFactory.getBean(beanName, args); + public Object resolveCandidate(String beanName, Class requiredType, BeanFactory beanFactory) { + return ((AbstractBeanFactory) beanFactory).getBean(beanName, requiredType, args); } }; return doResolveDependency(descriptorToUse, this.beanName, null, null); diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java index 54573d1ec9..5e47a3e790 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java @@ -26,6 +26,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Future; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.aop.Advisor; import org.springframework.aop.framework.Advised; @@ -37,6 +38,7 @@ import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.core.Ordered; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ReflectionUtils; @@ -67,6 +69,18 @@ public class EnableAsyncTests { asyncBean.work(); } + @Test + public void proxyingOccursWithMockitoStub() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(AsyncConfigWithMockito.class, AsyncBeanUser.class); + ctx.refresh(); + + AsyncBeanUser asyncBeanUser = ctx.getBean(AsyncBeanUser.class); + AsyncBean asyncBean = asyncBeanUser.getAsyncBean(); + assertThat(AopUtils.isAopProxy(asyncBean), is(true)); + asyncBean.work(); + } + @Test public void withAsyncBeanWithExecutorQualifiedByName() throws ExecutionException, InterruptedException { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -200,6 +214,20 @@ public class EnableAsyncTests { } + static class AsyncBeanUser { + + private final AsyncBean asyncBean; + + public AsyncBeanUser(AsyncBean asyncBean) { + this.asyncBean = asyncBean; + } + + public AsyncBean getAsyncBean() { + return asyncBean; + } + } + + @EnableAsync(annotation = CustomAsync.class) static class CustomAsyncAnnotationConfig { } @@ -252,6 +280,17 @@ public class EnableAsyncTests { } + @Configuration + @EnableAsync + static class AsyncConfigWithMockito { + + @Bean @Lazy + public AsyncBean asyncBean() { + return Mockito.mock(AsyncBean.class); + } + } + + @Configuration @EnableAsync static class CustomExecutorAsyncConfig implements AsyncConfigurer {